import React, { useContext, useEffect, useRef } from 'react'; import type { DateSegmentProps } from 'react-aria-components'; import { DateFieldStateContext, DateInput as RACDateInput, DateSegment } from 'react-aria-components'; import type { DateSegmentType } from 'react-stately'; import classNames from 'classnames'; import type { SafeExtract } from '@shoptet/utils'; import { InputLoader } from '../../InputLoader'; import type { CommonInputProps } from '../../types'; type DateSegmentObject = DateSegmentProps['segment']; type AllowedClipboardSegmentType = SafeExtract; function isAllowedClipboardSegmentType(type: string): type is AllowedClipboardSegmentType { return type === 'day' || type === 'month' || type === 'year'; } function segmentFits(type: AllowedClipboardSegmentType, value: number): boolean { return ( (type === 'day' && value >= 1 && value <= 31) || (type === 'month' && value >= 1 && value <= 12) || (type === 'year' && value >= 1 && value <= 9999) ); } // https://regex101.com/?regex=%5E%5Cd%2B%24&testString=&flags=&flavor=javascript&delimiter=%2F const DIGITS_ONLY_REGEX = /^\d+$/; // RAC's DateSegmentProps doesn't expose clipboard events, so we attach // listeners natively via a ref. The Group-level handler intercepts // full-date copy/paste in the capture phase, so these only fire for a // single focused segment. function SegmentWithPaste({ segment, ...rest }: { segment: DateSegmentObject }) { const state = useContext(DateFieldStateContext); const ref = useRef(null); useEffect(() => { const element = ref.current; if (element && state) { const segmentType = segment.type; if (isAllowedClipboardSegmentType(segmentType)) { const handleCopy = (event: ClipboardEvent) => { if (element.dataset.placeholder === 'true') return; const text = element.textContent?.trim(); if (text) { event.preventDefault(); event.clipboardData?.setData('text/plain', text); } }; const handlePaste = (event: ClipboardEvent) => { if (state.isDisabled || state.isReadOnly) return; const text = (event.clipboardData?.getData('text/plain') ?? '').trim(); if (DIGITS_ONLY_REGEX.test(text)) { event.preventDefault(); const parsed = parseInt(text, 10); if (segmentFits(segmentType, parsed)) { state.setSegment(segmentType, parsed); } } }; element.addEventListener('copy', handleCopy); element.addEventListener('paste', handlePaste); return () => { element.removeEventListener('copy', handleCopy); element.removeEventListener('paste', handlePaste); }; } } }, [state, segment.type]); return ; } export interface DateInputProps extends Pick { /** * Display loading indicator. */ loading?: boolean; } export const DateInput = React.forwardRef(function DateInput( { error, loading, warning, ...rest }, ref ) { return ( {segment => } ); });