import { useCallback, useState } from 'react'; import { parseDate } from '@internationalized/date'; // https://regex101.com/r/SDo13Q/1 const ISO_DATE_REGEX = /^\d{4}-\d{2}-\d{2}$/; function parseISODate(text: string): string | null { const trimmed = text.trim(); if (ISO_DATE_REGEX.test(trimmed) === false) return null; try { return parseDate(trimmed).toString(); } catch { return null; } } export interface UseDatePickerClipboardOptions { value?: string | null; commit?: (value: string | null) => void; onSelectAll?: (value: string | null) => void; readOnly?: boolean; disabled?: boolean; } export interface DatePickerClipboardHandlers { onCopyCapture: (event: React.ClipboardEvent) => void; onCutCapture: (event: React.ClipboardEvent) => void; onPasteCapture: (event: React.ClipboardEvent) => void; onKeyDownCapture: (event: React.KeyboardEvent) => void; onDoubleClick: (event: React.MouseEvent) => void; onMouseDownCapture: (event: React.MouseEvent) => void; onBlur: (event: React.FocusEvent) => void; } const isMetaCombination = (event: React.KeyboardEvent, key: string): boolean => (event.metaKey || event.ctrlKey) && event.key.toLowerCase() === key; export const useDatePickerClipboard = ({ value, commit, onSelectAll, readOnly, disabled, }: UseDatePickerClipboardOptions): [boolean, DatePickerClipboardHandlers] => { const [isAllSelected, setIsAllSelected] = useState(false); const onCopyCapture = useCallback( (event: React.ClipboardEvent) => { if (disabled) return; if (!isAllSelected) return; event.preventDefault(); event.stopPropagation(); if (typeof value === 'string' && value !== '') { event.clipboardData.setData('text/plain', value); } }, [value, disabled, isAllSelected] ); const onCutCapture = useCallback( (event: React.ClipboardEvent) => { if (disabled || readOnly) return; if (!isAllSelected) return; event.preventDefault(); event.stopPropagation(); if (typeof value === 'string' && value !== '') { event.clipboardData.setData('text/plain', value); } commit?.(null); setIsAllSelected(false); }, [value, disabled, readOnly, isAllSelected, commit] ); const onPasteCapture = useCallback( (event: React.ClipboardEvent) => { if (disabled || readOnly) return; const isoDate = parseISODate(event.clipboardData.getData('text/plain')); if (isoDate && commit) { event.preventDefault(); event.stopPropagation(); commit(isoDate); setIsAllSelected(false); return; } if (isAllSelected) { event.preventDefault(); event.stopPropagation(); setIsAllSelected(false); } }, [commit, readOnly, disabled, isAllSelected] ); const onKeyDownCapture = useCallback( (event: React.KeyboardEvent) => { if (disabled) return; if (isMetaCombination(event, 'a')) { event.preventDefault(); setIsAllSelected(true); onSelectAll?.(value ?? null); return; } if (isAllSelected && !readOnly && (event.key === 'Backspace' || event.key === 'Delete')) { event.preventDefault(); event.stopPropagation(); commit?.(null); setIsAllSelected(false); return; } // Keep isAllSelected on Meta/Ctrl so Cmd+C/X/V and undo/redo can run // while the gesture is in effect; clipboard handlers clear it themselves. if (event.metaKey || event.ctrlKey) return; if (isAllSelected) setIsAllSelected(false); }, [disabled, readOnly, isAllSelected, commit, onSelectAll, value] ); const onDoubleClick = useCallback( (event: React.MouseEvent) => { if (disabled) return; event.preventDefault(); setIsAllSelected(true); onSelectAll?.(value ?? null); }, [disabled, onSelectAll, value] ); const onMouseDownCapture = useCallback(() => { setIsAllSelected(current => (current ? false : current)); }, []); const onBlur = useCallback((event: React.FocusEvent) => { if (!event.currentTarget.contains(event.relatedTarget as Node | null)) { setIsAllSelected(false); } }, []); return [ isAllSelected, { onCopyCapture, onCutCapture, onPasteCapture, onKeyDownCapture, onDoubleClick, onMouseDownCapture, onBlur, }, ]; };