@proyecto-viviana/solidaria-components 0.2.9 → 0.3.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 (222) hide show
  1. package/README.md +39 -272
  2. package/dist/ActionBar.d.ts +21 -13
  3. package/dist/ActionBar.d.ts.map +1 -1
  4. package/dist/ActionGroup.d.ts +8 -8
  5. package/dist/ActionGroup.d.ts.map +1 -1
  6. package/dist/Alert.d.ts +5 -5
  7. package/dist/Alert.d.ts.map +1 -1
  8. package/dist/Autocomplete.d.ts +5 -5
  9. package/dist/Autocomplete.d.ts.map +1 -1
  10. package/dist/Breadcrumbs.d.ts +18 -7
  11. package/dist/Breadcrumbs.d.ts.map +1 -1
  12. package/dist/Button.d.ts +24 -5
  13. package/dist/Button.d.ts.map +1 -1
  14. package/dist/Calendar.d.ts +38 -7
  15. package/dist/Calendar.d.ts.map +1 -1
  16. package/dist/Checkbox.d.ts +32 -7
  17. package/dist/Checkbox.d.ts.map +1 -1
  18. package/dist/Collection.d.ts +19 -14
  19. package/dist/Collection.d.ts.map +1 -1
  20. package/dist/Color.d.ts +103 -14
  21. package/dist/Color.d.ts.map +1 -1
  22. package/dist/ColorEditor.d.ts +6 -6
  23. package/dist/ColorEditor.d.ts.map +1 -1
  24. package/dist/ComboBox.d.ts +85 -19
  25. package/dist/ComboBox.d.ts.map +1 -1
  26. package/dist/ContextualHelpTrigger.d.ts +2 -2
  27. package/dist/ContextualHelpTrigger.d.ts.map +1 -1
  28. package/dist/DateField.d.ts +8 -6
  29. package/dist/DateField.d.ts.map +1 -1
  30. package/dist/DatePicker.d.ts +53 -22
  31. package/dist/DatePicker.d.ts.map +1 -1
  32. package/dist/DateRangePickerContext.d.ts +30 -0
  33. package/dist/DateRangePickerContext.d.ts.map +1 -0
  34. package/dist/Dialog.d.ts +5 -5
  35. package/dist/Dialog.d.ts.map +1 -1
  36. package/dist/Disclosure.d.ts +23 -5
  37. package/dist/Disclosure.d.ts.map +1 -1
  38. package/dist/DragAndDrop.d.ts +6 -6
  39. package/dist/DragAndDrop.d.ts.map +1 -1
  40. package/dist/DragPreview.d.ts +2 -2
  41. package/dist/DragPreview.d.ts.map +1 -1
  42. package/dist/DropZone.d.ts +4 -4
  43. package/dist/DropZone.d.ts.map +1 -1
  44. package/dist/FieldError.d.ts +9 -5
  45. package/dist/FieldError.d.ts.map +1 -1
  46. package/dist/FileTrigger.d.ts +3 -3
  47. package/dist/FileTrigger.d.ts.map +1 -1
  48. package/dist/Focusable.d.ts +2 -2
  49. package/dist/Focusable.d.ts.map +1 -1
  50. package/dist/Form.d.ts +18 -4
  51. package/dist/Form.d.ts.map +1 -1
  52. package/dist/GridList.d.ts +32 -12
  53. package/dist/GridList.d.ts.map +1 -1
  54. package/dist/HiddenDateInput.d.ts +26 -0
  55. package/dist/HiddenDateInput.d.ts.map +1 -0
  56. package/dist/HiddenTimeInput.d.ts +25 -0
  57. package/dist/HiddenTimeInput.d.ts.map +1 -0
  58. package/dist/Icon.d.ts +5 -5
  59. package/dist/Icon.d.ts.map +1 -1
  60. package/dist/Keyboard.d.ts +1 -1
  61. package/dist/Landmark.d.ts +3 -3
  62. package/dist/Landmark.d.ts.map +1 -1
  63. package/dist/Link.d.ts +10 -4
  64. package/dist/Link.d.ts.map +1 -1
  65. package/dist/ListBox.d.ts +32 -12
  66. package/dist/ListBox.d.ts.map +1 -1
  67. package/dist/ListDropTargetDelegate.d.ts +6 -6
  68. package/dist/ListDropTargetDelegate.d.ts.map +1 -1
  69. package/dist/Menu.d.ts +65 -14
  70. package/dist/Menu.d.ts.map +1 -1
  71. package/dist/Meter.d.ts +3 -3
  72. package/dist/Meter.d.ts.map +1 -1
  73. package/dist/Modal.d.ts +5 -5
  74. package/dist/Modal.d.ts.map +1 -1
  75. package/dist/NumberField.d.ts +8 -12
  76. package/dist/NumberField.d.ts.map +1 -1
  77. package/dist/Popover.d.ts +28 -5
  78. package/dist/Popover.d.ts.map +1 -1
  79. package/dist/Pressable.d.ts +2 -2
  80. package/dist/Pressable.d.ts.map +1 -1
  81. package/dist/ProgressBar.d.ts +5 -3
  82. package/dist/ProgressBar.d.ts.map +1 -1
  83. package/dist/RadioGroup.d.ts +43 -9
  84. package/dist/RadioGroup.d.ts.map +1 -1
  85. package/dist/RangeCalendar.d.ts +34 -7
  86. package/dist/RangeCalendar.d.ts.map +1 -1
  87. package/dist/RouterProvider.d.ts +2 -2
  88. package/dist/RouterProvider.d.ts.map +1 -1
  89. package/dist/SearchField.d.ts +23 -20
  90. package/dist/SearchField.d.ts.map +1 -1
  91. package/dist/Select.d.ts +41 -11
  92. package/dist/Select.d.ts.map +1 -1
  93. package/dist/SelectionIndicator.d.ts +3 -3
  94. package/dist/SelectionIndicator.d.ts.map +1 -1
  95. package/dist/Separator.d.ts +9 -3
  96. package/dist/Separator.d.ts.map +1 -1
  97. package/dist/SharedElementTransition.d.ts +6 -4
  98. package/dist/SharedElementTransition.d.ts.map +1 -1
  99. package/dist/Slider.d.ts +12 -8
  100. package/dist/Slider.d.ts.map +1 -1
  101. package/dist/StepList.d.ts +90 -0
  102. package/dist/StepList.d.ts.map +1 -0
  103. package/dist/Switch.d.ts +11 -5
  104. package/dist/Switch.d.ts.map +1 -1
  105. package/dist/Table.d.ts +187 -23
  106. package/dist/Table.d.ts.map +1 -1
  107. package/dist/Tabs.d.ts +45 -9
  108. package/dist/Tabs.d.ts.map +1 -1
  109. package/dist/TagGroup.d.ts +12 -10
  110. package/dist/TagGroup.d.ts.map +1 -1
  111. package/dist/Text.d.ts +2 -2
  112. package/dist/TextField.d.ts +15 -11
  113. package/dist/TextField.d.ts.map +1 -1
  114. package/dist/TimeField.d.ts +6 -6
  115. package/dist/TimeField.d.ts.map +1 -1
  116. package/dist/Toast.d.ts +29 -14
  117. package/dist/Toast.d.ts.map +1 -1
  118. package/dist/ToggleButton.d.ts +11 -5
  119. package/dist/ToggleButton.d.ts.map +1 -1
  120. package/dist/ToggleButtonGroup.d.ts +7 -7
  121. package/dist/ToggleButtonGroup.d.ts.map +1 -1
  122. package/dist/Toolbar.d.ts +7 -3
  123. package/dist/Toolbar.d.ts.map +1 -1
  124. package/dist/Tooltip.d.ts +50 -8
  125. package/dist/Tooltip.d.ts.map +1 -1
  126. package/dist/Tree.d.ts +66 -17
  127. package/dist/Tree.d.ts.map +1 -1
  128. package/dist/Virtualizer.d.ts +12 -12
  129. package/dist/Virtualizer.d.ts.map +1 -1
  130. package/dist/VirtualizerLayouts.d.ts +2 -2
  131. package/dist/VirtualizerLayouts.d.ts.map +1 -1
  132. package/dist/VisuallyHidden.d.ts +1 -1
  133. package/dist/VisuallyHidden.d.ts.map +1 -1
  134. package/dist/contexts.d.ts +5 -1
  135. package/dist/contexts.d.ts.map +1 -1
  136. package/dist/index.d.ts +73 -71
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +23247 -18564
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.jsx +18110 -0
  141. package/dist/index.jsx.map +1 -0
  142. package/dist/useDragAndDrop.d.ts +13 -13
  143. package/dist/useDragAndDrop.d.ts.map +1 -1
  144. package/dist/utils.d.ts +2 -2
  145. package/dist/utils.d.ts.map +1 -1
  146. package/dist/virtualizer/Layout.d.ts +1 -1
  147. package/dist/virtualizer/Layout.d.ts.map +1 -1
  148. package/package.json +31 -32
  149. package/src/ActionBar.tsx +75 -72
  150. package/src/ActionGroup.tsx +53 -61
  151. package/src/Alert.tsx +17 -42
  152. package/src/Autocomplete.tsx +39 -44
  153. package/src/Breadcrumbs.tsx +149 -80
  154. package/src/Button.tsx +267 -70
  155. package/src/Calendar.tsx +218 -138
  156. package/src/Checkbox.tsx +413 -121
  157. package/src/Collection.tsx +67 -58
  158. package/src/Color.tsx +803 -380
  159. package/src/ColorEditor.tsx +131 -149
  160. package/src/ComboBox.tsx +414 -249
  161. package/src/ContextualHelpTrigger.tsx +86 -74
  162. package/src/DateField.tsx +185 -91
  163. package/src/DatePicker.tsx +524 -213
  164. package/src/DateRangePickerContext.tsx +44 -0
  165. package/src/Dialog.tsx +156 -118
  166. package/src/Disclosure.tsx +127 -80
  167. package/src/DragAndDrop.tsx +60 -54
  168. package/src/DragPreview.tsx +13 -11
  169. package/src/DropZone.tsx +42 -22
  170. package/src/FieldError.tsx +45 -23
  171. package/src/FileTrigger.tsx +19 -19
  172. package/src/Focusable.tsx +21 -24
  173. package/src/Form.tsx +71 -16
  174. package/src/GridList.tsx +273 -197
  175. package/src/HiddenDateInput.tsx +153 -0
  176. package/src/HiddenTimeInput.tsx +133 -0
  177. package/src/Icon.tsx +22 -43
  178. package/src/Keyboard.tsx +3 -3
  179. package/src/Landmark.tsx +37 -63
  180. package/src/Link.tsx +125 -75
  181. package/src/ListBox.tsx +332 -233
  182. package/src/ListDropTargetDelegate.ts +81 -80
  183. package/src/Menu.tsx +1023 -274
  184. package/src/Meter.tsx +38 -56
  185. package/src/Modal.tsx +243 -175
  186. package/src/NumberField.tsx +139 -143
  187. package/src/Popover.tsx +386 -233
  188. package/src/Pressable.tsx +21 -21
  189. package/src/ProgressBar.tsx +48 -57
  190. package/src/RadioGroup.tsx +524 -122
  191. package/src/RangeCalendar.tsx +157 -90
  192. package/src/RouterProvider.tsx +30 -47
  193. package/src/SearchField.tsx +362 -143
  194. package/src/Select.tsx +656 -233
  195. package/src/SelectionIndicator.tsx +18 -15
  196. package/src/Separator.tsx +47 -49
  197. package/src/SharedElementTransition.tsx +103 -97
  198. package/src/Slider.tsx +138 -98
  199. package/src/StepList.tsx +272 -0
  200. package/src/Switch.tsx +93 -46
  201. package/src/Table.tsx +1308 -342
  202. package/src/Tabs.tsx +324 -103
  203. package/src/TagGroup.tsx +139 -126
  204. package/src/Text.tsx +3 -3
  205. package/src/TextField.tsx +389 -79
  206. package/src/TimeField.tsx +136 -76
  207. package/src/Toast.tsx +209 -157
  208. package/src/ToggleButton.tsx +47 -37
  209. package/src/ToggleButtonGroup.tsx +39 -34
  210. package/src/Toolbar.tsx +54 -69
  211. package/src/Tooltip.tsx +387 -119
  212. package/src/Tree.tsx +651 -368
  213. package/src/Virtualizer.tsx +208 -180
  214. package/src/VirtualizerLayouts.ts +45 -30
  215. package/src/VisuallyHidden.tsx +19 -19
  216. package/src/contexts.ts +29 -37
  217. package/src/index.ts +110 -195
  218. package/src/useDragAndDrop.ts +87 -71
  219. package/src/utils.tsx +40 -55
  220. package/src/virtualizer/Layout.ts +14 -22
  221. package/dist/index.ssr.js +0 -16996
  222. package/dist/index.ssr.js.map +0 -1
@@ -11,11 +11,12 @@ import {
11
11
  createEffect,
12
12
  createMemo,
13
13
  createSignal,
14
+ onCleanup,
14
15
  splitProps,
15
16
  useContext,
16
17
  Show,
17
- } from 'solid-js';
18
- import { Portal } from 'solid-js/web';
18
+ } from "solid-js";
19
+ import { Portal } from "solid-js/web";
19
20
  import {
20
21
  createDatePicker,
21
22
  createDateRangePicker,
@@ -25,12 +26,16 @@ import {
25
26
  type AriaDatePickerProps,
26
27
  type AriaDateRangePickerProps,
27
28
  type DatePickerState as AriaDatePickerState,
28
- } from '@proyecto-viviana/solidaria';
29
+ } from "@proyecto-viviana/solidaria";
29
30
  import {
30
31
  createDateFieldState,
31
32
  createCalendarState,
32
33
  createRangeCalendarState,
34
+ createDatePickerState,
35
+ access,
33
36
  type DateFieldState,
37
+ type DatePickerState,
38
+ type CalendarStateProps,
34
39
  type CalendarState,
35
40
  type RangeCalendarState,
36
41
  type DateFieldStateProps,
@@ -38,7 +43,7 @@ import {
38
43
  type DateValue,
39
44
  type RangeCalendarStateProps,
40
45
  type RangeValue,
41
- } from '@proyecto-viviana/solid-stately';
46
+ } from "@proyecto-viviana/solid-stately";
42
47
  import {
43
48
  type RenderChildren,
44
49
  type ClassNameOrFunction,
@@ -47,14 +52,18 @@ import {
47
52
  useRenderProps,
48
53
  dataAttr,
49
54
  useIsHydrated,
50
- } from './utils';
51
- import { DateFieldContext } from './DateField';
52
- import { CalendarContext } from './Calendar';
53
- import { RangeCalendarContext } from './RangeCalendar';
54
-
55
- // ============================================
56
- // TYPES
57
- // ============================================
55
+ } from "./utils";
56
+ import { DateFieldContext } from "./DateField";
57
+ import { CalendarContext } from "./Calendar";
58
+ import { RangeCalendarContext } from "./RangeCalendar";
59
+ import { HiddenDateInput } from "./HiddenDateInput";
60
+ import { FormContext, type FormProps } from "./Form";
61
+ import {
62
+ DateRangePickerContext,
63
+ useDateRangePickerContext,
64
+ type DateRangePickerContextValue,
65
+ type DateRangePickerFieldContextValue,
66
+ } from "./DateRangePickerContext";
58
67
 
59
68
  export interface DatePickerRenderProps {
60
69
  /** Whether the picker is disabled. */
@@ -69,13 +78,13 @@ export interface DatePickerRenderProps {
69
78
  isOpen: boolean;
70
79
  }
71
80
 
72
- export interface DateRangePickerRenderProps
73
- extends Omit<DatePickerRenderProps, 'isInvalid'> {
81
+ export interface DateRangePickerRenderProps extends Omit<DatePickerRenderProps, "isInvalid"> {
74
82
  isInvalid: boolean;
75
83
  }
76
84
 
77
85
  export interface DatePickerContextValue {
78
86
  fieldState: DateFieldState<DateValue>;
87
+ datePickerState: DatePickerState<DateValue>;
79
88
  calendarState: CalendarState<DateValue>;
80
89
  overlayState: {
81
90
  isOpen: boolean;
@@ -88,44 +97,74 @@ export interface DatePickerContextValue {
88
97
  pickerAria: ReturnType<typeof createDatePicker>;
89
98
  }
90
99
 
91
- export interface DateRangePickerContextValue {
92
- calendarState: RangeCalendarState<DateValue>;
93
- overlayState: {
94
- isOpen: boolean;
95
- open: () => void;
96
- close: () => void;
97
- toggle: () => void;
100
+ export type DatePickerProps<T extends DateValue = DateValue> = Omit<
101
+ AriaDatePickerProps,
102
+ "id" | "isDisabled" | "isReadOnly" | "isRequired" | "minValue" | "maxValue"
103
+ > &
104
+ Omit<DateFieldStateProps<T>, "locale"> &
105
+ SlotProps & {
106
+ /** The children of the component. */
107
+ children?: JSX.Element;
108
+ /** The CSS className for the element. */
109
+ class?: ClassNameOrFunction<DatePickerRenderProps>;
110
+ /** The inline style for the element. */
111
+ style?: StyleOrFunction<DatePickerRenderProps>;
112
+ /** The locale to use for formatting. */
113
+ locale?: string;
114
+ /** Whether the calendar should close when a date is selected. */
115
+ shouldCloseOnSelect?: boolean;
116
+ /** Whether the overlay is open by default (uncontrolled). */
117
+ defaultOpen?: boolean;
118
+ /** Whether the overlay is open (controlled). */
119
+ isOpen?: boolean;
120
+ /** Callback when the overlay open state changes. */
121
+ onOpenChange?: (isOpen: boolean) => void;
122
+ /** The name for the hidden date input used in HTML form submission. */
123
+ name?: string;
124
+ /** The associated form id for the hidden date input. */
125
+ form?: string;
126
+ /** The number of months to display in the calendar popover. */
127
+ visibleMonths?: number;
128
+ /** Controls whether calendar paging advances by one month or by the visible month range. */
129
+ pageBehavior?: CalendarStateProps<T>["pageBehavior"];
130
+ /** Determines how visible months align around the initial focused date. */
131
+ selectionAlignment?: CalendarStateProps<T>["selectionAlignment"];
132
+ /** A function that determines whether a date is disabled. */
133
+ isDateDisabled?: (date: DateValue) => boolean;
98
134
  };
99
- triggerRef: () => HTMLElement | null;
100
- setTriggerRef: (element: HTMLElement | null) => void;
101
- pickerAria: ReturnType<typeof createDateRangePicker>;
102
- }
103
-
104
- export interface DatePickerProps<T extends DateValue = DateValue>
105
- extends Omit<AriaDatePickerProps, 'id' | 'isDisabled' | 'isReadOnly' | 'isRequired'>,
106
- Omit<DateFieldStateProps<T>, 'locale'>,
107
- SlotProps {
108
- /** The children of the component. */
109
- children?: JSX.Element;
110
- /** The CSS className for the element. */
111
- class?: ClassNameOrFunction<DatePickerRenderProps>;
112
- /** The inline style for the element. */
113
- style?: StyleOrFunction<DatePickerRenderProps>;
114
- /** The locale to use for formatting. */
115
- locale?: string;
116
- /** Whether the calendar should close when a date is selected. */
117
- shouldCloseOnSelect?: boolean;
118
- }
119
135
 
120
136
  export interface DateRangePickerProps<T extends DateValue = DateValue>
121
- extends Omit<AriaDateRangePickerProps, 'id' | 'isDisabled' | 'isReadOnly'>,
122
- Omit<RangeCalendarStateProps<T>, 'locale'>,
137
+ extends
138
+ Omit<AriaDateRangePickerProps, "id" | "isDisabled" | "isReadOnly">,
139
+ Omit<RangeCalendarStateProps<T>, "locale">,
123
140
  SlotProps {
124
141
  children?: JSX.Element;
125
142
  class?: ClassNameOrFunction<DateRangePickerRenderProps>;
126
143
  style?: StyleOrFunction<DateRangePickerRenderProps>;
127
144
  locale?: string;
128
145
  shouldCloseOnSelect?: boolean;
146
+ /** Whether the overlay is open by default (uncontrolled). */
147
+ defaultOpen?: boolean;
148
+ /** Whether the overlay is open (controlled). */
149
+ isOpen?: boolean;
150
+ /** Callback when the overlay open state changes. */
151
+ onOpenChange?: (isOpen: boolean) => void;
152
+ /** The granularity of the date/time fields. */
153
+ granularity?: "day" | "hour" | "minute" | "second";
154
+ /** Whether to show the hour in 12 or 24 hour format. */
155
+ hourCycle?: 12 | 24;
156
+ /** Whether to hide the time zone in date/time fields. */
157
+ hideTimeZone?: boolean;
158
+ /** The placeholder date used to determine segment structure. */
159
+ placeholderValue?: DateValue;
160
+ /** The name for the start date input used in HTML form submission. */
161
+ startName?: string;
162
+ /** The name for the end date input used in HTML form submission. */
163
+ endName?: string;
164
+ /** The associated form id for the hidden start/end date inputs. */
165
+ form?: string;
166
+ /** Controls whether native or ARIA validation should be used. */
167
+ validationBehavior?: "native" | "aria";
129
168
  }
130
169
 
131
170
  export interface DatePickerButtonRenderProps {
@@ -148,35 +187,72 @@ export interface DatePickerButtonProps extends SlotProps {
148
187
 
149
188
  export interface DateRangePickerButtonProps extends DatePickerButtonProps {}
150
189
 
151
- // ============================================
152
- // CONTEXT
153
- // ============================================
154
-
155
190
  export const DatePickerContext = createContext<DatePickerContextValue | null>(null);
156
- export const DateRangePickerContext = createContext<DateRangePickerContextValue | null>(null);
157
191
  export const DatePickerStateContext = createContext<DateFieldState<DateValue> | null>(null);
158
- export const DateRangePickerStateContext = createContext<RangeCalendarState<DateValue> | null>(null);
159
-
160
- export function useDatePickerContext(): DatePickerContextValue {
161
- const context = useContext(DatePickerContext);
162
- if (!context) {
163
- throw new Error('DatePicker components must be used within a DatePicker');
192
+ export const DateRangePickerStateContext = createContext<RangeCalendarState<DateValue> | null>(
193
+ null,
194
+ );
195
+ export { DateRangePickerContext, useDateRangePickerContext } from "./DateRangePickerContext";
196
+ export type {
197
+ DateRangePickerContextValue,
198
+ DateRangePickerFieldContextValue,
199
+ } from "./DateRangePickerContext";
200
+
201
+ function withFormValidationBehavior<P extends object>(props: P, formContext: FormProps | null): P {
202
+ if (!formContext?.validationBehavior) {
203
+ return props;
164
204
  }
165
- return context;
205
+
206
+ return new Proxy(props, {
207
+ get(target, property, receiver) {
208
+ const localValue = Reflect.get(target, property, receiver);
209
+ if (property === "validationBehavior" && localValue === undefined) {
210
+ return formContext.validationBehavior;
211
+ }
212
+
213
+ return localValue;
214
+ },
215
+ has(target, property) {
216
+ return (
217
+ Reflect.has(target, property) ||
218
+ (property === "validationBehavior" && formContext.validationBehavior !== undefined)
219
+ );
220
+ },
221
+ ownKeys(target) {
222
+ const keys = new Set(Reflect.ownKeys(target));
223
+ if (formContext.validationBehavior !== undefined) {
224
+ keys.add("validationBehavior");
225
+ }
226
+
227
+ return Array.from(keys);
228
+ },
229
+ getOwnPropertyDescriptor(target, property) {
230
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
231
+ if (descriptor) {
232
+ return descriptor;
233
+ }
234
+
235
+ if (property === "validationBehavior" && formContext.validationBehavior !== undefined) {
236
+ return {
237
+ enumerable: true,
238
+ configurable: true,
239
+ get: () => formContext.validationBehavior,
240
+ };
241
+ }
242
+
243
+ return undefined;
244
+ },
245
+ });
166
246
  }
167
247
 
168
- export function useDateRangePickerContext(): DateRangePickerContextValue {
169
- const context = useContext(DateRangePickerContext);
248
+ export function useDatePickerContext(): DatePickerContextValue {
249
+ const context = useContext(DatePickerContext);
170
250
  if (!context) {
171
- throw new Error('DateRangePicker components must be used within a DateRangePicker');
251
+ throw new Error("DatePicker components must be used within a DatePicker");
172
252
  }
173
253
  return context;
174
254
  }
175
255
 
176
- // ============================================
177
- // DATE PICKER COMPONENT
178
- // ============================================
179
-
180
256
  /**
181
257
  * A date picker combines a DateField and a Calendar popover.
182
258
  *
@@ -203,86 +279,121 @@ export function useDateRangePickerContext(): DateRangePickerContextValue {
203
279
  * ```
204
280
  */
205
281
  export function DatePicker<T extends DateValue = CalendarDate>(
206
- props: DatePickerProps<T>
282
+ props: DatePickerProps<T>,
207
283
  ): JSX.Element {
208
284
  // Use hydration-safe pattern for client-only rendering
209
285
  const isHydrated = useIsHydrated();
286
+ const formContext = useContext(FormContext);
210
287
 
211
288
  return (
212
289
  <Show
213
290
  when={isHydrated()}
214
- fallback={<div class="solidaria-DatePicker solidaria-DatePicker--placeholder" aria-hidden="true" />}
291
+ fallback={
292
+ <div class="solidaria-DatePicker solidaria-DatePicker--placeholder" aria-hidden="true" />
293
+ }
215
294
  >
216
- <DatePickerInner {...props} />
295
+ <DatePickerInner {...props} __formContext={formContext} />
217
296
  </Show>
218
297
  );
219
298
  }
220
299
 
300
+ type DatePickerInnerProps<T extends DateValue = DateValue> = DatePickerProps<T> & {
301
+ __formContext?: FormProps | null;
302
+ };
303
+
221
304
  /**
222
305
  * Internal DatePicker component that renders after client mount.
223
306
  */
224
307
  function DatePickerInner<T extends DateValue = CalendarDate>(
225
- props: DatePickerProps<T>
308
+ props: DatePickerInnerProps<T>,
226
309
  ): JSX.Element {
310
+ const formContext = props.__formContext ?? useContext(FormContext);
311
+ const mergedProps = withFormValidationBehavior(props, formContext);
227
312
  const [local, stateProps, rest] = splitProps(
228
- props,
229
- ['children', 'class', 'style', 'slot', 'shouldCloseOnSelect'],
313
+ mergedProps,
314
+ ["children", "class", "style", "slot", "shouldCloseOnSelect", "__formContext"],
230
315
  [
231
- 'value',
232
- 'defaultValue',
233
- 'onChange',
234
- 'minValue',
235
- 'maxValue',
236
- 'isDisabled',
237
- 'isReadOnly',
238
- 'isRequired',
239
- 'locale',
240
- 'granularity',
241
- 'hourCycle',
242
- 'hideTimeZone',
243
- 'placeholderValue',
244
- 'validationState',
245
- 'description',
246
- 'errorMessage',
247
- ]
316
+ "value",
317
+ "defaultValue",
318
+ "onChange",
319
+ "isOpen",
320
+ "defaultOpen",
321
+ "onOpenChange",
322
+ "minValue",
323
+ "maxValue",
324
+ "isInvalid",
325
+ "isDisabled",
326
+ "isReadOnly",
327
+ "isRequired",
328
+ "locale",
329
+ "granularity",
330
+ "hourCycle",
331
+ "hideTimeZone",
332
+ "placeholderValue",
333
+ "shouldForceLeadingZeros",
334
+ "createCalendar",
335
+ "validationState",
336
+ "validationBehavior",
337
+ "validate",
338
+ "description",
339
+ "errorMessage",
340
+ "isDateUnavailable",
341
+ "firstDayOfWeek",
342
+ "visibleMonths",
343
+ "pageBehavior",
344
+ "selectionAlignment",
345
+ "isDateDisabled",
346
+ ],
248
347
  );
249
348
 
250
- // Create overlay trigger state
251
- const [isOpen, setIsOpen] = createSignal(false);
252
- let triggerRef: HTMLElement | null = null;
349
+ const [triggerRef, setTriggerRef] = createSignal<HTMLElement | null>(null);
350
+ const [fieldRef, setFieldRef] = createSignal<HTMLDivElement | null>(null);
351
+
352
+ // Unified state using createDatePickerState as single source of truth
353
+ const datePickerState = createDatePickerState<T>({
354
+ ...(stateProps as unknown as import("@proyecto-viviana/solid-stately").DatePickerStateOptions<T>),
355
+ shouldCloseOnSelect: local.shouldCloseOnSelect,
356
+ });
253
357
 
254
358
  const overlayState = {
255
- get isOpen() { return isOpen(); },
256
- open: () => setIsOpen(true),
257
- close: () => setIsOpen(false),
258
- toggle: () => setIsOpen((prev) => !prev),
359
+ get isOpen() {
360
+ return datePickerState.isOpen();
361
+ },
362
+ open: datePickerState.open,
363
+ close: datePickerState.close,
364
+ toggle: () => datePickerState.setOpen(!datePickerState.isOpen()),
259
365
  };
260
366
 
261
- // Create date field state
262
- const fieldState = createDateFieldState({
367
+ // Create field state synced through datePickerState
368
+ const fieldState = createDateFieldState<T>({
263
369
  ...stateProps,
370
+ value: () => datePickerState.value(),
264
371
  onChange: (value) => {
265
- stateProps.onChange?.(value);
266
- if (local.shouldCloseOnSelect !== false && value) {
267
- overlayState.close();
268
- }
372
+ datePickerState.setValue(value);
269
373
  },
270
374
  });
271
375
 
272
- // Create calendar state that syncs with field
273
- const calendarState = createCalendarState({
274
- value: () => fieldState.value(),
376
+ // Create calendar state synced through datePickerState
377
+ const calendarState = createCalendarState<T>({
378
+ value: () => datePickerState.value(),
275
379
  onChange: (value) => {
276
- fieldState.setValue(value as T | null);
277
- if (local.shouldCloseOnSelect !== false) {
278
- overlayState.close();
380
+ if (!value) {
381
+ return;
279
382
  }
383
+ datePickerState.setDateValue(value);
280
384
  },
281
385
  minValue: stateProps.minValue,
282
386
  maxValue: stateProps.maxValue,
283
387
  isDisabled: stateProps.isDisabled,
284
388
  isReadOnly: stateProps.isReadOnly,
285
389
  locale: stateProps.locale,
390
+ createCalendar: stateProps.createCalendar as CalendarStateProps<T>["createCalendar"],
391
+ isDateUnavailable: stateProps.isDateUnavailable,
392
+ firstDayOfWeek: stateProps.firstDayOfWeek as 0 | 1 | 2 | 3 | 4 | 5 | 6 | undefined,
393
+ visibleMonths: stateProps.visibleMonths,
394
+ pageBehavior: stateProps.pageBehavior,
395
+ selectionAlignment: stateProps.selectionAlignment,
396
+ isDateDisabled: stateProps.isDateDisabled,
286
397
  });
287
398
 
288
399
  // Create date picker ARIA props
@@ -294,27 +405,30 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
294
405
  }),
295
406
  fieldState as unknown as DateFieldState<DateValue>,
296
407
  overlayState as AriaDatePickerState,
297
- calendarState as unknown as CalendarState<DateValue>
408
+ calendarState as unknown as CalendarState<DateValue>,
298
409
  );
299
410
 
300
- // Context value
301
411
  const contextValue: DatePickerContextValue = {
302
412
  fieldState: fieldState as unknown as DateFieldState<DateValue>,
413
+ datePickerState: datePickerState as unknown as DatePickerState<DateValue>,
303
414
  calendarState: calendarState as unknown as CalendarState<DateValue>,
304
415
  overlayState,
305
- triggerRef: () => triggerRef,
416
+ triggerRef,
306
417
  setTriggerRef: (element) => {
307
418
  if (!element) return;
308
- if (!triggerRef || !triggerRef.isConnected) {
309
- triggerRef = element;
419
+ const current = triggerRef();
420
+ if (!current || !current.isConnected) {
421
+ setTriggerRef(() => element);
310
422
  }
311
423
  },
312
424
  pickerAria,
313
425
  };
314
426
 
315
- // Render props values
316
427
  const isInvalid = createMemo(
317
- () => fieldState.isInvalid() || Boolean((rest as { isInvalid?: boolean }).isInvalid)
428
+ () =>
429
+ fieldState.isInvalid() ||
430
+ datePickerState.builtinValidation().isInvalid ||
431
+ Boolean(stateProps.isInvalid),
318
432
  );
319
433
 
320
434
  const renderValues = createMemo<DatePickerRenderProps>(() => ({
@@ -325,16 +439,20 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
325
439
  isOpen: overlayState.isOpen,
326
440
  }));
327
441
 
328
- // Resolve render props
329
442
  const renderProps = useRenderProps(
330
443
  {
331
444
  class: local.class,
332
445
  style: local.style,
333
- defaultClassName: 'solidaria-DatePicker',
446
+ defaultClassName: "solidaria-DatePicker",
334
447
  },
335
- renderValues
448
+ renderValues,
336
449
  );
337
450
 
451
+ const validationBehavior = () =>
452
+ (stateProps as { validationBehavior?: "aria" | "native" }).validationBehavior ??
453
+ formContext?.validationBehavior ??
454
+ "native";
455
+
338
456
  return (
339
457
  <DatePickerStateContext.Provider value={fieldState as unknown as DateFieldState<DateValue>}>
340
458
  <DatePickerContext.Provider value={contextValue}>
@@ -352,6 +470,7 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
352
470
  >
353
471
  <CalendarContext.Provider value={calendarState as unknown as CalendarState<DateValue>}>
354
472
  <div
473
+ ref={setFieldRef}
355
474
  {...pickerAria.groupProps}
356
475
  class={renderProps.class()}
357
476
  style={renderProps.style()}
@@ -363,6 +482,24 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
363
482
  >
364
483
  {props.children}
365
484
  </div>
485
+ <Show when={(rest as Record<string, unknown>).name}>
486
+ <HiddenDateInput
487
+ name={(rest as Record<string, unknown>).name as string | undefined}
488
+ form={(rest as Record<string, unknown>).form as string | undefined}
489
+ value={() => datePickerState.value()}
490
+ autoComplete={(rest as Record<string, unknown>).autoComplete as string | undefined}
491
+ isDisabled={fieldState.isDisabled()}
492
+ isRequired={fieldState.isRequired()}
493
+ validationBehavior={validationBehavior()}
494
+ validationState={fieldState}
495
+ focus={() => {
496
+ fieldRef()?.querySelector<HTMLElement>('[role="spinbutton"]')?.focus();
497
+ }}
498
+ minValue={() => access(stateProps.minValue) as DateValue | undefined}
499
+ maxValue={() => access(stateProps.maxValue) as DateValue | undefined}
500
+ granularity={datePickerState.granularity}
501
+ />
502
+ </Show>
366
503
  </CalendarContext.Provider>
367
504
  </DateFieldContext.Provider>
368
505
  </DatePickerContext.Provider>
@@ -371,13 +508,18 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
371
508
  }
372
509
 
373
510
  export function DateRangePicker<T extends DateValue = CalendarDate>(
374
- props: DateRangePickerProps<T>
511
+ props: DateRangePickerProps<T>,
375
512
  ): JSX.Element {
376
513
  const isHydrated = useIsHydrated();
377
514
  return (
378
515
  <Show
379
516
  when={isHydrated()}
380
- fallback={<div class="solidaria-DateRangePicker solidaria-DateRangePicker--placeholder" aria-hidden="true" />}
517
+ fallback={
518
+ <div
519
+ class="solidaria-DateRangePicker solidaria-DateRangePicker--placeholder"
520
+ aria-hidden="true"
521
+ />
522
+ }
381
523
  >
382
524
  <DateRangePickerInner {...props} />
383
525
  </Show>
@@ -385,50 +527,155 @@ export function DateRangePicker<T extends DateValue = CalendarDate>(
385
527
  }
386
528
 
387
529
  function DateRangePickerInner<T extends DateValue = CalendarDate>(
388
- props: DateRangePickerProps<T>
530
+ props: DateRangePickerProps<T>,
389
531
  ): JSX.Element {
390
- const [local, stateProps, rest] = splitProps(
532
+ const [local, overlayProps, stateProps, rest] = splitProps(
391
533
  props,
392
- ['children', 'class', 'style', 'slot', 'shouldCloseOnSelect'],
534
+ ["children", "class", "style", "slot", "shouldCloseOnSelect"],
535
+ ["defaultOpen", "isOpen", "onOpenChange"],
393
536
  [
394
- 'value',
395
- 'defaultValue',
396
- 'onChange',
397
- 'minValue',
398
- 'maxValue',
399
- 'isDisabled',
400
- 'isReadOnly',
401
- 'focusedValue',
402
- 'defaultFocusedValue',
403
- 'onFocusChange',
404
- 'locale',
405
- 'isDateUnavailable',
406
- 'visibleMonths',
407
- 'isDateDisabled',
408
- 'validationState',
409
- 'allowsNonContiguousRanges',
410
- 'firstDayOfWeek',
411
- ]
537
+ "value",
538
+ "defaultValue",
539
+ "onChange",
540
+ "minValue",
541
+ "maxValue",
542
+ "isDisabled",
543
+ "isReadOnly",
544
+ "focusedValue",
545
+ "defaultFocusedValue",
546
+ "onFocusChange",
547
+ "locale",
548
+ "granularity",
549
+ "hourCycle",
550
+ "hideTimeZone",
551
+ "placeholderValue",
552
+ "createCalendar",
553
+ "isDateUnavailable",
554
+ "visibleMonths",
555
+ "isDateDisabled",
556
+ "validationState",
557
+ "allowsNonContiguousRanges",
558
+ "firstDayOfWeek",
559
+ "pageBehavior",
560
+ "selectionAlignment",
561
+ ],
412
562
  );
413
563
 
414
- const [isOpen, setIsOpen] = createSignal(false);
564
+ const [internalOpen, setInternalOpen] = createSignal(overlayProps.defaultOpen ?? false);
565
+ const isOpen = () => access(overlayProps.isOpen) ?? internalOpen();
566
+ const setOpen = (open: boolean) => {
567
+ if (access(overlayProps.isOpen) === undefined) {
568
+ setInternalOpen(open);
569
+ }
570
+ overlayProps.onOpenChange?.(open);
571
+ };
572
+
415
573
  let triggerRef: HTMLElement | null = null;
416
574
  const overlayState = {
417
- get isOpen() { return isOpen(); },
418
- open: () => setIsOpen(true),
419
- close: () => setIsOpen(false),
420
- toggle: () => setIsOpen((prev) => !prev),
575
+ get isOpen() {
576
+ return isOpen();
577
+ },
578
+ open: () => setOpen(true),
579
+ close: () => setOpen(false),
580
+ toggle: () => setOpen(!isOpen()),
581
+ };
582
+
583
+ const [internalRangeValue, setInternalRangeValue] = createSignal<RangeValue<T> | null>(
584
+ stateProps.defaultValue ?? null,
585
+ );
586
+ const currentRangeValue = createMemo<RangeValue<T> | null>(() => {
587
+ const controlled = access(stateProps.value);
588
+ return controlled !== undefined ? controlled : internalRangeValue();
589
+ });
590
+ const setCommittedRangeValue = (value: RangeValue<T> | null) => {
591
+ if (access(stateProps.value) === undefined) {
592
+ setInternalRangeValue(() => value);
593
+ }
594
+ stateProps.onChange?.(value);
421
595
  };
422
596
 
423
597
  const calendarState = createRangeCalendarState({
424
598
  ...stateProps,
599
+ value: currentRangeValue,
425
600
  onChange: (value) => {
426
- stateProps.onChange?.(value);
601
+ setCommittedRangeValue(value);
427
602
  if (local.shouldCloseOnSelect !== false && value?.start && value?.end) {
428
- overlayState.close();
603
+ setOpen(false);
429
604
  }
430
605
  },
431
606
  });
607
+
608
+ const isInvalid = createMemo(
609
+ () =>
610
+ Boolean((rest as { isInvalid?: boolean }).isInvalid) ||
611
+ calendarState.validationState() === "invalid",
612
+ );
613
+ const isRequired = createMemo(() => Boolean((rest as { isRequired?: boolean }).isRequired));
614
+ const [startFieldValue, setStartFieldValue] = createSignal<T | null>(
615
+ currentRangeValue()?.start ?? null,
616
+ );
617
+ const [endFieldValue, setEndFieldValue] = createSignal<T | null>(
618
+ currentRangeValue()?.end ?? null,
619
+ );
620
+ const rangeGranularity = createMemo<"day" | "hour" | "minute" | "second">(() => {
621
+ if (stateProps.granularity) {
622
+ return stateProps.granularity;
623
+ }
624
+ const value = currentRangeValue()?.start ?? currentRangeValue()?.end;
625
+ if (value && "hour" in value) {
626
+ return "second" in value ? "second" : "minute";
627
+ }
628
+ return "day";
629
+ });
630
+
631
+ createEffect(() => {
632
+ const value = currentRangeValue();
633
+ setStartFieldValue(() => value?.start ?? null);
634
+ setEndFieldValue(() => value?.end ?? null);
635
+ });
636
+
637
+ const setRangeFieldValue = (part: "start" | "end", nextValue: T | null) => {
638
+ if (part === "start") {
639
+ setStartFieldValue(() => nextValue);
640
+ } else {
641
+ setEndFieldValue(() => nextValue);
642
+ }
643
+
644
+ const nextStart = part === "start" ? nextValue : startFieldValue();
645
+ const nextEnd = part === "end" ? nextValue : endFieldValue();
646
+
647
+ setCommittedRangeValue(
648
+ nextStart && nextEnd ? ({ start: nextStart, end: nextEnd } as RangeValue<T>) : null,
649
+ );
650
+ };
651
+
652
+ const rangeFieldStateProps = {
653
+ minValue: stateProps.minValue,
654
+ maxValue: stateProps.maxValue,
655
+ isDisabled: stateProps.isDisabled,
656
+ isReadOnly: stateProps.isReadOnly,
657
+ isRequired,
658
+ locale: access(stateProps.locale),
659
+ granularity: rangeGranularity(),
660
+ hourCycle: stateProps.hourCycle,
661
+ hideTimeZone: stateProps.hideTimeZone,
662
+ placeholderValue: stateProps.placeholderValue,
663
+ validationState: () => (isInvalid() ? "invalid" : access(stateProps.validationState)),
664
+ isDateUnavailable: stateProps.isDateUnavailable,
665
+ } satisfies Partial<DateFieldStateProps<T>>;
666
+
667
+ const startFieldState = createDateFieldState<T>({
668
+ ...rangeFieldStateProps,
669
+ value: startFieldValue,
670
+ onChange: (value) => setRangeFieldValue("start", value),
671
+ });
672
+
673
+ const endFieldState = createDateFieldState<T>({
674
+ ...rangeFieldStateProps,
675
+ value: endFieldValue,
676
+ onChange: (value) => setRangeFieldValue("end", value),
677
+ });
678
+
432
679
  const pickerAria = createDateRangePicker(
433
680
  () => ({
434
681
  ...(rest as Record<string, unknown>),
@@ -436,18 +683,47 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
436
683
  errorMessage: (props as { errorMessage?: string }).errorMessage,
437
684
  }),
438
685
  calendarState as unknown as RangeCalendarState<DateValue>,
439
- overlayState as AriaDatePickerState
686
+ overlayState as AriaDatePickerState,
440
687
  );
441
688
 
442
- const isInvalid = createMemo(
443
- () => Boolean((rest as { isInvalid?: boolean }).isInvalid) || calendarState.validationState() === 'invalid'
444
- );
445
- const isRequired = createMemo(
446
- () => Boolean((rest as { isRequired?: boolean }).isRequired)
447
- );
689
+ const startFieldContext: DateRangePickerFieldContextValue = {
690
+ state: startFieldState as unknown as DateFieldState<DateValue>,
691
+ aria: {
692
+ labelProps: {},
693
+ get inputProps() {
694
+ return pickerAria.startInputProps;
695
+ },
696
+ get descriptionProps() {
697
+ return pickerAria.descriptionProps;
698
+ },
699
+ get errorMessageProps() {
700
+ return pickerAria.errorMessageProps;
701
+ },
702
+ },
703
+ };
704
+
705
+ const endFieldContext: DateRangePickerFieldContextValue = {
706
+ state: endFieldState as unknown as DateFieldState<DateValue>,
707
+ aria: {
708
+ labelProps: {},
709
+ get inputProps() {
710
+ return pickerAria.endInputProps;
711
+ },
712
+ get descriptionProps() {
713
+ return pickerAria.descriptionProps;
714
+ },
715
+ get errorMessageProps() {
716
+ return pickerAria.errorMessageProps;
717
+ },
718
+ },
719
+ };
448
720
 
449
721
  const contextValue: DateRangePickerContextValue = {
450
722
  calendarState: calendarState as unknown as RangeCalendarState<DateValue>,
723
+ startFieldState: startFieldState as unknown as DateFieldState<DateValue>,
724
+ endFieldState: endFieldState as unknown as DateFieldState<DateValue>,
725
+ startFieldContext,
726
+ endFieldContext,
451
727
  overlayState,
452
728
  triggerRef: () => triggerRef,
453
729
  setTriggerRef: (element) => {
@@ -469,15 +745,19 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
469
745
  {
470
746
  class: local.class,
471
747
  style: local.style,
472
- defaultClassName: 'solidaria-DateRangePicker',
748
+ defaultClassName: "solidaria-DateRangePicker",
473
749
  },
474
- renderValues
750
+ renderValues,
475
751
  );
476
752
 
477
753
  return (
478
- <DateRangePickerStateContext.Provider value={calendarState as unknown as RangeCalendarState<DateValue>}>
754
+ <DateRangePickerStateContext.Provider
755
+ value={calendarState as unknown as RangeCalendarState<DateValue>}
756
+ >
479
757
  <DateRangePickerContext.Provider value={contextValue}>
480
- <RangeCalendarContext.Provider value={calendarState as unknown as RangeCalendarState<DateValue>}>
758
+ <RangeCalendarContext.Provider
759
+ value={calendarState as unknown as RangeCalendarState<DateValue>}
760
+ >
481
761
  <div
482
762
  {...pickerAria.groupProps}
483
763
  class={renderProps.class()}
@@ -490,52 +770,66 @@ function DateRangePickerInner<T extends DateValue = CalendarDate>(
490
770
  >
491
771
  {props.children}
492
772
  </div>
773
+ <Show when={(rest as Record<string, unknown>).startName}>
774
+ <HiddenDateInput
775
+ name={(rest as Record<string, unknown>).startName as string | undefined}
776
+ form={(rest as Record<string, unknown>).form as string | undefined}
777
+ value={() => currentRangeValue()?.start ?? null}
778
+ isDisabled={access(stateProps.isDisabled) ?? false}
779
+ minValue={() => access(stateProps.minValue) as DateValue | undefined}
780
+ maxValue={() => access(stateProps.maxValue) as DateValue | undefined}
781
+ granularity={rangeGranularity()}
782
+ />
783
+ </Show>
784
+ <Show when={(rest as Record<string, unknown>).endName}>
785
+ <HiddenDateInput
786
+ name={(rest as Record<string, unknown>).endName as string | undefined}
787
+ form={(rest as Record<string, unknown>).form as string | undefined}
788
+ value={() => currentRangeValue()?.end ?? null}
789
+ isDisabled={access(stateProps.isDisabled) ?? false}
790
+ minValue={() => access(stateProps.minValue) as DateValue | undefined}
791
+ maxValue={() => access(stateProps.maxValue) as DateValue | undefined}
792
+ granularity={rangeGranularity()}
793
+ />
794
+ </Show>
493
795
  </RangeCalendarContext.Provider>
494
796
  </DateRangePickerContext.Provider>
495
797
  </DateRangePickerStateContext.Provider>
496
798
  );
497
799
  }
498
800
 
499
- // ============================================
500
- // DATE PICKER BUTTON COMPONENT
501
- // ============================================
502
-
503
801
  /**
504
802
  * A button that opens the date picker calendar.
505
803
  */
506
804
  export function DatePickerButton(props: DatePickerButtonProps): JSX.Element {
507
805
  const context = useDatePickerContext();
508
- let buttonRef: HTMLButtonElement | undefined;
509
806
 
510
- // Render props values
511
807
  const renderValues = createMemo<DatePickerButtonRenderProps>(() => ({
512
808
  isDisabled: context.fieldState.isDisabled() || (props.isDisabled ?? false),
513
809
  isOpen: context.overlayState.isOpen,
514
810
  }));
515
811
 
516
- // Resolve render props
517
812
  const renderProps = useRenderProps(
518
813
  {
519
814
  children: props.children,
520
815
  class: props.class,
521
816
  style: props.style,
522
- defaultClassName: 'solidaria-DatePickerButton',
817
+ defaultClassName: "solidaria-DatePickerButton",
523
818
  },
524
- renderValues
819
+ renderValues,
525
820
  );
526
821
 
527
822
  // Determine children content - avoid Show for SSR hydration compatibility
528
823
  const getChildren = () => {
529
- if (typeof props.children === 'function') {
824
+ if (typeof props.children === "function") {
530
825
  return renderProps.renderChildren();
531
826
  }
532
- return props.children ?? '📅';
827
+ return props.children ?? "📅";
533
828
  };
534
829
 
535
830
  return (
536
831
  <button
537
832
  ref={(el) => {
538
- buttonRef = el;
539
833
  context.setTriggerRef(el);
540
834
  }}
541
835
  {...context.pickerAria.buttonProps}
@@ -563,16 +857,16 @@ export function DateRangePickerButton(props: DateRangePickerButtonProps): JSX.El
563
857
  children: props.children,
564
858
  class: props.class,
565
859
  style: props.style,
566
- defaultClassName: 'solidaria-DateRangePickerButton',
860
+ defaultClassName: "solidaria-DateRangePickerButton",
567
861
  },
568
- renderValues
862
+ renderValues,
569
863
  );
570
864
 
571
865
  const getChildren = () => {
572
- if (typeof props.children === 'function') {
866
+ if (typeof props.children === "function") {
573
867
  return renderProps.renderChildren();
574
868
  }
575
- return props.children ?? '📅';
869
+ return props.children ?? "📅";
576
870
  };
577
871
 
578
872
  return (
@@ -590,10 +884,6 @@ export function DateRangePickerButton(props: DateRangePickerButtonProps): JSX.El
590
884
  );
591
885
  }
592
886
 
593
- // ============================================
594
- // DATE PICKER CONTENT COMPONENT
595
- // ============================================
596
-
597
887
  export interface DatePickerContentProps extends SlotProps {
598
888
  /** The children of the component. */
599
889
  children?: JSX.Element;
@@ -689,6 +979,20 @@ export function DateRangePickerErrorMessage(props: DateRangePickerErrorMessagePr
689
979
  );
690
980
  }
691
981
 
982
+ function createEscapeDismissFallback(isOpen: () => boolean, close: () => void): void {
983
+ createEffect(() => {
984
+ if (!isOpen() || typeof document === "undefined") return;
985
+
986
+ const onKeyDown = (event: KeyboardEvent) => {
987
+ if (event.key !== "Escape" || event.defaultPrevented || event.isComposing) return;
988
+ close();
989
+ };
990
+
991
+ document.addEventListener("keydown", onKeyDown);
992
+ onCleanup(() => document.removeEventListener("keydown", onKeyDown));
993
+ });
994
+ }
995
+
692
996
  /**
693
997
  * The content area of the date picker (typically contains a Calendar).
694
998
  */
@@ -700,9 +1004,9 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
700
1004
 
701
1005
  const popoverAria = createPopover(
702
1006
  {
703
- triggerRef: context.triggerRef,
1007
+ triggerRef: () => context.triggerRef()?.parentElement ?? context.triggerRef(),
704
1008
  popoverRef: () => contentRef ?? null,
705
- placement: 'bottom start',
1009
+ placement: "bottom start",
706
1010
  offset: 8,
707
1011
  isNonModal: false,
708
1012
  isKeyboardDismissDisabled: false,
@@ -712,48 +1016,50 @@ export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
712
1016
  open: context.overlayState.open,
713
1017
  close: context.overlayState.close,
714
1018
  toggle: context.overlayState.toggle,
715
- }
1019
+ },
716
1020
  );
717
1021
 
1022
+ createEscapeDismissFallback(() => context.overlayState.isOpen, context.overlayState.close);
1023
+
718
1024
  const cleanPopoverProps = () => {
719
- const { style: _style, ref: _ref, ...rest } = popoverAria.popoverProps as Record<string, unknown>;
1025
+ const {
1026
+ style: _style,
1027
+ ref: _ref,
1028
+ ...rest
1029
+ } = popoverAria.popoverProps as Record<string, unknown>;
720
1030
  return rest;
721
1031
  };
722
1032
 
723
1033
  const mergedStyle = (): JSX.CSSProperties => {
724
- const popoverStyle = (popoverAria.popoverProps as Record<string, unknown>).style as JSX.CSSProperties | undefined;
1034
+ const popoverStyle = (popoverAria.popoverProps as Record<string, unknown>).style as
1035
+ | JSX.CSSProperties
1036
+ | undefined;
725
1037
  return {
726
- ...(popoverStyle ?? {}),
727
- ...(props.style ?? {}),
1038
+ ...popoverStyle,
1039
+ ...props.style,
728
1040
  };
729
1041
  };
730
1042
 
1043
+ // Return focus to trigger when overlay closes
731
1044
  createEffect(() => {
732
- if (!context.overlayState.isOpen || !contentRef) return;
733
- if (document.activeElement !== contentRef) {
734
- contentRef.focus();
1045
+ const open = context.overlayState.isOpen;
1046
+ if (!open) {
1047
+ requestAnimationFrame(() => context.triggerRef()?.focus());
735
1048
  }
736
1049
  });
737
1050
 
738
1051
  return (
739
1052
  <Show when={context.overlayState.isOpen}>
740
1053
  <Portal mount={portalContainer()}>
741
- <FocusScope contain restoreFocus autoFocus>
1054
+ <FocusScope contain restoreFocus>
742
1055
  <div
743
1056
  ref={contentRef}
744
1057
  {...cleanPopoverProps()}
745
1058
  {...context.pickerAria.dialogProps}
746
1059
  tabIndex={-1}
747
- class={props.class ?? 'solidaria-DatePickerContent'}
1060
+ class={props.class ?? "solidaria-DatePickerContent"}
748
1061
  style={mergedStyle()}
749
1062
  data-placement={popoverAria.placement() ?? undefined}
750
- onKeyDown={(event) => {
751
- if (event.key === 'Escape') {
752
- event.preventDefault();
753
- event.stopPropagation();
754
- context.overlayState.close();
755
- }
756
- }}
757
1063
  >
758
1064
  {props.children}
759
1065
  </div>
@@ -771,9 +1077,9 @@ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.
771
1077
 
772
1078
  const popoverAria = createPopover(
773
1079
  {
774
- triggerRef: context.triggerRef,
1080
+ triggerRef: () => context.triggerRef()?.parentElement ?? context.triggerRef(),
775
1081
  popoverRef: () => contentRef ?? null,
776
- placement: 'bottom start',
1082
+ placement: "bottom start",
777
1083
  offset: 8,
778
1084
  isNonModal: false,
779
1085
  isKeyboardDismissDisabled: false,
@@ -783,48 +1089,50 @@ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.
783
1089
  open: context.overlayState.open,
784
1090
  close: context.overlayState.close,
785
1091
  toggle: context.overlayState.toggle,
786
- }
1092
+ },
787
1093
  );
788
1094
 
1095
+ createEscapeDismissFallback(() => context.overlayState.isOpen, context.overlayState.close);
1096
+
789
1097
  const cleanPopoverProps = () => {
790
- const { style: _style, ref: _ref, ...rest } = popoverAria.popoverProps as Record<string, unknown>;
1098
+ const {
1099
+ style: _style,
1100
+ ref: _ref,
1101
+ ...rest
1102
+ } = popoverAria.popoverProps as Record<string, unknown>;
791
1103
  return rest;
792
1104
  };
793
1105
 
794
1106
  const mergedStyle = (): JSX.CSSProperties => {
795
- const popoverStyle = (popoverAria.popoverProps as Record<string, unknown>).style as JSX.CSSProperties | undefined;
1107
+ const popoverStyle = (popoverAria.popoverProps as Record<string, unknown>).style as
1108
+ | JSX.CSSProperties
1109
+ | undefined;
796
1110
  return {
797
- ...(popoverStyle ?? {}),
798
- ...(props.style ?? {}),
1111
+ ...popoverStyle,
1112
+ ...props.style,
799
1113
  };
800
1114
  };
801
1115
 
1116
+ // Return focus to trigger when overlay closes
802
1117
  createEffect(() => {
803
- if (!context.overlayState.isOpen || !contentRef) return;
804
- if (document.activeElement !== contentRef) {
805
- contentRef.focus();
1118
+ const open = context.overlayState.isOpen;
1119
+ if (!open) {
1120
+ requestAnimationFrame(() => context.triggerRef()?.focus());
806
1121
  }
807
1122
  });
808
1123
 
809
1124
  return (
810
1125
  <Show when={context.overlayState.isOpen}>
811
1126
  <Portal mount={portalContainer()}>
812
- <FocusScope contain restoreFocus autoFocus>
1127
+ <FocusScope contain restoreFocus>
813
1128
  <div
814
1129
  ref={contentRef}
815
1130
  {...cleanPopoverProps()}
816
1131
  {...context.pickerAria.dialogProps}
817
1132
  tabIndex={-1}
818
- class={props.class ?? 'solidaria-DateRangePickerContent'}
1133
+ class={props.class ?? "solidaria-DateRangePickerContent"}
819
1134
  style={mergedStyle()}
820
1135
  data-placement={popoverAria.placement() ?? undefined}
821
- onKeyDown={(event) => {
822
- if (event.key === 'Escape') {
823
- event.preventDefault();
824
- event.stopPropagation();
825
- context.overlayState.close();
826
- }
827
- }}
828
1136
  >
829
1137
  {props.children}
830
1138
  </div>
@@ -834,4 +1142,7 @@ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.
834
1142
  );
835
1143
  }
836
1144
 
1145
+ export { HiddenDateInput } from "./HiddenDateInput";
1146
+ export type { HiddenDateInputProps } from "./HiddenDateInput";
1147
+
837
1148
  // DatePickerContextValue is already exported at declaration