diff --git a/packages/react-aria-components/src/DateField.tsx b/packages/react-aria-components/src/DateField.tsx index f08a593ce85..29234fd8cb4 100644 --- a/packages/react-aria-components/src/DateField.tsx +++ b/packages/react-aria-components/src/DateField.tsx @@ -77,6 +77,7 @@ export const DateFieldContext = createContext, export const TimeFieldContext = createContext, HTMLDivElement>>(null); export const DateFieldStateContext = createContext(null); export const TimeFieldStateContext = createContext(null); +const DateInputFocusableRefContext = createContext | null>(null); /** * A date field allows users to enter and edit date and time values using a keyboard. @@ -255,6 +256,10 @@ export interface DateInputProps extends SlotProps, StyleRenderProps, + /** + * A ref for the first focusable date segment. + */ + focusableRef?: ForwardedRef, children: (segment: IDateSegment) => ReactElement } @@ -296,15 +301,18 @@ const DateInputStandalone = forwardRef((props: DateInputProps, ref: ForwardedRef }); const DateInputInner = forwardRef((props: DateInputProps, ref: ForwardedRef) => { - let {className, children} = props; + let {className, children, focusableRef, ...otherProps} = props; let dateFieldState = useContext(DateFieldStateContext); let timeFieldState = useContext(TimeFieldStateContext); let state = dateFieldState ?? timeFieldState!; return ( - <> + cloneElement(children(segment), {key: i}))} - + ); }); @@ -378,7 +386,13 @@ export const DateSegment = /*#__PURE__*/ (forwardRef as forwardRefType)(function let dateFieldState = useContext(DateFieldStateContext); let timeFieldState = useContext(TimeFieldStateContext); let state = dateFieldState ?? timeFieldState!; - let domRef = useObjectRef(ref); + let focusableRef = useContext(DateInputFocusableRefContext); + + // If this is the first editable segment and focusableRef is provided, use it + let isFirstEditableSegment = segment.isEditable && + segment.type === state.segments.find(s => s.isEditable)?.type; + + let domRef = useObjectRef((isFirstEditableSegment && focusableRef) ? focusableRef : ref); let {segmentProps} = useDateSegment(segment, state, domRef); let {focusProps, isFocused, isFocusVisible} = useFocusRing(); let {hoverProps, isHovered} = useHover({...otherProps, isDisabled: state.isDisabled || segment.type === 'literal'}); diff --git a/packages/react-aria-components/test/DateField.test.js b/packages/react-aria-components/test/DateField.test.js index 6f9830aba14..667a7da2f4a 100644 --- a/packages/react-aria-components/test/DateField.test.js +++ b/packages/react-aria-components/test/DateField.test.js @@ -478,4 +478,25 @@ describe('DateField', () => { expect(segements[1]).toHaveTextContent('dd'); expect(segements[2]).toHaveTextContent('yyyy'); }); + + it('should support focusableRef', () => { + let focusableRef = React.createRef(); + let {getAllByRole} = render( + + + + {segment => } + + + ); + + let segments = getAllByRole('spinbutton'); + expect(focusableRef.current).toBe(segments[0]); + + act(() => { + focusableRef.current.focus(); + }); + + expect(document.activeElement).toBe(segments[0]); + }); }); diff --git a/packages/react-aria-components/test/DatePicker.test.js b/packages/react-aria-components/test/DatePicker.test.js index 5298849b1a5..ab367eab685 100644 --- a/packages/react-aria-components/test/DatePicker.test.js +++ b/packages/react-aria-components/test/DatePicker.test.js @@ -334,4 +334,29 @@ describe('DatePicker', () => { let input = group.querySelector('.react-aria-DateInput'); expect(input).toHaveTextContent('5/30/2000'); }); + + it('should support focusableRef on DateInput', () => { + let focusableRef = React.createRef(); + let {getByRole} = render( + + + + + {(segment) => } + + + + + ); + + let group = getByRole('group'); + let segments = within(group).getAllByRole('spinbutton'); + expect(focusableRef.current).toBe(segments[0]); + + act(() => { + focusableRef.current.focus(); + }); + + expect(document.activeElement).toBe(segments[0]); + }); });