Fix Firefox DatePicker popup positioning at top-left

When Firefox input variant falls through to button variant, the
positioning logic, close handler, and click-outside handler still
checked variant==='input' and used wrapperRef (which is unattached).
Introduced usesNativeInput flag (input variant + not Firefox) so all
three handlers correctly use triggerRef for Firefox fallback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Kyle 2026-03-03 15:56:05 +08:00
parent e7979afba3
commit 63b3a3a073

View File

@ -144,8 +144,10 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
}, [value, open]); }, [value, open]);
// Position popup // Position popup
// Firefox + input variant falls through to button variant, so use triggerRef
const usesNativeInput = variant === 'input' && !isFirefox;
const updatePosition = React.useCallback(() => { const updatePosition = React.useCallback(() => {
const el = variant === 'input' ? wrapperRef.current : triggerRef.current; const el = usesNativeInput ? wrapperRef.current : triggerRef.current;
if (!el) return; if (!el) return;
const rect = el.getBoundingClientRect(); const rect = el.getBoundingClientRect();
const popupHeight = mode === 'datetime' ? 370 : 320; const popupHeight = mode === 'datetime' ? 370 : 320;
@ -156,7 +158,7 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
top: flipped ? rect.top - popupHeight - 4 : rect.bottom + 4, top: flipped ? rect.top - popupHeight - 4 : rect.bottom + 4,
left: Math.min(rect.left, window.innerWidth - 290), left: Math.min(rect.left, window.innerWidth - 290),
}); });
}, [mode, variant]); }, [mode, usesNativeInput]);
React.useEffect(() => { React.useEffect(() => {
if (!open) return; if (!open) return;
@ -172,13 +174,13 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
const closePopup = React.useCallback( const closePopup = React.useCallback(
(refocusTrigger = true) => { (refocusTrigger = true) => {
setOpen(false); setOpen(false);
if (variant === 'button') { if (!usesNativeInput) {
onBlur?.(); onBlur?.();
} else if (refocusTrigger) { } else if (refocusTrigger) {
setTimeout(() => inputElRef.current?.focus(), 0); setTimeout(() => inputElRef.current?.focus(), 0);
} }
}, },
[variant, onBlur] [usesNativeInput, onBlur]
); );
// Dismiss on click outside // Dismiss on click outside
@ -186,13 +188,13 @@ const DatePicker = React.forwardRef<HTMLButtonElement, DatePickerProps>(
if (!open) return; if (!open) return;
const handler = (e: MouseEvent) => { const handler = (e: MouseEvent) => {
if (popupRef.current?.contains(e.target as Node)) return; if (popupRef.current?.contains(e.target as Node)) return;
if (variant === 'button' && triggerRef.current?.contains(e.target as Node)) return; if (!usesNativeInput && triggerRef.current?.contains(e.target as Node)) return;
if (variant === 'input' && wrapperRef.current?.contains(e.target as Node)) return; if (usesNativeInput && wrapperRef.current?.contains(e.target as Node)) return;
closePopup(false); closePopup(false);
}; };
document.addEventListener('mousedown', handler); document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler);
}, [open, variant, closePopup]); }, [open, usesNativeInput, closePopup]);
// Dismiss on Escape // Dismiss on Escape
React.useEffect(() => { React.useEffect(() => {