@proyecto-viviana/solidaria-components 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/dist/ActionBar.d.ts.map +1 -1
  2. package/dist/ActionBar.js +2 -11
  3. package/dist/ActionBar.js.map +1 -1
  4. package/dist/ActionBar.jsx +2 -11
  5. package/dist/ActionBar.jsx.map +1 -1
  6. package/dist/ActionGroup.d.ts.map +1 -1
  7. package/dist/ActionGroup.js +1 -1
  8. package/dist/ActionGroup.js.map +1 -1
  9. package/dist/ActionGroup.jsx +1 -1
  10. package/dist/ActionGroup.jsx.map +1 -1
  11. package/dist/Autocomplete.d.ts +2 -2
  12. package/dist/Autocomplete.d.ts.map +1 -1
  13. package/dist/Autocomplete.js +7 -7
  14. package/dist/Autocomplete.js.map +1 -1
  15. package/dist/Autocomplete.jsx +7 -7
  16. package/dist/Autocomplete.jsx.map +1 -1
  17. package/dist/Breadcrumbs.d.ts.map +1 -1
  18. package/dist/Breadcrumbs.js +18 -18
  19. package/dist/Breadcrumbs.js.map +1 -1
  20. package/dist/Breadcrumbs.jsx +21 -26
  21. package/dist/Breadcrumbs.jsx.map +1 -1
  22. package/dist/Calendar.d.ts.map +1 -1
  23. package/dist/Calendar.js +100 -65
  24. package/dist/Calendar.js.map +1 -1
  25. package/dist/Calendar.jsx +10 -1
  26. package/dist/Calendar.jsx.map +1 -1
  27. package/dist/Color.d.ts.map +1 -1
  28. package/dist/Color.js +15 -18
  29. package/dist/Color.js.map +1 -1
  30. package/dist/Color.jsx +15 -19
  31. package/dist/Color.jsx.map +1 -1
  32. package/dist/ComboBox.d.ts +4 -0
  33. package/dist/ComboBox.d.ts.map +1 -1
  34. package/dist/ComboBox.js +21 -13
  35. package/dist/ComboBox.js.map +1 -1
  36. package/dist/ComboBox.jsx +23 -13
  37. package/dist/ComboBox.jsx.map +1 -1
  38. package/dist/DateField.d.ts +11 -2
  39. package/dist/DateField.d.ts.map +1 -1
  40. package/dist/DateField2.js +237 -234
  41. package/dist/DateField2.js.map +1 -1
  42. package/dist/DateField2.jsx +134 -97
  43. package/dist/DateField2.jsx.map +1 -1
  44. package/dist/DatePicker.d.ts +42 -3
  45. package/dist/DatePicker.d.ts.map +1 -1
  46. package/dist/DatePicker.js +6 -990
  47. package/dist/DatePicker.jsx +6 -681
  48. package/dist/DatePicker2.js +1171 -0
  49. package/dist/DatePicker2.js.map +1 -0
  50. package/dist/DatePicker2.jsx +820 -0
  51. package/dist/DatePicker2.jsx.map +1 -0
  52. package/dist/DateRangePickerContext.d.ts +2 -0
  53. package/dist/DateRangePickerContext.d.ts.map +1 -1
  54. package/dist/Disclosure.d.ts.map +1 -1
  55. package/dist/Disclosure.js +3 -0
  56. package/dist/Disclosure.js.map +1 -1
  57. package/dist/Disclosure.jsx +1 -1
  58. package/dist/Disclosure.jsx.map +1 -1
  59. package/dist/ElementTag.d.ts +29 -0
  60. package/dist/ElementTag.d.ts.map +1 -0
  61. package/dist/ElementTag.js +220 -0
  62. package/dist/ElementTag.js.map +1 -0
  63. package/dist/ElementTag.jsx +67 -0
  64. package/dist/ElementTag.jsx.map +1 -0
  65. package/dist/GridList.d.ts.map +1 -1
  66. package/dist/GridList.js +2 -2
  67. package/dist/GridList.js.map +1 -1
  68. package/dist/GridList.jsx +2 -2
  69. package/dist/GridList.jsx.map +1 -1
  70. package/dist/Landmark.js +4 -3
  71. package/dist/Landmark.js.map +1 -1
  72. package/dist/Landmark.jsx +3 -3
  73. package/dist/Landmark.jsx.map +1 -1
  74. package/dist/Link.js +3 -2
  75. package/dist/Link.js.map +1 -1
  76. package/dist/Link.jsx +3 -3
  77. package/dist/Link.jsx.map +1 -1
  78. package/dist/ListBox.d.ts +2 -2
  79. package/dist/ListBox.d.ts.map +1 -1
  80. package/dist/ListBox.js +327 -166
  81. package/dist/ListBox.js.map +1 -1
  82. package/dist/ListBox.jsx +161 -25
  83. package/dist/ListBox.jsx.map +1 -1
  84. package/dist/Modal.d.ts.map +1 -1
  85. package/dist/Modal.js +7 -5
  86. package/dist/Modal.js.map +1 -1
  87. package/dist/Modal.jsx +5 -5
  88. package/dist/Modal.jsx.map +1 -1
  89. package/dist/Popover.d.ts.map +1 -1
  90. package/dist/Popover.js +16 -9
  91. package/dist/Popover.js.map +1 -1
  92. package/dist/Popover.jsx +25 -4
  93. package/dist/Popover.jsx.map +1 -1
  94. package/dist/RangeCalendar.d.ts.map +1 -1
  95. package/dist/RangeCalendar.js +182 -46
  96. package/dist/RangeCalendar.js.map +1 -1
  97. package/dist/RangeCalendar.jsx +72 -2
  98. package/dist/RangeCalendar.jsx.map +1 -1
  99. package/dist/SearchField.d.ts.map +1 -1
  100. package/dist/SearchField.js +26 -2
  101. package/dist/SearchField.js.map +1 -1
  102. package/dist/SearchField.jsx +26 -2
  103. package/dist/SearchField.jsx.map +1 -1
  104. package/dist/Select.d.ts +7 -0
  105. package/dist/Select.d.ts.map +1 -1
  106. package/dist/Select.js +64 -51
  107. package/dist/Select.js.map +1 -1
  108. package/dist/Select.jsx +35 -20
  109. package/dist/Select.jsx.map +1 -1
  110. package/dist/Separator.js +3 -2
  111. package/dist/Separator.js.map +1 -1
  112. package/dist/Separator.jsx +2 -2
  113. package/dist/Separator.jsx.map +1 -1
  114. package/dist/SharedElementTransition.d.ts.map +1 -1
  115. package/dist/SharedElementTransition.js +35 -33
  116. package/dist/SharedElementTransition.js.map +1 -1
  117. package/dist/SharedElementTransition.jsx +35 -33
  118. package/dist/SharedElementTransition.jsx.map +1 -1
  119. package/dist/StepList.d.ts.map +1 -1
  120. package/dist/StepList.js +2 -9
  121. package/dist/StepList.js.map +1 -1
  122. package/dist/StepList.jsx +1 -6
  123. package/dist/StepList.jsx.map +1 -1
  124. package/dist/Table.d.ts +2 -0
  125. package/dist/Table.d.ts.map +1 -1
  126. package/dist/Table.js +10 -3
  127. package/dist/Table.js.map +1 -1
  128. package/dist/Table.jsx +10 -3
  129. package/dist/Table.jsx.map +1 -1
  130. package/dist/Tabs.d.ts.map +1 -1
  131. package/dist/Tabs.js +3 -0
  132. package/dist/Tabs.js.map +1 -1
  133. package/dist/Tabs.jsx +1 -1
  134. package/dist/Tabs.jsx.map +1 -1
  135. package/dist/TagGroup.d.ts +3 -1
  136. package/dist/TagGroup.d.ts.map +1 -1
  137. package/dist/TagGroup.js +12 -8
  138. package/dist/TagGroup.js.map +1 -1
  139. package/dist/TagGroup.jsx +11 -8
  140. package/dist/TagGroup.jsx.map +1 -1
  141. package/dist/Text.d.ts.map +1 -1
  142. package/dist/Text.js +9 -8
  143. package/dist/Text.js.map +1 -1
  144. package/dist/Text.jsx +3 -3
  145. package/dist/Text.jsx.map +1 -1
  146. package/dist/TextField.js +4 -1
  147. package/dist/TextField.js.map +1 -1
  148. package/dist/TextField.jsx +4 -1
  149. package/dist/TextField.jsx.map +1 -1
  150. package/dist/TimeField.d.ts +14 -50
  151. package/dist/TimeField.d.ts.map +1 -1
  152. package/dist/TimeField.js +111 -205
  153. package/dist/TimeField.js.map +1 -1
  154. package/dist/TimeField.jsx +76 -82
  155. package/dist/TimeField.jsx.map +1 -1
  156. package/dist/Tree.d.ts.map +1 -1
  157. package/dist/Tree.js +3 -3
  158. package/dist/Tree.js.map +1 -1
  159. package/dist/Tree.jsx +3 -3
  160. package/dist/Tree.jsx.map +1 -1
  161. package/dist/VisuallyHidden.d.ts.map +1 -1
  162. package/dist/VisuallyHidden.js +3 -11
  163. package/dist/VisuallyHidden.js.map +1 -1
  164. package/dist/VisuallyHidden.jsx +3 -4
  165. package/dist/VisuallyHidden.jsx.map +1 -1
  166. package/dist/index.d.ts +3 -3
  167. package/dist/index.d.ts.map +1 -1
  168. package/dist/index.js +6 -6
  169. package/dist/index.jsx +6 -6
  170. package/dist/useDragAndDrop.d.ts +0 -8
  171. package/dist/useDragAndDrop.d.ts.map +1 -1
  172. package/dist/useDragAndDrop2.js +6 -2
  173. package/dist/useDragAndDrop2.js.map +1 -1
  174. package/dist/useDragAndDrop2.jsx +6 -2
  175. package/dist/useDragAndDrop2.jsx.map +1 -1
  176. package/dist/utils.d.ts +1 -0
  177. package/dist/utils.d.ts.map +1 -1
  178. package/dist/utils.js +2 -2
  179. package/dist/utils.js.map +1 -1
  180. package/dist/utils.jsx +2 -2
  181. package/dist/utils.jsx.map +1 -1
  182. package/package.json +3 -9
  183. package/src/ActionBar.tsx +10 -12
  184. package/src/ActionGroup.tsx +5 -3
  185. package/src/Autocomplete.tsx +14 -9
  186. package/src/Breadcrumbs.tsx +57 -42
  187. package/src/Calendar.tsx +30 -1
  188. package/src/Color.tsx +42 -27
  189. package/src/ComboBox.tsx +49 -17
  190. package/src/DateField.tsx +285 -52
  191. package/src/DatePicker.tsx +345 -57
  192. package/src/DateRangePickerContext.tsx +2 -0
  193. package/src/Disclosure.tsx +4 -0
  194. package/src/ElementTag.tsx +116 -0
  195. package/src/GridList.tsx +8 -2
  196. package/src/Landmark.tsx +4 -4
  197. package/src/Link.tsx +4 -4
  198. package/src/ListBox.tsx +323 -21
  199. package/src/Modal.tsx +21 -5
  200. package/src/Popover.tsx +28 -7
  201. package/src/RangeCalendar.tsx +120 -1
  202. package/src/SearchField.tsx +53 -1
  203. package/src/Select.tsx +114 -22
  204. package/src/Separator.tsx +3 -3
  205. package/src/SharedElementTransition.tsx +92 -36
  206. package/src/StepList.tsx +0 -7
  207. package/src/Table.tsx +19 -4
  208. package/src/Tabs.tsx +1 -0
  209. package/src/TagGroup.tsx +26 -6
  210. package/src/Text.tsx +5 -4
  211. package/src/TextField.tsx +1 -1
  212. package/src/TimeField.tsx +113 -196
  213. package/src/Tree.tsx +14 -4
  214. package/src/VisuallyHidden.tsx +8 -12
  215. package/src/index.ts +3 -8
  216. package/src/useDragAndDrop.ts +8 -10
  217. package/src/utils.tsx +3 -3
  218. package/dist/DatePicker.js.map +0 -1
  219. package/dist/DatePicker.jsx.map +0 -1
  220. package/dist/HiddenTimeInput.d.ts +0 -25
  221. package/dist/HiddenTimeInput.d.ts.map +0 -1
  222. package/dist/HiddenTimeInput.js +0 -104
  223. package/dist/HiddenTimeInput.js.map +0 -1
  224. package/dist/HiddenTimeInput.jsx +0 -69
  225. package/dist/HiddenTimeInput.jsx.map +0 -1
  226. package/src/HiddenTimeInput.tsx +0 -133
@@ -20,13 +20,19 @@ import {
20
20
  import { Portal } from "solid-js/web";
21
21
  import {
22
22
  createDatePicker,
23
+ createDateField,
23
24
  createDateRangePicker,
25
+ createEnterAnimation,
26
+ createFocusRing,
27
+ createHover,
24
28
  createPopover,
29
+ mergeProps,
25
30
  FocusScope,
26
31
  useUNSAFE_PortalContext,
27
32
  type AriaDatePickerProps,
28
33
  type AriaDateRangePickerProps,
29
34
  type DatePickerState as AriaDatePickerState,
35
+ type PlacementAxis,
30
36
  } from "@proyecto-viviana/solidaria";
31
37
  import {
32
38
  createDateFieldState,
@@ -176,6 +182,25 @@ export interface DatePickerButtonRenderProps {
176
182
  isDisabled: boolean;
177
183
  /** Whether the calendar is open. */
178
184
  isOpen: boolean;
185
+ /**
186
+ * Whether the button is currently pressed. Provided by the single
187
+ * DatePicker (fed from `createDatePicker().isButtonPressed()`); the range
188
+ * variant does not yet publish press state, so this is optional.
189
+ */
190
+ isPressed?: boolean;
191
+ /**
192
+ * Whether the button is hovered. Fed from the trigger's own `createHover`;
193
+ * mirrors RAC `Button`'s `isHovered` renderProp so the styled S2
194
+ * `baseColor("gray-100")` hover-step background compiles into the className.
195
+ */
196
+ isHovered?: boolean;
197
+ /**
198
+ * Whether the button is keyboard-focused (focus-visible). Fed from
199
+ * `createFocusRing`; mirrors RAC `Button`'s `isFocusVisible` so the styled
200
+ * S2 `focusRing()` outline (a renderProps-gated class, not a CSS
201
+ * `[data-focus-visible]` selector) compiles into the className.
202
+ */
203
+ isFocusVisible?: boolean;
179
204
  }
180
205
 
181
206
  export interface DatePickerButtonProps extends SlotProps {
@@ -187,6 +212,12 @@ export interface DatePickerButtonProps extends SlotProps {
187
212
  style?: StyleOrFunction<DatePickerButtonRenderProps>;
188
213
  /** Whether the button is disabled. */
189
214
  isDisabled?: boolean;
215
+ /**
216
+ * Ref callback for the underlying button element. Mirrors S2's CalendarButton,
217
+ * whose `buttonRef` feeds `pressScale(buttonRef)`; the styled layer forwards a
218
+ * setter here so it can size the press transform against the real element.
219
+ */
220
+ ref?: (element: HTMLButtonElement) => void;
190
221
  }
191
222
 
192
223
  export interface DateRangePickerButtonProps extends DatePickerButtonProps {}
@@ -454,6 +485,23 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
454
485
  isDateDisabled: stateProps.isDateDisabled,
455
486
  });
456
487
 
488
+ // RAC's useDatePicker re-mounts a FRESH useCalendarState every popover open
489
+ // and passes autoFocus:true, so useCalendarState seeds isFocused=true and the
490
+ // focused cell auto-focuses (useCalendarState.mjs line 80:
491
+ // `useState(props.autoFocus || false)`). The port SHARES one persistent
492
+ // calendarState across opens, so isFocused would stay whatever it last was
493
+ // (false after the first close). Mirror the fresh-mount behavior by flagging
494
+ // the shared state focused on each false->true open transition; the
495
+ // CalendarCell focus effect then pulls DOM focus to the value/today cell.
496
+ let wasOpen = false;
497
+ createEffect(() => {
498
+ const open = datePickerState.isOpen();
499
+ if (open && !wasOpen) {
500
+ calendarState.setFocused(true);
501
+ }
502
+ wasOpen = open;
503
+ });
504
+
457
505
  // Create date picker ARIA props
458
506
  const pickerAria = createDatePicker(
459
507
  () => ({
@@ -464,6 +512,18 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
464
512
  fieldState as unknown as DateFieldState<DateValue>,
465
513
  overlayState as AriaDatePickerState,
466
514
  calendarState as unknown as CalendarState<DateValue>,
515
+ () => fieldRef(),
516
+ );
517
+
518
+ // The composed picker's fieldProps (stamped role="presentation") are fed
519
+ // through createDateField — mirroring RAC's DateField consuming
520
+ // useDatePicker().fieldProps. This is what actually names the segments and
521
+ // publishes the value description through the shared hookData WeakMap; without
522
+ // it the segments would be unnamed and carry no selected-date announcement.
523
+ const fieldAria = createDateField(
524
+ () => pickerAria.fieldProps,
525
+ fieldState as unknown as DateFieldState<DateValue>,
526
+ () => fieldRef(),
467
527
  );
468
528
 
469
529
  const contextValue: DatePickerContextValue = {
@@ -518,18 +578,35 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
518
578
  <DateFieldContext.Provider
519
579
  value={{
520
580
  state: fieldState as unknown as DateFieldState<DateValue>,
581
+ // Read through getters so the DateInput/DateSegment consumers see the
582
+ // LIVE memo values (createDescription appends the value-description id
583
+ // via a deferred effect; froze snapshots would miss it). inputProps are
584
+ // the createDateField group props (role="presentation" here), which name
585
+ // the segments — NOT the raw pickerAria.fieldProps.
521
586
  aria: {
522
- labelProps: pickerAria.labelProps,
523
- inputProps: pickerAria.fieldProps,
524
- descriptionProps: pickerAria.descriptionProps,
525
- errorMessageProps: pickerAria.errorMessageProps,
587
+ get labelProps() {
588
+ return pickerAria.labelProps;
589
+ },
590
+ get inputProps() {
591
+ return fieldAria.fieldProps;
592
+ },
593
+ get descriptionProps() {
594
+ return pickerAria.descriptionProps;
595
+ },
596
+ get errorMessageProps() {
597
+ return pickerAria.errorMessageProps;
598
+ },
526
599
  },
527
600
  }}
528
601
  >
529
602
  <CalendarContext.Provider value={calendarState as unknown as CalendarState<DateValue>}>
603
+ {/* BARE ROLELESS root — mirrors RAC `DatePicker`'s outer `<div>`. The
604
+ * presentation FieldGroup shell (rendered as a child via
605
+ * `DatePickerFieldGroup`) is what carries `pickerAria.groupProps`
606
+ * (role="presentation" + label/describedby + arrow-nav/press). A
607
+ * described node here would be a spurious AX entry the S2 oracle lacks. */}
530
608
  <div
531
609
  ref={setFieldRef}
532
- {...pickerAria.groupProps}
533
610
  class={renderProps.class()}
534
611
  style={renderProps.style()}
535
612
  data-disabled={dataAttr(fieldState.isDisabled())}
@@ -649,6 +726,10 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
649
726
  };
650
727
 
651
728
  let triggerRef: HTMLElement | null = null;
729
+ // The roleless root element — scopes the shared segment focus manager and the
730
+ // outer arrow-navigation across BOTH fields, mirroring the single DatePicker's
731
+ // `fieldRef`.
732
+ const [fieldRef, setFieldRef] = createSignal<HTMLDivElement | null>(null);
652
733
  const overlayState = {
653
734
  get isOpen() {
654
735
  return isOpen();
@@ -683,6 +764,21 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
683
764
  },
684
765
  });
685
766
 
767
+ // Mirror the single DatePicker: RAC re-mounts a FRESH useRangeCalendarState
768
+ // per popover open with autoFocus:true, so isFocused seeds true and the focused
769
+ // cell auto-focuses AND publishes its range-selection prompt describedby
770
+ // ("Click to start selecting date range"). The port shares one persistent
771
+ // range state across opens, so flag it focused on each false->true open
772
+ // transition to reproduce that behavior.
773
+ let wasRangeOpen = false;
774
+ createEffect(() => {
775
+ const open = overlayState.isOpen;
776
+ if (open && !wasRangeOpen) {
777
+ calendarState.setFocused(true);
778
+ }
779
+ wasRangeOpen = open;
780
+ });
781
+
686
782
  const isInvalid = createMemo(
687
783
  () =>
688
784
  Boolean((rest as { isInvalid?: boolean }).isInvalid) ||
@@ -767,14 +863,36 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
767
863
  }),
768
864
  calendarState as unknown as RangeCalendarState<DateValue>,
769
865
  overlayState as AriaDatePickerState,
866
+ () => fieldRef(),
867
+ );
868
+
869
+ // Each range field's presentation field props (stamped role="presentation" +
870
+ // the SHARED focus manager) are fed through createDateField — mirroring RAC's
871
+ // two `<DateInput>`s consuming useDateRangePicker().start/endFieldProps. This
872
+ // names the segments ("month, Start Date" / "month, End Date") and publishes
873
+ // them through the shared hookData WeakMap; the shared focus manager makes arrow
874
+ // keys and auto-advance walk across the start/end boundary.
875
+ const startFieldAria = createDateField(
876
+ () => pickerAria.startFieldProps,
877
+ startFieldState as unknown as DateFieldState<DateValue>,
878
+ () => fieldRef(),
879
+ );
880
+ const endFieldAria = createDateField(
881
+ () => pickerAria.endFieldProps,
882
+ endFieldState as unknown as DateFieldState<DateValue>,
883
+ () => fieldRef(),
770
884
  );
771
885
 
772
886
  const startFieldContext: DateRangePickerFieldContextValue = {
773
887
  state: startFieldState as unknown as DateFieldState<DateValue>,
774
888
  aria: {
775
889
  labelProps: {},
890
+ // The DateInput group carries createDateField's fieldProps (role="presentation"
891
+ // + arrow-nav bubbling + unicode-bidi isolate), mirroring the standalone
892
+ // DateField's `inputProps ← fieldProps` mapping. NO hiddenInputProps — the
893
+ // range owner renders its HiddenDateInput siblings below.
776
894
  get inputProps() {
777
- return pickerAria.startInputProps;
895
+ return startFieldAria.fieldProps;
778
896
  },
779
897
  get descriptionProps() {
780
898
  return pickerAria.descriptionProps;
@@ -790,7 +908,7 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
790
908
  aria: {
791
909
  labelProps: {},
792
910
  get inputProps() {
793
- return pickerAria.endInputProps;
911
+ return endFieldAria.fieldProps;
794
912
  },
795
913
  get descriptionProps() {
796
914
  return pickerAria.descriptionProps;
@@ -841,8 +959,15 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
841
959
  <RangeCalendarContext.Provider
842
960
  value={calendarState as unknown as RangeCalendarState<DateValue>}
843
961
  >
962
+ {/* BARE ROLELESS root — mirrors RAC `DateRangePicker`'s outer `<div>`.
963
+ * The presentation FieldGroup shell (rendered as a child via the styled
964
+ * DateRangeDisplay) is what carries `pickerAria.groupProps` (role=
965
+ * "presentation" + label/describedby + outer arrow-nav/press). A described
966
+ * or role="group" node here would be a spurious AX entry the S2 oracle
967
+ * lacks. The ref scopes the shared segment focus manager across both
968
+ * fields (see createDateRangePicker). */}
844
969
  <div
845
- {...pickerAria.groupProps}
970
+ ref={setFieldRef}
846
971
  class={renderProps.class()}
847
972
  style={renderProps.style()}
848
973
  data-disabled={dataAttr(calendarState.isDisabled())}
@@ -907,9 +1032,31 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
907
1032
  export function DatePickerButton(props: DatePickerButtonProps): JSX.Element {
908
1033
  const context = useDatePickerContext();
909
1034
 
1035
+ // Mirror RAC's `buttonProps.isDisabled = props.isDisabled || props.isReadOnly`
1036
+ // (a read-only picker can't open its calendar) — `createDatePicker` computes
1037
+ // this as `isButtonDisabled()`. Reading `fieldState.isDisabled()` alone would
1038
+ // miss the read-only case (read-only ≠ disabled on the field), so the trigger
1039
+ // would paint enabled while the S2 oracle dims it.
1040
+ const isDisabled = () => context.pickerAria.isButtonDisabled() || (props.isDisabled ?? false);
1041
+
1042
+ // Mirror RAC: the trigger is a `<Button>` whose own `useFocusRing`/`useHover`/
1043
+ // `usePress` drive its interaction paint state. The press signal is owned by
1044
+ // `createDatePicker` (`isButtonPressed`); focus-visible and hover are wired
1045
+ // here so the S2 `focusRing()`/`baseColor()` selectors (`data-focus-visible`,
1046
+ // `data-hovered`, `data-pressed`) resolve exactly as they do upstream.
1047
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
1048
+ const { isHovered, hoverProps } = createHover({
1049
+ get isDisabled() {
1050
+ return isDisabled();
1051
+ },
1052
+ });
1053
+
910
1054
  const renderValues = createMemo<DatePickerButtonRenderProps>(() => ({
911
- isDisabled: context.fieldState.isDisabled() || (props.isDisabled ?? false),
1055
+ isDisabled: isDisabled(),
912
1056
  isOpen: context.overlayState.isOpen,
1057
+ isPressed: context.pickerAria.isButtonPressed(),
1058
+ isHovered: isHovered(),
1059
+ isFocusVisible: isFocusVisible(),
913
1060
  }));
914
1061
 
915
1062
  const renderProps = useRenderProps(
@@ -932,17 +1079,30 @@ export function DatePickerButton(props: DatePickerButtonProps): JSX.Element {
932
1079
  return props.children ?? "📅";
933
1080
  };
934
1081
 
1082
+ const buttonProps = createMemo(() =>
1083
+ mergeProps(
1084
+ context.pickerAria.buttonProps as Record<string, unknown>,
1085
+ focusProps as Record<string, unknown>,
1086
+ hoverProps as Record<string, unknown>,
1087
+ ),
1088
+ );
1089
+
935
1090
  return (
936
1091
  <button
937
1092
  ref={(el) => {
938
1093
  context.setTriggerRef(el);
1094
+ props.ref?.(el);
939
1095
  }}
940
- {...context.pickerAria.buttonProps}
1096
+ {...buttonProps()}
941
1097
  class={renderProps.class()}
942
1098
  style={renderProps.style()}
943
- disabled={context.fieldState.isDisabled() || props.isDisabled}
944
- data-disabled={dataAttr(context.fieldState.isDisabled() || props.isDisabled)}
1099
+ disabled={isDisabled()}
1100
+ data-disabled={dataAttr(isDisabled())}
945
1101
  data-open={dataAttr(context.overlayState.isOpen)}
1102
+ attr:data-hovered={isHovered() ? "true" : undefined}
1103
+ attr:data-focused={isFocused() ? "true" : undefined}
1104
+ attr:data-focus-visible={isFocusVisible() ? "true" : undefined}
1105
+ attr:data-pressed={context.pickerAria.isButtonPressed() ? "true" : undefined}
946
1106
  >
947
1107
  {getChildren()}
948
1108
  </button>
@@ -952,9 +1112,30 @@ export function DatePickerButton(props: DatePickerButtonProps): JSX.Element {
952
1112
  export function DateRangePickerButton(props: DateRangePickerButtonProps): JSX.Element {
953
1113
  const context = useDateRangePickerContext();
954
1114
 
1115
+ // Mirror RAC's `buttonProps.isDisabled = props.isDisabled || props.isReadOnly`
1116
+ // (a read-only picker can't open its calendar) — `createDateRangePicker` computes
1117
+ // this as `isButtonDisabled()`. Reading `calendarState.isDisabled()` alone would
1118
+ // miss the read-only case, so the trigger would paint enabled while S2 dims it.
1119
+ const isDisabled = () => context.pickerAria.isButtonDisabled() || (props.isDisabled ?? false);
1120
+
1121
+ // Mirror RAC: the trigger is a `<Button>` whose own `useFocusRing`/`useHover`/
1122
+ // `usePress` drive its interaction paint state. The press signal is owned by
1123
+ // `createDateRangePicker` (`isButtonPressed`); focus-visible and hover are wired
1124
+ // here so the S2 `focusRing()`/`baseColor()` selectors (`data-focus-visible`,
1125
+ // `data-hovered`, `data-pressed`) resolve exactly as they do upstream.
1126
+ const { isFocused, isFocusVisible, focusProps } = createFocusRing();
1127
+ const { isHovered, hoverProps } = createHover({
1128
+ get isDisabled() {
1129
+ return isDisabled();
1130
+ },
1131
+ });
1132
+
955
1133
  const renderValues = createMemo<DatePickerButtonRenderProps>(() => ({
956
- isDisabled: context.calendarState.isDisabled() || (props.isDisabled ?? false),
1134
+ isDisabled: isDisabled(),
957
1135
  isOpen: context.overlayState.isOpen,
1136
+ isPressed: context.pickerAria.isButtonPressed(),
1137
+ isHovered: isHovered(),
1138
+ isFocusVisible: isFocusVisible(),
958
1139
  }));
959
1140
 
960
1141
  const renderProps = useRenderProps(
@@ -976,26 +1157,56 @@ export function DateRangePickerButton(props: DateRangePickerButtonProps): JSX.El
976
1157
  return props.children ?? "📅";
977
1158
  };
978
1159
 
1160
+ const buttonProps = createMemo(() =>
1161
+ mergeProps(
1162
+ context.pickerAria.buttonProps as Record<string, unknown>,
1163
+ focusProps as Record<string, unknown>,
1164
+ hoverProps as Record<string, unknown>,
1165
+ ),
1166
+ );
1167
+
979
1168
  return (
980
1169
  <button
981
- ref={(el) => context.setTriggerRef(el)}
982
- {...context.pickerAria.buttonProps}
1170
+ ref={(el) => {
1171
+ context.setTriggerRef(el);
1172
+ props.ref?.(el);
1173
+ }}
1174
+ {...buttonProps()}
983
1175
  class={renderProps.class()}
984
1176
  style={renderProps.style()}
985
- disabled={context.calendarState.isDisabled() || props.isDisabled}
986
- data-disabled={dataAttr(context.calendarState.isDisabled() || props.isDisabled)}
1177
+ disabled={isDisabled()}
1178
+ data-disabled={dataAttr(isDisabled())}
987
1179
  data-open={dataAttr(context.overlayState.isOpen)}
1180
+ attr:data-hovered={isHovered() ? "true" : undefined}
1181
+ attr:data-focused={isFocused() ? "true" : undefined}
1182
+ attr:data-focus-visible={isFocusVisible() ? "true" : undefined}
1183
+ attr:data-pressed={context.pickerAria.isButtonPressed() ? "true" : undefined}
988
1184
  >
989
1185
  {getChildren()}
990
1186
  </button>
991
1187
  );
992
1188
  }
993
1189
 
1190
+ /**
1191
+ * Render props for the popover surface, mirroring RAC `Popover`'s
1192
+ * enter/exit/placement renderProps. The styled layer keys the S2
1193
+ * opacity/translate enter transition on these (a renderProps-gated class, not a
1194
+ * CSS `[data-entering]` selector).
1195
+ */
1196
+ export interface DatePickerContentRenderProps {
1197
+ /** Whether the popover is playing its enter transition (`data-entering`). */
1198
+ isEntering: boolean;
1199
+ /** Whether the popover is playing its exit transition (`data-exiting`). */
1200
+ isExiting: boolean;
1201
+ /** The resolved placement axis, for the direction-dependent translate. */
1202
+ placement: PlacementAxis | null;
1203
+ }
1204
+
994
1205
  export interface DatePickerContentProps extends SlotProps {
995
1206
  /** The children of the component. */
996
1207
  children?: JSX.Element;
997
- /** The CSS className for the element. */
998
- class?: string;
1208
+ /** The CSS className for the element, or a function of the render props. */
1209
+ class?: string | ((renderProps: DatePickerContentRenderProps) => string);
999
1210
  /** The inline style for the element. */
1000
1211
  style?: JSX.CSSProperties;
1001
1212
  }
@@ -1023,10 +1234,12 @@ export interface DatePickerDescriptionProps {
1023
1234
 
1024
1235
  export function DatePickerDescription(props: DatePickerDescriptionProps): JSX.Element {
1025
1236
  const context = useDatePickerContext();
1237
+ // Rendered as <span> (→ AX role "text"), mirroring S2's <Text slot="description">
1238
+ // and the standalone DateFieldDescription — NOT a <p> (paragraph).
1026
1239
  return (
1027
- <p {...context.pickerAria.descriptionProps} class={props.class}>
1240
+ <span {...context.pickerAria.descriptionProps} class={props.class}>
1028
1241
  {props.children}
1029
- </p>
1242
+ </span>
1030
1243
  );
1031
1244
  }
1032
1245
 
@@ -1037,10 +1250,12 @@ export interface DatePickerErrorMessageProps {
1037
1250
 
1038
1251
  export function DatePickerErrorMessage(props: DatePickerErrorMessageProps): JSX.Element {
1039
1252
  const context = useDatePickerContext();
1253
+ // Rendered as <span> (→ AX role "text"), mirroring S2's <FieldError> and the
1254
+ // standalone DateFieldErrorMessage — NOT a <p> and NOT role="alert".
1040
1255
  return (
1041
- <p {...context.pickerAria.errorMessageProps} class={props.class}>
1256
+ <span {...context.pickerAria.errorMessageProps} class={props.class}>
1042
1257
  {props.children}
1043
- </p>
1258
+ </span>
1044
1259
  );
1045
1260
  }
1046
1261
 
@@ -1065,10 +1280,12 @@ export interface DateRangePickerDescriptionProps {
1065
1280
 
1066
1281
  export function DateRangePickerDescription(props: DateRangePickerDescriptionProps): JSX.Element {
1067
1282
  const context = useDateRangePickerContext();
1283
+ // Rendered as <span> (→ AX role "text"), mirroring S2's <Text slot="description">
1284
+ // and the single DatePickerDescription — NOT a <p> (paragraph).
1068
1285
  return (
1069
- <p {...context.pickerAria.descriptionProps} class={props.class}>
1286
+ <span {...context.pickerAria.descriptionProps} class={props.class}>
1070
1287
  {props.children}
1071
- </p>
1288
+ </span>
1072
1289
  );
1073
1290
  }
1074
1291
 
@@ -1079,10 +1296,12 @@ export interface DateRangePickerErrorMessageProps {
1079
1296
 
1080
1297
  export function DateRangePickerErrorMessage(props: DateRangePickerErrorMessageProps): JSX.Element {
1081
1298
  const context = useDateRangePickerContext();
1299
+ // Rendered as <span> (→ AX role "text"), mirroring S2's <FieldError> and the
1300
+ // single DatePickerErrorMessage — NOT a <p> and NOT role="alert".
1082
1301
  return (
1083
- <p {...context.pickerAria.errorMessageProps} class={props.class}>
1302
+ <span {...context.pickerAria.errorMessageProps} class={props.class}>
1084
1303
  {props.children}
1085
- </p>
1304
+ </span>
1086
1305
  );
1087
1306
  }
1088
1307
 
@@ -1106,13 +1325,16 @@ function createEscapeDismissFallback(isOpen: () => boolean, close: () => void):
1106
1325
  export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
1107
1326
  const context = useDatePickerContext();
1108
1327
  const portalContext = useUNSAFE_PortalContext();
1109
- let contentRef: HTMLDivElement | undefined;
1328
+ // A signal (not a plain `let`) so the enter-animation effect re-runs when the
1329
+ // section mounts on open — `createEnterAnimation` reads this to know the
1330
+ // element exists before removing the initial `data-entering` styles.
1331
+ const [contentRef, setContentRef] = createSignal<HTMLElement>();
1110
1332
  const portalContainer = () => portalContext.getContainer?.() ?? undefined;
1111
1333
 
1112
1334
  const popoverAria = createPopover(
1113
1335
  {
1114
1336
  triggerRef: () => context.triggerRef()?.parentElement ?? context.triggerRef(),
1115
- popoverRef: () => contentRef ?? null,
1337
+ popoverRef: () => contentRef() ?? null,
1116
1338
  placement: "bottom start",
1117
1339
  offset: 8,
1118
1340
  isNonModal: false,
@@ -1128,6 +1350,35 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
1128
1350
 
1129
1351
  createEscapeDismissFallback(() => context.overlayState.isOpen, context.overlayState.close);
1130
1352
 
1353
+ // Mirror S2's `<Popover>` enter transition (RAC `useEnterAnimation`): the
1354
+ // surface mounts carrying `data-entering` (opacity 0 + a small placement-ward
1355
+ // translate), which an effect removes on the next frame so the CSS transition
1356
+ // plays. The exit half is out of scope for this unit — the popover unmounts on
1357
+ // close (no `isOpen || isExiting` gate), so `isExiting` stays false; only the
1358
+ // enter motion is certified (D2).
1359
+ const isEntering = createEnterAnimation(contentRef, () => context.overlayState.isOpen);
1360
+
1361
+ const renderProps = (): DatePickerContentRenderProps => ({
1362
+ isEntering: isEntering(),
1363
+ isExiting: false,
1364
+ // Seed the base placement so the ENTER frame already carries the
1365
+ // placement-ward `translate` (S2's popover motion is placement-gated:
1366
+ // `translateY: { placement: { bottom: { isEntering: -4 } } }`). RAC resolves
1367
+ // placement in a `useLayoutEffect` BEFORE the first paint, so no painted
1368
+ // frame is ever null-placement; the port's positioning `createEffect` lands
1369
+ // one frame later, so without this seed the entering frame drops `translateY`
1370
+ // and only `opacity` transitions (D2 caught the missing translate). The
1371
+ // desired placement is `bottom start` (see `createPopover` above), so the
1372
+ // base axis is always `bottom` until the measure refines it.
1373
+ placement: popoverAria.placement() ?? "bottom",
1374
+ });
1375
+
1376
+ const resolvedClass = (): string => {
1377
+ const c = props.class;
1378
+ if (typeof c === "function") return c(renderProps());
1379
+ return c ?? "solidaria-DatePickerContent";
1380
+ };
1381
+
1131
1382
  const cleanPopoverProps = () => {
1132
1383
  const {
1133
1384
  style: _style,
@@ -1147,28 +1398,39 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
1147
1398
  };
1148
1399
  };
1149
1400
 
1150
- // Return focus to trigger when overlay closes
1151
- createEffect(() => {
1152
- const open = context.overlayState.isOpen;
1153
- if (!open) {
1154
- requestAnimationFrame(() => context.triggerRef()?.focus());
1155
- }
1156
- });
1401
+ // Close-focus restoration is delegated to the overlay's `FocusScope`
1402
+ // (`restoreFocus`) — exactly as RAC's DatePicker does, and as certified Select
1403
+ // and Menu do. The prior ad-hoc effect (`if (!open) triggerRef().focus()`)
1404
+ // fired once on mount with `open === false`, stealing focus to the trigger and
1405
+ // painting a phantom focus ring at rest.
1157
1406
 
1158
1407
  return (
1159
1408
  <Show when={context.overlayState.isOpen}>
1160
1409
  <Portal mount={portalContainer()}>
1161
1410
  <FocusScope contain restoreFocus>
1411
+ {/* Un-folded to mirror S2's `<Popover>` DOM. The OUTER role-null `<div>`
1412
+ * is the positioned surface that carries the enter motion + chrome —
1413
+ * this is the element RAC/S2 animate (`AriaPopover`'s div: role null, no
1414
+ * tabindex, accessible name computed from the calendar's textContent).
1415
+ * A nested `role="dialog"` element (RAC's `Dialog`, which S2's
1416
+ * CalendarPopover wraps the Calendar in) carries the dialog semantics.
1417
+ * Folding both onto one `<section role="dialog">` made D2 animate the
1418
+ * dialog node instead of the role-null div; the role-null wrapper is
1419
+ * pruned from the AX tree so D5/D6 are unaffected. */}
1162
1420
  <div
1163
- ref={contentRef}
1421
+ ref={setContentRef}
1164
1422
  {...cleanPopoverProps()}
1165
- {...context.pickerAria.dialogProps}
1166
- tabIndex={-1}
1167
- class={props.class ?? "solidaria-DatePickerContent"}
1423
+ class={resolvedClass()}
1168
1424
  style={mergedStyle()}
1169
- data-placement={popoverAria.placement() ?? undefined}
1425
+ data-placement={popoverAria.placement() ?? "bottom"}
1426
+ data-entering={dataAttr(isEntering())}
1170
1427
  >
1171
- {props.children}
1428
+ {/* RAC's `Dialog` renders a `<section role="dialog">` by default — the
1429
+ * D5 roving trail records this element's tag, so it must be `section`
1430
+ * (a `<div>` here regressed the focus-trail tag match). */}
1431
+ <section {...context.pickerAria.dialogProps} tabIndex={-1}>
1432
+ {props.children}
1433
+ </section>
1172
1434
  </div>
1173
1435
  </FocusScope>
1174
1436
  </Portal>
@@ -1179,13 +1441,16 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
1179
1441
  export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.Element {
1180
1442
  const context = useDateRangePickerContext();
1181
1443
  const portalContext = useUNSAFE_PortalContext();
1182
- let contentRef: HTMLDivElement | undefined;
1444
+ // A signal (not a plain `let`) so the enter-animation effect re-runs when the
1445
+ // surface mounts on open — `createEnterAnimation` reads this to know the
1446
+ // element exists before removing the initial `data-entering` styles.
1447
+ const [contentRef, setContentRef] = createSignal<HTMLElement>();
1183
1448
  const portalContainer = () => portalContext.getContainer?.() ?? undefined;
1184
1449
 
1185
1450
  const popoverAria = createPopover(
1186
1451
  {
1187
1452
  triggerRef: () => context.triggerRef()?.parentElement ?? context.triggerRef(),
1188
- popoverRef: () => contentRef ?? null,
1453
+ popoverRef: () => contentRef() ?? null,
1189
1454
  placement: "bottom start",
1190
1455
  offset: 8,
1191
1456
  isNonModal: false,
@@ -1201,6 +1466,26 @@ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.
1201
1466
 
1202
1467
  createEscapeDismissFallback(() => context.overlayState.isOpen, context.overlayState.close);
1203
1468
 
1469
+ // Mirror S2's `<Popover>` enter transition (RAC `useEnterAnimation`): the
1470
+ // surface mounts carrying `data-entering` (opacity 0 + a small placement-ward
1471
+ // translate), which an effect removes on the next frame so the CSS transition
1472
+ // plays. Only the enter half is certified (D2) — the popover unmounts on close.
1473
+ const isEntering = createEnterAnimation(contentRef, () => context.overlayState.isOpen);
1474
+
1475
+ const renderProps = (): DatePickerContentRenderProps => ({
1476
+ isEntering: isEntering(),
1477
+ isExiting: false,
1478
+ // Seed the base placement so the ENTER frame already carries the
1479
+ // placement-ward `translate` (see the single DatePickerContent note).
1480
+ placement: popoverAria.placement() ?? "bottom",
1481
+ });
1482
+
1483
+ const resolvedClass = (): string => {
1484
+ const c = props.class;
1485
+ if (typeof c === "function") return c(renderProps());
1486
+ return c ?? "solidaria-DateRangePickerContent";
1487
+ };
1488
+
1204
1489
  const cleanPopoverProps = () => {
1205
1490
  const {
1206
1491
  style: _style,
@@ -1220,28 +1505,31 @@ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.
1220
1505
  };
1221
1506
  };
1222
1507
 
1223
- // Return focus to trigger when overlay closes
1224
- createEffect(() => {
1225
- const open = context.overlayState.isOpen;
1226
- if (!open) {
1227
- requestAnimationFrame(() => context.triggerRef()?.focus());
1228
- }
1229
- });
1508
+ // Close-focus restoration is delegated to the overlay's `FocusScope`
1509
+ // (`restoreFocus`) — see the single-value `DatePickerContent` note. The prior
1510
+ // ad-hoc effect fired on mount and painted a phantom focus ring at rest.
1230
1511
 
1231
1512
  return (
1232
1513
  <Show when={context.overlayState.isOpen}>
1233
1514
  <Portal mount={portalContainer()}>
1234
1515
  <FocusScope contain restoreFocus>
1516
+ {/* Un-folded to mirror S2's `<Popover>` DOM (see DatePickerContent).
1517
+ * OUTER role-null `<div>` = positioned surface carrying the enter
1518
+ * motion + chrome (the element RAC/S2 animate; pruned from the AX
1519
+ * tree). Nested `<section role="dialog">` carries the dialog
1520
+ * semantics — the D5 roving trail records this tag, so it must be
1521
+ * `section`, not a folded `div`. */}
1235
1522
  <div
1236
- ref={contentRef}
1523
+ ref={setContentRef}
1237
1524
  {...cleanPopoverProps()}
1238
- {...context.pickerAria.dialogProps}
1239
- tabIndex={-1}
1240
- class={props.class ?? "solidaria-DateRangePickerContent"}
1525
+ class={resolvedClass()}
1241
1526
  style={mergedStyle()}
1242
- data-placement={popoverAria.placement() ?? undefined}
1527
+ data-placement={popoverAria.placement() ?? "bottom"}
1528
+ data-entering={dataAttr(isEntering())}
1243
1529
  >
1244
- {props.children}
1530
+ <section {...context.pickerAria.dialogProps} tabIndex={-1}>
1531
+ {props.children}
1532
+ </section>
1245
1533
  </div>
1246
1534
  </FocusScope>
1247
1535
  </Portal>
@@ -11,6 +11,8 @@ export interface DateRangePickerFieldContextValue {
11
11
  aria: {
12
12
  labelProps: Record<string, unknown>;
13
13
  inputProps: Record<string, unknown>;
14
+ /** Optional hidden validation <input> props (unused by the range fields). */
15
+ hiddenInputProps?: Record<string, unknown>;
14
16
  descriptionProps: Record<string, unknown>;
15
17
  errorMessageProps: Record<string, unknown>;
16
18
  };
@@ -406,6 +406,10 @@ export function DisclosureTrigger(props: DisclosureTriggerProps): JSX.Element {
406
406
  {...mergeProps(domProps() as Record<string, unknown>, getButtonProps(), cleanFocusProps())}
407
407
  ref={(el) => assignRef(local.ref, el)}
408
408
  type="button"
409
+ // Mirror react-aria's `useFocusable` (useFocusable.tsx:114-118): always emit
410
+ // an explicit tabIndex so the native trigger is a real, discoverable tab stop
411
+ // (Safari won't focus a native button otherwise). Disabled → attribute absent.
412
+ tabindex={isDisabled() ? undefined : 0}
409
413
  class={local.class}
410
414
  style={local.style}
411
415
  data-expanded={dataAttr(isExpanded())}