From 5a77e0576a42339e0c872542304e24a14748d1f5 Mon Sep 17 00:00:00 2001 From: ZedingZhang Date: Tue, 15 Sep 2026 13:41:06 +0800 Subject: [PATCH] fix: support custom focus targets in DateRangePicker --- .../react-aria-components/src/DatePicker.tsx | 10 +++++----- packages/react-aria-components/test/types.tsx | 7 +++++++ .../src/datepicker/useDateRangePicker.ts | 18 +++++++++++------- packages/react-stately/src/datepicker/types.ts | 16 +++++++++++----- .../src/datepicker/useDateRangePickerState.ts | 12 +++++++----- 5 files changed, 41 insertions(+), 22 deletions(-) diff --git a/packages/react-aria-components/src/DatePicker.tsx b/packages/react-aria-components/src/DatePicker.tsx index 0f6484a159d..f2f62ec0856 100644 --- a/packages/react-aria-components/src/DatePicker.tsx +++ b/packages/react-aria-components/src/DatePicker.tsx @@ -127,13 +127,13 @@ export interface DatePickerProps */ className?: ClassNameOrFunction; } -export interface DateRangePickerProps +export interface DateRangePickerProps extends Omit< - AriaDateRangePickerProps, + AriaDateRangePickerProps, 'label' | 'description' | 'errorMessage' | 'validationState' | 'validationBehavior' >, - Pick, 'shouldCloseOnSelect'>, + Pick, 'shouldCloseOnSelect'>, RACValidation, RenderProps, SlotProps, @@ -271,8 +271,8 @@ export const DatePicker = /*#__PURE__*/ (forwardRef as forwardRefType)(function * users to enter or select a date and time range. */ export const DateRangePicker = /*#__PURE__*/ (forwardRef as forwardRefType)( - function DateRangePicker( - props: DateRangePickerProps, + function DateRangePicker( + props: DateRangePickerProps, ref: ForwardedRef ) { [props, ref] = useContextProps(props, ref, DateRangePickerContext); diff --git a/packages/react-aria-components/test/types.tsx b/packages/react-aria-components/test/types.tsx index 3eae9dfa5ff..feec10bca42 100644 --- a/packages/react-aria-components/test/types.tsx +++ b/packages/react-aria-components/test/types.tsx @@ -96,3 +96,10 @@ motion(RAC.Text); motion(RAC.TextField); motion(RAC.ToggleButton); motion(RAC.Tooltip); + +// DateRangePicker should accept form library focus handlers targeting an HTMLElement. +let dateRangePickerFormProps = { + onBlur: (event?: React.FocusEvent) => event?.currentTarget.focus(), + onFocus: (event?: React.FocusEvent) => event?.currentTarget.focus() +} satisfies RAC.DateRangePickerProps; +; diff --git a/packages/react-aria/src/datepicker/useDateRangePicker.ts b/packages/react-aria/src/datepicker/useDateRangePicker.ts index 29d7833c254..00968742a61 100644 --- a/packages/react-aria/src/datepicker/useDateRangePicker.ts +++ b/packages/react-aria/src/datepicker/useDateRangePicker.ts @@ -36,6 +36,7 @@ import { privateValidationStateProp } from 'react-stately/private/form/useFormValidationState'; import {filterDOMProps} from '../utils/filterDOMProps'; +import {FocusEvent, useMemo, useRef} from 'react'; import {focusManagerSymbol, roleSymbol} from './useDateField'; import intlMessages from '../../intl/datepicker/*.json'; import {mergeProps} from '../utils/mergeProps'; @@ -48,10 +49,13 @@ import {useFocusWithin} from '../interactions/useFocusWithin'; import {useId} from '../utils/useId'; import {useLocale} from '../i18n/I18nProvider'; import {useLocalizedStringFormatter} from '../i18n/useLocalizedStringFormatter'; -import {useMemo, useRef} from 'react'; -export interface AriaDateRangePickerProps - extends DateRangePickerProps, AriaLabelingProps, Omit, DOMProps {} +export interface AriaDateRangePickerProps + extends + DateRangePickerProps, + AriaLabelingProps, + Omit, + DOMProps {} export interface DateRangePickerAria extends ValidationResult { /** Props for the date range picker's visible label element, if any. */ @@ -79,8 +83,8 @@ export interface DateRangePickerAria extends ValidationResult { * A date range picker combines two DateFields and a RangeCalendar popover to allow * users to enter or select a date and time range. */ -export function useDateRangePicker( - props: AriaDateRangePickerProps, +export function useDateRangePicker( + props: AriaDateRangePickerProps, state: DateRangePickerState, ref: RefObject ): DateRangePickerAria { @@ -158,14 +162,14 @@ export function useDateRangePicker( let dialog = document.getElementById(dialogId); if (!nodeContains(dialog, e.relatedTarget)) { isFocused.current = false; - props.onBlur?.(e); + props.onBlur?.(e as FocusEvent); props.onFocusChange?.(false); } }, onFocusWithin: e => { if (!isFocused.current) { isFocused.current = true; - props.onFocus?.(e); + props.onFocus?.(e as FocusEvent); props.onFocusChange?.(true); } } diff --git a/packages/react-stately/src/datepicker/types.ts b/packages/react-stately/src/datepicker/types.ts index 4acc8ffe858..56ba0a8a6fa 100644 --- a/packages/react-stately/src/datepicker/types.ts +++ b/packages/react-stately/src/datepicker/types.ts @@ -43,8 +43,13 @@ export type MappedTimeValue = T extends ZonedDateTime : never; export type Granularity = 'day' | 'hour' | 'minute' | 'second'; -interface DateFieldBase - extends InputBase, Validation>, FocusableProps, LabelableProps, HelpTextProps { +interface DateFieldBase + extends + InputBase, + Validation>, + FocusableProps, + LabelableProps, + HelpTextProps { /** The minimum allowed date that a user may select. */ minValue?: DateValue | null; /** The maximum allowed date that a user may select. */ @@ -85,7 +90,8 @@ interface DateFieldBase export interface DateFieldProps extends DateFieldBase, ValueBase | null> {} -interface DatePickerBase extends DateFieldBase, OverlayTriggerProps { +interface DatePickerBase + extends DateFieldBase, OverlayTriggerProps { /** * Controls the behavior of paging. Pagination either works by advancing the visible page by * visibleDuration (default) or one unit of visibleDuration. @@ -102,9 +108,9 @@ interface DatePickerBase extends DateFieldBase, OverlayT export interface DatePickerProps extends DatePickerBase, ValueBase | null> {} -export interface DateRangePickerProps +export interface DateRangePickerProps extends - Omit, 'validate' | 'isDateUnavailable'>, + Omit, 'validate' | 'isDateUnavailable'>, Validation>>, ValueBase | null, RangeValue> | null> { /** diff --git a/packages/react-stately/src/datepicker/useDateRangePickerState.ts b/packages/react-stately/src/datepicker/useDateRangePickerState.ts index 3701896563d..685cf289645 100644 --- a/packages/react-stately/src/datepicker/useDateRangePickerState.ts +++ b/packages/react-stately/src/datepicker/useDateRangePickerState.ts @@ -34,8 +34,9 @@ import {useControlledState} from '../utils/useControlledState'; import {useMemo, useState} from 'react'; export interface DateRangePickerStateOptions< - T extends DateValue = DateValue -> extends DateRangePickerProps { + T extends DateValue = DateValue, + Target extends Element = Element +> extends DateRangePickerProps { /** * Determines whether the date picker popover should close automatically when a date is selected. * @@ -106,9 +107,10 @@ export interface DateRangePickerState extends OverlayTriggerState, FormValidatio * A date range picker combines two DateFields and a RangeCalendar popover to allow * users to enter or select a date and time range. */ -export function useDateRangePickerState( - props: DateRangePickerStateOptions -): DateRangePickerState { +export function useDateRangePickerState< + T extends DateValue = DateValue, + Target extends Element = Element +>(props: DateRangePickerStateOptions): DateRangePickerState { let overlayState = useOverlayTriggerState(props); let [controlledValue, setControlledValue] = useControlledState< DateRange | null,