Skip to content

chore: fix S2 datepicker errorMessage type#9909

Merged
yihuiliao merged 5 commits intomainfrom
calendar-types
Apr 11, 2026
Merged

chore: fix S2 datepicker errorMessage type#9909
yihuiliao merged 5 commits intomainfrom
calendar-types

Conversation

@yihuiliao
Copy link
Copy Markdown
Member

@yihuiliao yihuiliao commented Apr 10, 2026

i know this isn't the approach we discussed yesterday but the reason is explained in the comment

✅ Pull Request Checklist:

  • Included link to corresponding React Spectrum GitHub Issue.
  • Added/updated unit tests and storybook for this change (for new code or code which already has tests).
  • Filled out test instructions.
  • Updated documentation (if it already exists for this component).
  • Looked at the Accessibility Practices for this feature - Aria Practices

📝 Test Instructions:

🧢 Your Project:

@github-actions github-actions bot added the S2 label Apr 10, 2026
@rspbot
Copy link
Copy Markdown

rspbot commented Apr 10, 2026

LFDanLu
LFDanLu previously approved these changes Apr 10, 2026

// Ideally, we could omit errorMessage here and let S2 Calendar read it from RAC's CalendarContext which already contains the resolved value via calendarProps from useDatePicker.
// However, RAC's CalendarProps omits errorMessage, so reading it back from the context would require an unsafe cast. Instead, we resolve it here using the same logic as useDatePicker.
let resolvedErrorMessage = typeof errorMessage === 'function' ? errorMessage(state.displayValidation) : (errorMessage || state.displayValidation.validationErrors.join(' '));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit unfortunate that we have to duplicate this logic here, but I don't see a great way around this since the RAC Datepicker shouldn't accept errorMessage as a prop since RAC makes use of FieldError...

reidbarber
reidbarber previously approved these changes Apr 10, 2026
Copy link
Copy Markdown
Member

@devongovett devongovett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this for RangeCalendar too?

@yihuiliao yihuiliao dismissed stale reviews from reidbarber and LFDanLu via 52e3935 April 10, 2026 22:22
@rspbot
Copy link
Copy Markdown

rspbot commented Apr 10, 2026

@rspbot
Copy link
Copy Markdown

rspbot commented Apr 10, 2026

## API Changes

@react-spectrum/s2

/@react-spectrum/s2:DatePicker

 DatePicker <T extends DateValue> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   contextualHelp?: ReactNode
   createCalendar?: (CalendarIdentifier) => Calendar
   defaultOpen?: boolean
   defaultValue?: DateValue | null
   description?: ReactNode
-  errorMessage?: ReactNode
+  errorMessage?: ReactNode | (ValidationResult) => ReactNode
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   id?: string
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxValue?: DateValue | null
   maxVisibleMonths?: number = 1
   minValue?: DateValue | null
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (MappedDateValue<DateValue> | null) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldFlip?: boolean = true
   shouldForceLeadingZeros?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (MappedDateValue<DateValue>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: DateValue | null
 }

/@react-spectrum/s2:DateRangePicker

 DateRangePicker <T extends DateValue> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsNonContiguousRanges?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   contextualHelp?: ReactNode
   createCalendar?: (CalendarIdentifier) => Calendar
   defaultOpen?: boolean
   defaultValue?: RangeValue<DateValue> | null
   description?: ReactNode
   endName?: string
-  errorMessage?: ReactNode
+  errorMessage?: ReactNode | (ValidationResult) => ReactNode
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   id?: string
   interactOutsideBehavior?: 'clear' | 'reset' | 'select' = 'select'
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxValue?: DateValue | null
   maxVisibleMonths?: number = 1
   minValue?: DateValue | null
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (RangeValue<MappedDateValue<DateValue>> | null) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldFlip?: boolean = true
   shouldForceLeadingZeros?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   startName?: string
   styles?: StylesProp
   validate?: (RangeValue<MappedDateValue<DateValue>>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: RangeValue<DateValue> | null
 }

/@react-spectrum/s2:DatePickerProps

 DatePickerProps <T extends DateValue> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoComplete?: string
   autoFocus?: boolean
   contextualHelp?: ReactNode
   createCalendar?: (CalendarIdentifier) => Calendar
   defaultOpen?: boolean
   defaultValue?: DateValue | null
   description?: ReactNode
-  errorMessage?: ReactNode
+  errorMessage?: ReactNode | (ValidationResult) => ReactNode
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   id?: string
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxValue?: DateValue | null
   maxVisibleMonths?: number = 1
   minValue?: DateValue | null
   name?: string
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (MappedDateValue<DateValue> | null) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldFlip?: boolean = true
   shouldForceLeadingZeros?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   styles?: StylesProp
   validate?: (MappedDateValue<DateValue>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: DateValue | null
 }

/@react-spectrum/s2:DateRangePickerProps

 DateRangePickerProps <T extends DateValue> {
   UNSAFE_className?: UnsafeClassName
   UNSAFE_style?: CSSProperties
   allowsNonContiguousRanges?: boolean
   aria-describedby?: string
   aria-details?: string
   aria-label?: string
   aria-labelledby?: string
   autoFocus?: boolean
   contextualHelp?: ReactNode
   createCalendar?: (CalendarIdentifier) => Calendar
   defaultOpen?: boolean
   defaultValue?: RangeValue<DateValue> | null
   description?: ReactNode
   endName?: string
-  errorMessage?: ReactNode
+  errorMessage?: ReactNode | (ValidationResult) => ReactNode
   firstDayOfWeek?: 'sun' | 'mon' | 'tue' | 'wed' | 'thu' | 'fri' | 'sat'
   form?: string
   granularity?: Granularity
   hideTimeZone?: boolean = false
   id?: string
   interactOutsideBehavior?: 'clear' | 'reset' | 'select' = 'select'
   isDateUnavailable?: (DateValue) => boolean
   isDisabled?: boolean
   isInvalid?: boolean
   isOpen?: boolean
   isReadOnly?: boolean
   isRequired?: boolean
   label?: ReactNode
   labelAlign?: Alignment = 'start'
   labelPosition?: LabelPosition = 'top'
   maxValue?: DateValue | null
   maxVisibleMonths?: number = 1
   minValue?: DateValue | null
   necessityIndicator?: NecessityIndicator = 'icon'
   onBlur?: (FocusEvent<Target>) => void
   onChange?: (RangeValue<MappedDateValue<DateValue>> | null) => void
   onFocus?: (FocusEvent<Target>) => void
   onFocusChange?: (boolean) => void
   onKeyDown?: (KeyboardEvent) => void
   onKeyUp?: (KeyboardEvent) => void
   onOpenChange?: (boolean) => void
   pageBehavior?: PageBehavior = visible
   placeholderValue?: DateValue | null
   shouldCloseOnSelect?: boolean | () => boolean = true
   shouldFlip?: boolean = true
   shouldForceLeadingZeros?: boolean
   size?: 'S' | 'M' | 'L' | 'XL' = 'M'
   slot?: string | null
   startName?: string
   styles?: StylesProp
   validate?: (RangeValue<MappedDateValue<DateValue>>) => ValidationError | boolean | null | undefined
   validationBehavior?: 'native' | 'aria' = 'native'
   value?: RangeValue<DateValue> | null
 }

@yihuiliao yihuiliao added this pull request to the merge queue Apr 10, 2026
Merged via the queue into main with commit 9e18b4a Apr 11, 2026
29 checks passed
@yihuiliao yihuiliao deleted the calendar-types branch April 11, 2026 00:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants