@proyecto-viviana/solidaria-components 0.2.5 → 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 (225) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +39 -272
  3. package/dist/ActionBar.d.ts +79 -0
  4. package/dist/ActionBar.d.ts.map +1 -0
  5. package/dist/ActionGroup.d.ts +74 -0
  6. package/dist/ActionGroup.d.ts.map +1 -0
  7. package/dist/Alert.d.ts +70 -0
  8. package/dist/Alert.d.ts.map +1 -0
  9. package/dist/Autocomplete.d.ts +5 -5
  10. package/dist/Autocomplete.d.ts.map +1 -1
  11. package/dist/Breadcrumbs.d.ts +27 -8
  12. package/dist/Breadcrumbs.d.ts.map +1 -1
  13. package/dist/Button.d.ts +28 -5
  14. package/dist/Button.d.ts.map +1 -1
  15. package/dist/Calendar.d.ts +51 -7
  16. package/dist/Calendar.d.ts.map +1 -1
  17. package/dist/Checkbox.d.ts +33 -8
  18. package/dist/Checkbox.d.ts.map +1 -1
  19. package/dist/Collection.d.ts +130 -0
  20. package/dist/Collection.d.ts.map +1 -0
  21. package/dist/Color.d.ts +210 -9
  22. package/dist/Color.d.ts.map +1 -1
  23. package/dist/ColorEditor.d.ts +42 -0
  24. package/dist/ColorEditor.d.ts.map +1 -0
  25. package/dist/ComboBox.d.ts +146 -16
  26. package/dist/ComboBox.d.ts.map +1 -1
  27. package/dist/ContextualHelpTrigger.d.ts +40 -0
  28. package/dist/ContextualHelpTrigger.d.ts.map +1 -0
  29. package/dist/DateField.d.ts +35 -8
  30. package/dist/DateField.d.ts.map +1 -1
  31. package/dist/DatePicker.d.ts +101 -5
  32. package/dist/DatePicker.d.ts.map +1 -1
  33. package/dist/DateRangePickerContext.d.ts +30 -0
  34. package/dist/DateRangePickerContext.d.ts.map +1 -0
  35. package/dist/Dialog.d.ts +5 -5
  36. package/dist/Dialog.d.ts.map +1 -1
  37. package/dist/Disclosure.d.ts +25 -5
  38. package/dist/Disclosure.d.ts.map +1 -1
  39. package/dist/DragAndDrop.d.ts +80 -0
  40. package/dist/DragAndDrop.d.ts.map +1 -0
  41. package/dist/DragPreview.d.ts +14 -0
  42. package/dist/DragPreview.d.ts.map +1 -0
  43. package/dist/DropZone.d.ts +27 -0
  44. package/dist/DropZone.d.ts.map +1 -0
  45. package/dist/FieldError.d.ts +27 -0
  46. package/dist/FieldError.d.ts.map +1 -0
  47. package/dist/FileTrigger.d.ts +26 -0
  48. package/dist/FileTrigger.d.ts.map +1 -0
  49. package/dist/Focusable.d.ts +27 -0
  50. package/dist/Focusable.d.ts.map +1 -0
  51. package/dist/Form.d.ts +41 -0
  52. package/dist/Form.d.ts.map +1 -0
  53. package/dist/GridList.d.ts +69 -10
  54. package/dist/GridList.d.ts.map +1 -1
  55. package/dist/HiddenDateInput.d.ts +26 -0
  56. package/dist/HiddenDateInput.d.ts.map +1 -0
  57. package/dist/HiddenTimeInput.d.ts +25 -0
  58. package/dist/HiddenTimeInput.d.ts.map +1 -0
  59. package/dist/Icon.d.ts +57 -0
  60. package/dist/Icon.d.ts.map +1 -0
  61. package/dist/Keyboard.d.ts +13 -0
  62. package/dist/Keyboard.d.ts.map +1 -0
  63. package/dist/Landmark.d.ts +3 -3
  64. package/dist/Landmark.d.ts.map +1 -1
  65. package/dist/Link.d.ts +10 -4
  66. package/dist/Link.d.ts.map +1 -1
  67. package/dist/ListBox.d.ts +73 -11
  68. package/dist/ListBox.d.ts.map +1 -1
  69. package/dist/ListDropTargetDelegate.d.ts +38 -0
  70. package/dist/ListDropTargetDelegate.d.ts.map +1 -0
  71. package/dist/Menu.d.ts +79 -10
  72. package/dist/Menu.d.ts.map +1 -1
  73. package/dist/Meter.d.ts +4 -4
  74. package/dist/Meter.d.ts.map +1 -1
  75. package/dist/Modal.d.ts +6 -4
  76. package/dist/Modal.d.ts.map +1 -1
  77. package/dist/NumberField.d.ts +10 -12
  78. package/dist/NumberField.d.ts.map +1 -1
  79. package/dist/Popover.d.ts +32 -7
  80. package/dist/Popover.d.ts.map +1 -1
  81. package/dist/Pressable.d.ts +27 -0
  82. package/dist/Pressable.d.ts.map +1 -0
  83. package/dist/ProgressBar.d.ts +6 -4
  84. package/dist/ProgressBar.d.ts.map +1 -1
  85. package/dist/RadioGroup.d.ts +43 -9
  86. package/dist/RadioGroup.d.ts.map +1 -1
  87. package/dist/RangeCalendar.d.ts +39 -7
  88. package/dist/RangeCalendar.d.ts.map +1 -1
  89. package/dist/RouterProvider.d.ts +75 -0
  90. package/dist/RouterProvider.d.ts.map +1 -0
  91. package/dist/SearchField.d.ts +23 -21
  92. package/dist/SearchField.d.ts.map +1 -1
  93. package/dist/Select.d.ts +48 -7
  94. package/dist/Select.d.ts.map +1 -1
  95. package/dist/SelectionIndicator.d.ts +30 -0
  96. package/dist/SelectionIndicator.d.ts.map +1 -0
  97. package/dist/Separator.d.ts +9 -3
  98. package/dist/Separator.d.ts.map +1 -1
  99. package/dist/SharedElementTransition.d.ts +41 -0
  100. package/dist/SharedElementTransition.d.ts.map +1 -0
  101. package/dist/Slider.d.ts +15 -8
  102. package/dist/Slider.d.ts.map +1 -1
  103. package/dist/StepList.d.ts +90 -0
  104. package/dist/StepList.d.ts.map +1 -0
  105. package/dist/Switch.d.ts +11 -5
  106. package/dist/Switch.d.ts.map +1 -1
  107. package/dist/Table.d.ts +222 -19
  108. package/dist/Table.d.ts.map +1 -1
  109. package/dist/Tabs.d.ts +47 -10
  110. package/dist/Tabs.d.ts.map +1 -1
  111. package/dist/TagGroup.d.ts +22 -10
  112. package/dist/TagGroup.d.ts.map +1 -1
  113. package/dist/Text.d.ts +10 -0
  114. package/dist/Text.d.ts.map +1 -0
  115. package/dist/TextField.d.ts +19 -11
  116. package/dist/TextField.d.ts.map +1 -1
  117. package/dist/TimeField.d.ts +32 -7
  118. package/dist/TimeField.d.ts.map +1 -1
  119. package/dist/Toast.d.ts +29 -14
  120. package/dist/Toast.d.ts.map +1 -1
  121. package/dist/ToggleButton.d.ts +36 -0
  122. package/dist/ToggleButton.d.ts.map +1 -0
  123. package/dist/ToggleButtonGroup.d.ts +33 -0
  124. package/dist/ToggleButtonGroup.d.ts.map +1 -0
  125. package/dist/Toolbar.d.ts +7 -3
  126. package/dist/Toolbar.d.ts.map +1 -1
  127. package/dist/Tooltip.d.ts +58 -7
  128. package/dist/Tooltip.d.ts.map +1 -1
  129. package/dist/Tree.d.ts +102 -11
  130. package/dist/Tree.d.ts.map +1 -1
  131. package/dist/Virtualizer.d.ts +61 -0
  132. package/dist/Virtualizer.d.ts.map +1 -0
  133. package/dist/VirtualizerLayouts.d.ts +82 -0
  134. package/dist/VirtualizerLayouts.d.ts.map +1 -0
  135. package/dist/VisuallyHidden.d.ts +4 -2
  136. package/dist/VisuallyHidden.d.ts.map +1 -1
  137. package/dist/contexts.d.ts +6 -1
  138. package/dist/contexts.d.ts.map +1 -1
  139. package/dist/index.d.ts +73 -39
  140. package/dist/index.d.ts.map +1 -1
  141. package/dist/index.js +23342 -10644
  142. package/dist/index.js.map +1 -7
  143. package/dist/index.jsx +18110 -0
  144. package/dist/index.jsx.map +1 -0
  145. package/dist/useDragAndDrop.d.ts +93 -0
  146. package/dist/useDragAndDrop.d.ts.map +1 -0
  147. package/dist/utils.d.ts +8 -2
  148. package/dist/utils.d.ts.map +1 -1
  149. package/dist/virtualizer/Layout.d.ts +79 -0
  150. package/dist/virtualizer/Layout.d.ts.map +1 -0
  151. package/package.json +33 -32
  152. package/src/ActionBar.tsx +251 -0
  153. package/src/ActionGroup.tsx +277 -0
  154. package/src/Alert.tsx +152 -0
  155. package/src/Autocomplete.tsx +39 -44
  156. package/src/Breadcrumbs.tsx +227 -72
  157. package/src/Button.tsx +315 -74
  158. package/src/Calendar.tsx +347 -141
  159. package/src/Checkbox.tsx +414 -123
  160. package/src/Collection.tsx +350 -0
  161. package/src/Color.tsx +1325 -284
  162. package/src/ColorEditor.tsx +213 -0
  163. package/src/ComboBox.tsx +644 -245
  164. package/src/ContextualHelpTrigger.tsx +195 -0
  165. package/src/DateField.tsx +274 -106
  166. package/src/DatePicker.tsx +892 -111
  167. package/src/DateRangePickerContext.tsx +44 -0
  168. package/src/Dialog.tsx +173 -104
  169. package/src/Disclosure.tsx +158 -105
  170. package/src/DragAndDrop.tsx +340 -0
  171. package/src/DragPreview.tsx +47 -0
  172. package/src/DropZone.tsx +233 -0
  173. package/src/FieldError.tsx +89 -0
  174. package/src/FileTrigger.tsx +83 -0
  175. package/src/Focusable.tsx +103 -0
  176. package/src/Form.tsx +140 -0
  177. package/src/GridList.tsx +542 -128
  178. package/src/HiddenDateInput.tsx +153 -0
  179. package/src/HiddenTimeInput.tsx +133 -0
  180. package/src/Icon.tsx +133 -0
  181. package/src/Keyboard.tsx +26 -0
  182. package/src/Landmark.tsx +37 -63
  183. package/src/Link.tsx +132 -69
  184. package/src/ListBox.tsx +656 -106
  185. package/src/ListDropTargetDelegate.ts +283 -0
  186. package/src/Menu.tsx +1234 -132
  187. package/src/Meter.tsx +44 -58
  188. package/src/Modal.tsx +262 -166
  189. package/src/NumberField.tsx +267 -151
  190. package/src/Popover.tsx +452 -343
  191. package/src/Pressable.tsx +108 -0
  192. package/src/ProgressBar.tsx +54 -59
  193. package/src/RadioGroup.tsx +533 -121
  194. package/src/RangeCalendar.tsx +249 -150
  195. package/src/RouterProvider.tsx +223 -0
  196. package/src/SearchField.tsx +460 -133
  197. package/src/Select.tsx +804 -233
  198. package/src/SelectionIndicator.tsx +108 -0
  199. package/src/Separator.tsx +47 -49
  200. package/src/SharedElementTransition.tsx +264 -0
  201. package/src/Slider.tsx +148 -98
  202. package/src/StepList.tsx +272 -0
  203. package/src/Switch.tsx +93 -46
  204. package/src/Table.tsx +1551 -225
  205. package/src/Tabs.tsx +377 -123
  206. package/src/TagGroup.tsx +233 -135
  207. package/src/Text.tsx +18 -0
  208. package/src/TextField.tsx +413 -86
  209. package/src/TimeField.tsx +232 -222
  210. package/src/Toast.tsx +306 -160
  211. package/src/ToggleButton.tsx +169 -0
  212. package/src/ToggleButtonGroup.tsx +141 -0
  213. package/src/Toolbar.tsx +61 -70
  214. package/src/Tooltip.tsx +473 -116
  215. package/src/Tree.tsx +1514 -175
  216. package/src/Virtualizer.tsx +730 -0
  217. package/src/VirtualizerLayouts.ts +280 -0
  218. package/src/VisuallyHidden.tsx +32 -38
  219. package/src/contexts.ts +29 -36
  220. package/src/index.ts +972 -620
  221. package/src/useDragAndDrop.ts +367 -0
  222. package/src/utils.tsx +69 -50
  223. package/src/virtualizer/Layout.ts +192 -0
  224. package/dist/index.ssr.js +0 -9785
  225. package/dist/index.ssr.js.map +0 -7
@@ -8,26 +8,42 @@
8
8
  import {
9
9
  type JSX,
10
10
  createContext,
11
+ createEffect,
11
12
  createMemo,
12
13
  createSignal,
14
+ onCleanup,
13
15
  splitProps,
14
16
  useContext,
15
17
  Show,
16
- } from 'solid-js';
18
+ } from "solid-js";
19
+ import { Portal } from "solid-js/web";
17
20
  import {
18
21
  createDatePicker,
22
+ createDateRangePicker,
23
+ createPopover,
24
+ FocusScope,
25
+ useUNSAFE_PortalContext,
19
26
  type AriaDatePickerProps,
27
+ type AriaDateRangePickerProps,
20
28
  type DatePickerState as AriaDatePickerState,
21
- } from '@proyecto-viviana/solidaria';
29
+ } from "@proyecto-viviana/solidaria";
22
30
  import {
23
31
  createDateFieldState,
24
32
  createCalendarState,
33
+ createRangeCalendarState,
34
+ createDatePickerState,
35
+ access,
25
36
  type DateFieldState,
37
+ type DatePickerState,
38
+ type CalendarStateProps,
26
39
  type CalendarState,
40
+ type RangeCalendarState,
27
41
  type DateFieldStateProps,
28
42
  type CalendarDate,
29
43
  type DateValue,
30
- } from '@proyecto-viviana/solid-stately';
44
+ type RangeCalendarStateProps,
45
+ type RangeValue,
46
+ } from "@proyecto-viviana/solid-stately";
31
47
  import {
32
48
  type RenderChildren,
33
49
  type ClassNameOrFunction,
@@ -36,12 +52,18 @@ import {
36
52
  useRenderProps,
37
53
  dataAttr,
38
54
  useIsHydrated,
39
- } from './utils';
40
- import { DateFieldContext } from './DateField';
41
-
42
- // ============================================
43
- // TYPES
44
- // ============================================
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";
45
67
 
46
68
  export interface DatePickerRenderProps {
47
69
  /** Whether the picker is disabled. */
@@ -56,8 +78,13 @@ export interface DatePickerRenderProps {
56
78
  isOpen: boolean;
57
79
  }
58
80
 
81
+ export interface DateRangePickerRenderProps extends Omit<DatePickerRenderProps, "isInvalid"> {
82
+ isInvalid: boolean;
83
+ }
84
+
59
85
  export interface DatePickerContextValue {
60
86
  fieldState: DateFieldState<DateValue>;
87
+ datePickerState: DatePickerState<DateValue>;
61
88
  calendarState: CalendarState<DateValue>;
62
89
  overlayState: {
63
90
  isOpen: boolean;
@@ -65,23 +92,79 @@ export interface DatePickerContextValue {
65
92
  close: () => void;
66
93
  toggle: () => void;
67
94
  };
95
+ triggerRef: () => HTMLElement | null;
96
+ setTriggerRef: (element: HTMLElement | null) => void;
68
97
  pickerAria: ReturnType<typeof createDatePicker>;
69
98
  }
70
99
 
71
- export interface DatePickerProps<T extends DateValue = DateValue>
72
- extends Omit<AriaDatePickerProps, 'id' | 'isDisabled' | 'isReadOnly' | 'isRequired'>,
73
- Omit<DateFieldStateProps<T>, 'locale'>,
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;
134
+ };
135
+
136
+ export interface DateRangePickerProps<T extends DateValue = DateValue>
137
+ extends
138
+ Omit<AriaDateRangePickerProps, "id" | "isDisabled" | "isReadOnly">,
139
+ Omit<RangeCalendarStateProps<T>, "locale">,
74
140
  SlotProps {
75
- /** The children of the component. */
76
141
  children?: JSX.Element;
77
- /** The CSS className for the element. */
78
- class?: ClassNameOrFunction<DatePickerRenderProps>;
79
- /** The inline style for the element. */
80
- style?: StyleOrFunction<DatePickerRenderProps>;
81
- /** The locale to use for formatting. */
142
+ class?: ClassNameOrFunction<DateRangePickerRenderProps>;
143
+ style?: StyleOrFunction<DateRangePickerRenderProps>;
82
144
  locale?: string;
83
- /** Whether the calendar should close when a date is selected. */
84
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";
85
168
  }
86
169
 
87
170
  export interface DatePickerButtonRenderProps {
@@ -102,24 +185,74 @@ export interface DatePickerButtonProps extends SlotProps {
102
185
  isDisabled?: boolean;
103
186
  }
104
187
 
105
- // ============================================
106
- // CONTEXT
107
- // ============================================
188
+ export interface DateRangePickerButtonProps extends DatePickerButtonProps {}
108
189
 
109
190
  export const DatePickerContext = createContext<DatePickerContextValue | null>(null);
191
+ export const DatePickerStateContext = createContext<DateFieldState<DateValue> | null>(null);
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;
204
+ }
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
+ });
246
+ }
110
247
 
111
248
  export function useDatePickerContext(): DatePickerContextValue {
112
249
  const context = useContext(DatePickerContext);
113
250
  if (!context) {
114
- throw new Error('DatePicker components must be used within a DatePicker');
251
+ throw new Error("DatePicker components must be used within a DatePicker");
115
252
  }
116
253
  return context;
117
254
  }
118
255
 
119
- // ============================================
120
- // DATE PICKER COMPONENT
121
- // ============================================
122
-
123
256
  /**
124
257
  * A date picker combines a DateField and a Calendar popover.
125
258
  *
@@ -146,146 +279,524 @@ export function useDatePickerContext(): DatePickerContextValue {
146
279
  * ```
147
280
  */
148
281
  export function DatePicker<T extends DateValue = CalendarDate>(
149
- props: DatePickerProps<T>
282
+ props: DatePickerProps<T>,
150
283
  ): JSX.Element {
151
284
  // Use hydration-safe pattern for client-only rendering
152
285
  const isHydrated = useIsHydrated();
286
+ const formContext = useContext(FormContext);
153
287
 
154
288
  return (
155
289
  <Show
156
290
  when={isHydrated()}
157
- 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
+ }
158
294
  >
159
- <DatePickerInner {...props} />
295
+ <DatePickerInner {...props} __formContext={formContext} />
160
296
  </Show>
161
297
  );
162
298
  }
163
299
 
300
+ type DatePickerInnerProps<T extends DateValue = DateValue> = DatePickerProps<T> & {
301
+ __formContext?: FormProps | null;
302
+ };
303
+
164
304
  /**
165
305
  * Internal DatePicker component that renders after client mount.
166
306
  */
167
307
  function DatePickerInner<T extends DateValue = CalendarDate>(
168
- props: DatePickerProps<T>
308
+ props: DatePickerInnerProps<T>,
169
309
  ): JSX.Element {
310
+ const formContext = props.__formContext ?? useContext(FormContext);
311
+ const mergedProps = withFormValidationBehavior(props, formContext);
170
312
  const [local, stateProps, rest] = splitProps(
171
- props,
172
- ['children', 'class', 'style', 'slot', 'shouldCloseOnSelect'],
313
+ mergedProps,
314
+ ["children", "class", "style", "slot", "shouldCloseOnSelect", "__formContext"],
173
315
  [
174
- 'value',
175
- 'defaultValue',
176
- 'onChange',
177
- 'minValue',
178
- 'maxValue',
179
- 'isDisabled',
180
- 'isReadOnly',
181
- 'isRequired',
182
- 'locale',
183
- 'granularity',
184
- 'hourCycle',
185
- 'hideTimeZone',
186
- 'placeholderValue',
187
- 'validationState',
188
- 'description',
189
- 'errorMessage',
190
- ]
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
+ ],
191
347
  );
192
348
 
193
- // Create overlay trigger state
194
- const [isOpen, setIsOpen] = createSignal(false);
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
+ });
195
357
 
196
358
  const overlayState = {
197
- get isOpen() { return isOpen(); },
198
- open: () => setIsOpen(true),
199
- close: () => setIsOpen(false),
200
- 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()),
201
365
  };
202
366
 
203
- // Create date field state
204
- const fieldState = createDateFieldState({
367
+ // Create field state synced through datePickerState
368
+ const fieldState = createDateFieldState<T>({
205
369
  ...stateProps,
370
+ value: () => datePickerState.value(),
206
371
  onChange: (value) => {
207
- stateProps.onChange?.(value);
208
- if (local.shouldCloseOnSelect !== false && value) {
209
- overlayState.close();
210
- }
372
+ datePickerState.setValue(value);
211
373
  },
212
374
  });
213
375
 
214
- // Create calendar state that syncs with field
215
- const calendarState = createCalendarState({
216
- value: () => fieldState.value(),
376
+ // Create calendar state synced through datePickerState
377
+ const calendarState = createCalendarState<T>({
378
+ value: () => datePickerState.value(),
217
379
  onChange: (value) => {
218
- fieldState.setValue(value as T | null);
219
- if (local.shouldCloseOnSelect !== false) {
220
- overlayState.close();
380
+ if (!value) {
381
+ return;
221
382
  }
383
+ datePickerState.setDateValue(value);
222
384
  },
223
385
  minValue: stateProps.minValue,
224
386
  maxValue: stateProps.maxValue,
225
387
  isDisabled: stateProps.isDisabled,
226
388
  isReadOnly: stateProps.isReadOnly,
227
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,
228
397
  });
229
398
 
230
399
  // Create date picker ARIA props
231
400
  const pickerAria = createDatePicker(
232
- rest,
401
+ () => ({
402
+ ...(rest as Record<string, unknown>),
403
+ description: stateProps.description,
404
+ errorMessage: stateProps.errorMessage,
405
+ }),
233
406
  fieldState as unknown as DateFieldState<DateValue>,
234
407
  overlayState as AriaDatePickerState,
235
- calendarState as unknown as CalendarState<DateValue>
408
+ calendarState as unknown as CalendarState<DateValue>,
236
409
  );
237
410
 
238
- // Context value
239
411
  const contextValue: DatePickerContextValue = {
240
412
  fieldState: fieldState as unknown as DateFieldState<DateValue>,
413
+ datePickerState: datePickerState as unknown as DatePickerState<DateValue>,
241
414
  calendarState: calendarState as unknown as CalendarState<DateValue>,
242
415
  overlayState,
416
+ triggerRef,
417
+ setTriggerRef: (element) => {
418
+ if (!element) return;
419
+ const current = triggerRef();
420
+ if (!current || !current.isConnected) {
421
+ setTriggerRef(() => element);
422
+ }
423
+ },
243
424
  pickerAria,
244
425
  };
245
426
 
246
- // Render props values
427
+ const isInvalid = createMemo(
428
+ () =>
429
+ fieldState.isInvalid() ||
430
+ datePickerState.builtinValidation().isInvalid ||
431
+ Boolean(stateProps.isInvalid),
432
+ );
433
+
247
434
  const renderValues = createMemo<DatePickerRenderProps>(() => ({
248
435
  isDisabled: fieldState.isDisabled(),
249
436
  isReadOnly: fieldState.isReadOnly(),
250
437
  isRequired: fieldState.isRequired(),
251
- isInvalid: fieldState.isInvalid(),
438
+ isInvalid: isInvalid(),
252
439
  isOpen: overlayState.isOpen,
253
440
  }));
254
441
 
255
- // Resolve render props
256
442
  const renderProps = useRenderProps(
257
443
  {
258
444
  class: local.class,
259
445
  style: local.style,
260
- defaultClassName: 'solidaria-DatePicker',
446
+ defaultClassName: "solidaria-DatePicker",
261
447
  },
262
- renderValues
448
+ renderValues,
263
449
  );
264
450
 
451
+ const validationBehavior = () =>
452
+ (stateProps as { validationBehavior?: "aria" | "native" }).validationBehavior ??
453
+ formContext?.validationBehavior ??
454
+ "native";
455
+
265
456
  return (
266
- <DatePickerContext.Provider value={contextValue}>
267
- {/* Also provide DateFieldContext so DateInput/DateSegment work inside DatePicker */}
268
- <DateFieldContext.Provider value={fieldState as unknown as DateFieldState<DateValue>}>
269
- <div
270
- {...pickerAria.groupProps}
271
- class={renderProps.class()}
272
- style={renderProps.style()}
273
- data-disabled={dataAttr(fieldState.isDisabled())}
274
- data-readonly={dataAttr(fieldState.isReadOnly())}
275
- data-required={dataAttr(fieldState.isRequired())}
276
- data-invalid={dataAttr(fieldState.isInvalid())}
277
- data-open={dataAttr(overlayState.isOpen)}
457
+ <DatePickerStateContext.Provider value={fieldState as unknown as DateFieldState<DateValue>}>
458
+ <DatePickerContext.Provider value={contextValue}>
459
+ {/* Also provide DateFieldContext so DateInput/DateSegment work inside DatePicker */}
460
+ <DateFieldContext.Provider
461
+ value={{
462
+ state: fieldState as unknown as DateFieldState<DateValue>,
463
+ aria: {
464
+ labelProps: pickerAria.labelProps,
465
+ inputProps: pickerAria.fieldProps,
466
+ descriptionProps: pickerAria.descriptionProps,
467
+ errorMessageProps: pickerAria.errorMessageProps,
468
+ },
469
+ }}
278
470
  >
279
- {props.children}
280
- </div>
281
- </DateFieldContext.Provider>
282
- </DatePickerContext.Provider>
471
+ <CalendarContext.Provider value={calendarState as unknown as CalendarState<DateValue>}>
472
+ <div
473
+ ref={setFieldRef}
474
+ {...pickerAria.groupProps}
475
+ class={renderProps.class()}
476
+ style={renderProps.style()}
477
+ data-disabled={dataAttr(fieldState.isDisabled())}
478
+ data-readonly={dataAttr(fieldState.isReadOnly())}
479
+ data-required={dataAttr(fieldState.isRequired())}
480
+ data-invalid={dataAttr(isInvalid())}
481
+ data-open={dataAttr(overlayState.isOpen)}
482
+ >
483
+ {props.children}
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>
503
+ </CalendarContext.Provider>
504
+ </DateFieldContext.Provider>
505
+ </DatePickerContext.Provider>
506
+ </DatePickerStateContext.Provider>
283
507
  );
284
508
  }
285
509
 
286
- // ============================================
287
- // DATE PICKER BUTTON COMPONENT
288
- // ============================================
510
+ export function DateRangePicker<T extends DateValue = CalendarDate>(
511
+ props: DateRangePickerProps<T>,
512
+ ): JSX.Element {
513
+ const isHydrated = useIsHydrated();
514
+ return (
515
+ <Show
516
+ when={isHydrated()}
517
+ fallback={
518
+ <div
519
+ class="solidaria-DateRangePicker solidaria-DateRangePicker--placeholder"
520
+ aria-hidden="true"
521
+ />
522
+ }
523
+ >
524
+ <DateRangePickerInner {...props} />
525
+ </Show>
526
+ );
527
+ }
528
+
529
+ function DateRangePickerInner<T extends DateValue = CalendarDate>(
530
+ props: DateRangePickerProps<T>,
531
+ ): JSX.Element {
532
+ const [local, overlayProps, stateProps, rest] = splitProps(
533
+ props,
534
+ ["children", "class", "style", "slot", "shouldCloseOnSelect"],
535
+ ["defaultOpen", "isOpen", "onOpenChange"],
536
+ [
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
+ ],
562
+ );
563
+
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
+
573
+ let triggerRef: HTMLElement | null = null;
574
+ const overlayState = {
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);
595
+ };
596
+
597
+ const calendarState = createRangeCalendarState({
598
+ ...stateProps,
599
+ value: currentRangeValue,
600
+ onChange: (value) => {
601
+ setCommittedRangeValue(value);
602
+ if (local.shouldCloseOnSelect !== false && value?.start && value?.end) {
603
+ setOpen(false);
604
+ }
605
+ },
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
+
679
+ const pickerAria = createDateRangePicker(
680
+ () => ({
681
+ ...(rest as Record<string, unknown>),
682
+ description: (props as { description?: string }).description,
683
+ errorMessage: (props as { errorMessage?: string }).errorMessage,
684
+ }),
685
+ calendarState as unknown as RangeCalendarState<DateValue>,
686
+ overlayState as AriaDatePickerState,
687
+ );
688
+
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
+ };
720
+
721
+ const contextValue: DateRangePickerContextValue = {
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,
727
+ overlayState,
728
+ triggerRef: () => triggerRef,
729
+ setTriggerRef: (element) => {
730
+ if (!element) return;
731
+ if (!triggerRef || !triggerRef.isConnected) triggerRef = element;
732
+ },
733
+ pickerAria,
734
+ };
735
+
736
+ const renderValues = createMemo<DateRangePickerRenderProps>(() => ({
737
+ isDisabled: calendarState.isDisabled(),
738
+ isReadOnly: calendarState.isReadOnly(),
739
+ isRequired: isRequired(),
740
+ isInvalid: isInvalid(),
741
+ isOpen: overlayState.isOpen,
742
+ }));
743
+
744
+ const renderProps = useRenderProps(
745
+ {
746
+ class: local.class,
747
+ style: local.style,
748
+ defaultClassName: "solidaria-DateRangePicker",
749
+ },
750
+ renderValues,
751
+ );
752
+
753
+ return (
754
+ <DateRangePickerStateContext.Provider
755
+ value={calendarState as unknown as RangeCalendarState<DateValue>}
756
+ >
757
+ <DateRangePickerContext.Provider value={contextValue}>
758
+ <RangeCalendarContext.Provider
759
+ value={calendarState as unknown as RangeCalendarState<DateValue>}
760
+ >
761
+ <div
762
+ {...pickerAria.groupProps}
763
+ class={renderProps.class()}
764
+ style={renderProps.style()}
765
+ data-disabled={dataAttr(calendarState.isDisabled())}
766
+ data-readonly={dataAttr(calendarState.isReadOnly())}
767
+ data-required={dataAttr(isRequired())}
768
+ data-invalid={dataAttr(isInvalid())}
769
+ data-open={dataAttr(overlayState.isOpen)}
770
+ >
771
+ {props.children}
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>
795
+ </RangeCalendarContext.Provider>
796
+ </DateRangePickerContext.Provider>
797
+ </DateRangePickerStateContext.Provider>
798
+ );
799
+ }
289
800
 
290
801
  /**
291
802
  * A button that opens the date picker calendar.
@@ -293,33 +804,34 @@ function DatePickerInner<T extends DateValue = CalendarDate>(
293
804
  export function DatePickerButton(props: DatePickerButtonProps): JSX.Element {
294
805
  const context = useDatePickerContext();
295
806
 
296
- // Render props values
297
807
  const renderValues = createMemo<DatePickerButtonRenderProps>(() => ({
298
808
  isDisabled: context.fieldState.isDisabled() || (props.isDisabled ?? false),
299
809
  isOpen: context.overlayState.isOpen,
300
810
  }));
301
811
 
302
- // Resolve render props
303
812
  const renderProps = useRenderProps(
304
813
  {
305
814
  children: props.children,
306
815
  class: props.class,
307
816
  style: props.style,
308
- defaultClassName: 'solidaria-DatePickerButton',
817
+ defaultClassName: "solidaria-DatePickerButton",
309
818
  },
310
- renderValues
819
+ renderValues,
311
820
  );
312
821
 
313
822
  // Determine children content - avoid Show for SSR hydration compatibility
314
823
  const getChildren = () => {
315
- if (typeof props.children === 'function') {
824
+ if (typeof props.children === "function") {
316
825
  return renderProps.renderChildren();
317
826
  }
318
- return props.children ?? '📅';
827
+ return props.children ?? "📅";
319
828
  };
320
829
 
321
830
  return (
322
831
  <button
832
+ ref={(el) => {
833
+ context.setTriggerRef(el);
834
+ }}
323
835
  {...context.pickerAria.buttonProps}
324
836
  class={renderProps.class()}
325
837
  style={renderProps.style()}
@@ -332,9 +844,45 @@ export function DatePickerButton(props: DatePickerButtonProps): JSX.Element {
332
844
  );
333
845
  }
334
846
 
335
- // ============================================
336
- // DATE PICKER CONTENT COMPONENT
337
- // ============================================
847
+ export function DateRangePickerButton(props: DateRangePickerButtonProps): JSX.Element {
848
+ const context = useDateRangePickerContext();
849
+
850
+ const renderValues = createMemo<DatePickerButtonRenderProps>(() => ({
851
+ isDisabled: context.calendarState.isDisabled() || (props.isDisabled ?? false),
852
+ isOpen: context.overlayState.isOpen,
853
+ }));
854
+
855
+ const renderProps = useRenderProps(
856
+ {
857
+ children: props.children,
858
+ class: props.class,
859
+ style: props.style,
860
+ defaultClassName: "solidaria-DateRangePickerButton",
861
+ },
862
+ renderValues,
863
+ );
864
+
865
+ const getChildren = () => {
866
+ if (typeof props.children === "function") {
867
+ return renderProps.renderChildren();
868
+ }
869
+ return props.children ?? "📅";
870
+ };
871
+
872
+ return (
873
+ <button
874
+ ref={(el) => context.setTriggerRef(el)}
875
+ {...context.pickerAria.buttonProps}
876
+ class={renderProps.class()}
877
+ style={renderProps.style()}
878
+ disabled={context.calendarState.isDisabled() || props.isDisabled}
879
+ data-disabled={dataAttr(context.calendarState.isDisabled() || props.isDisabled)}
880
+ data-open={dataAttr(context.overlayState.isOpen)}
881
+ >
882
+ {getChildren()}
883
+ </button>
884
+ );
885
+ }
338
886
 
339
887
  export interface DatePickerContentProps extends SlotProps {
340
888
  /** The children of the component. */
@@ -345,23 +893,256 @@ export interface DatePickerContentProps extends SlotProps {
345
893
  style?: JSX.CSSProperties;
346
894
  }
347
895
 
896
+ export interface DateRangePickerContentProps extends DatePickerContentProps {}
897
+
898
+ export interface DatePickerLabelProps {
899
+ children?: JSX.Element;
900
+ class?: string;
901
+ }
902
+
903
+ export function DatePickerLabel(props: DatePickerLabelProps): JSX.Element {
904
+ const context = useDatePickerContext();
905
+ return (
906
+ <span {...context.pickerAria.labelProps} class={props.class}>
907
+ {props.children}
908
+ </span>
909
+ );
910
+ }
911
+
912
+ export interface DatePickerDescriptionProps {
913
+ children?: JSX.Element;
914
+ class?: string;
915
+ }
916
+
917
+ export function DatePickerDescription(props: DatePickerDescriptionProps): JSX.Element {
918
+ const context = useDatePickerContext();
919
+ return (
920
+ <p {...context.pickerAria.descriptionProps} class={props.class}>
921
+ {props.children}
922
+ </p>
923
+ );
924
+ }
925
+
926
+ export interface DatePickerErrorMessageProps {
927
+ children?: JSX.Element;
928
+ class?: string;
929
+ }
930
+
931
+ export function DatePickerErrorMessage(props: DatePickerErrorMessageProps): JSX.Element {
932
+ const context = useDatePickerContext();
933
+ return (
934
+ <p {...context.pickerAria.errorMessageProps} class={props.class}>
935
+ {props.children}
936
+ </p>
937
+ );
938
+ }
939
+
940
+ export interface DateRangePickerLabelProps {
941
+ children?: JSX.Element;
942
+ class?: string;
943
+ }
944
+
945
+ export function DateRangePickerLabel(props: DateRangePickerLabelProps): JSX.Element {
946
+ const context = useDateRangePickerContext();
947
+ return (
948
+ <span {...context.pickerAria.labelProps} class={props.class}>
949
+ {props.children}
950
+ </span>
951
+ );
952
+ }
953
+
954
+ export interface DateRangePickerDescriptionProps {
955
+ children?: JSX.Element;
956
+ class?: string;
957
+ }
958
+
959
+ export function DateRangePickerDescription(props: DateRangePickerDescriptionProps): JSX.Element {
960
+ const context = useDateRangePickerContext();
961
+ return (
962
+ <p {...context.pickerAria.descriptionProps} class={props.class}>
963
+ {props.children}
964
+ </p>
965
+ );
966
+ }
967
+
968
+ export interface DateRangePickerErrorMessageProps {
969
+ children?: JSX.Element;
970
+ class?: string;
971
+ }
972
+
973
+ export function DateRangePickerErrorMessage(props: DateRangePickerErrorMessageProps): JSX.Element {
974
+ const context = useDateRangePickerContext();
975
+ return (
976
+ <p {...context.pickerAria.errorMessageProps} class={props.class}>
977
+ {props.children}
978
+ </p>
979
+ );
980
+ }
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
+
348
996
  /**
349
997
  * The content area of the date picker (typically contains a Calendar).
350
998
  */
351
999
  export function DatePickerContent(props: DatePickerContentProps): JSX.Element {
352
1000
  const context = useDatePickerContext();
1001
+ const portalContext = useUNSAFE_PortalContext();
1002
+ let contentRef: HTMLDivElement | undefined;
1003
+ const portalContainer = () => portalContext.getContainer?.() ?? undefined;
1004
+
1005
+ const popoverAria = createPopover(
1006
+ {
1007
+ triggerRef: () => context.triggerRef()?.parentElement ?? context.triggerRef(),
1008
+ popoverRef: () => contentRef ?? null,
1009
+ placement: "bottom start",
1010
+ offset: 8,
1011
+ isNonModal: false,
1012
+ isKeyboardDismissDisabled: false,
1013
+ },
1014
+ {
1015
+ isOpen: () => context.overlayState.isOpen,
1016
+ open: context.overlayState.open,
1017
+ close: context.overlayState.close,
1018
+ toggle: context.overlayState.toggle,
1019
+ },
1020
+ );
1021
+
1022
+ createEscapeDismissFallback(() => context.overlayState.isOpen, context.overlayState.close);
1023
+
1024
+ const cleanPopoverProps = () => {
1025
+ const {
1026
+ style: _style,
1027
+ ref: _ref,
1028
+ ...rest
1029
+ } = popoverAria.popoverProps as Record<string, unknown>;
1030
+ return rest;
1031
+ };
1032
+
1033
+ const mergedStyle = (): JSX.CSSProperties => {
1034
+ const popoverStyle = (popoverAria.popoverProps as Record<string, unknown>).style as
1035
+ | JSX.CSSProperties
1036
+ | undefined;
1037
+ return {
1038
+ ...popoverStyle,
1039
+ ...props.style,
1040
+ };
1041
+ };
1042
+
1043
+ // Return focus to trigger when overlay closes
1044
+ createEffect(() => {
1045
+ const open = context.overlayState.isOpen;
1046
+ if (!open) {
1047
+ requestAnimationFrame(() => context.triggerRef()?.focus());
1048
+ }
1049
+ });
353
1050
 
354
1051
  return (
355
1052
  <Show when={context.overlayState.isOpen}>
356
- <div
357
- {...context.pickerAria.dialogProps}
358
- class={props.class ?? 'solidaria-DatePickerContent'}
359
- style={props.style}
360
- >
361
- {props.children}
362
- </div>
1053
+ <Portal mount={portalContainer()}>
1054
+ <FocusScope contain restoreFocus>
1055
+ <div
1056
+ ref={contentRef}
1057
+ {...cleanPopoverProps()}
1058
+ {...context.pickerAria.dialogProps}
1059
+ tabIndex={-1}
1060
+ class={props.class ?? "solidaria-DatePickerContent"}
1061
+ style={mergedStyle()}
1062
+ data-placement={popoverAria.placement() ?? undefined}
1063
+ >
1064
+ {props.children}
1065
+ </div>
1066
+ </FocusScope>
1067
+ </Portal>
363
1068
  </Show>
364
1069
  );
365
1070
  }
366
1071
 
1072
+ export function DateRangePickerContent(props: DateRangePickerContentProps): JSX.Element {
1073
+ const context = useDateRangePickerContext();
1074
+ const portalContext = useUNSAFE_PortalContext();
1075
+ let contentRef: HTMLDivElement | undefined;
1076
+ const portalContainer = () => portalContext.getContainer?.() ?? undefined;
1077
+
1078
+ const popoverAria = createPopover(
1079
+ {
1080
+ triggerRef: () => context.triggerRef()?.parentElement ?? context.triggerRef(),
1081
+ popoverRef: () => contentRef ?? null,
1082
+ placement: "bottom start",
1083
+ offset: 8,
1084
+ isNonModal: false,
1085
+ isKeyboardDismissDisabled: false,
1086
+ },
1087
+ {
1088
+ isOpen: () => context.overlayState.isOpen,
1089
+ open: context.overlayState.open,
1090
+ close: context.overlayState.close,
1091
+ toggle: context.overlayState.toggle,
1092
+ },
1093
+ );
1094
+
1095
+ createEscapeDismissFallback(() => context.overlayState.isOpen, context.overlayState.close);
1096
+
1097
+ const cleanPopoverProps = () => {
1098
+ const {
1099
+ style: _style,
1100
+ ref: _ref,
1101
+ ...rest
1102
+ } = popoverAria.popoverProps as Record<string, unknown>;
1103
+ return rest;
1104
+ };
1105
+
1106
+ const mergedStyle = (): JSX.CSSProperties => {
1107
+ const popoverStyle = (popoverAria.popoverProps as Record<string, unknown>).style as
1108
+ | JSX.CSSProperties
1109
+ | undefined;
1110
+ return {
1111
+ ...popoverStyle,
1112
+ ...props.style,
1113
+ };
1114
+ };
1115
+
1116
+ // Return focus to trigger when overlay closes
1117
+ createEffect(() => {
1118
+ const open = context.overlayState.isOpen;
1119
+ if (!open) {
1120
+ requestAnimationFrame(() => context.triggerRef()?.focus());
1121
+ }
1122
+ });
1123
+
1124
+ return (
1125
+ <Show when={context.overlayState.isOpen}>
1126
+ <Portal mount={portalContainer()}>
1127
+ <FocusScope contain restoreFocus>
1128
+ <div
1129
+ ref={contentRef}
1130
+ {...cleanPopoverProps()}
1131
+ {...context.pickerAria.dialogProps}
1132
+ tabIndex={-1}
1133
+ class={props.class ?? "solidaria-DateRangePickerContent"}
1134
+ style={mergedStyle()}
1135
+ data-placement={popoverAria.placement() ?? undefined}
1136
+ >
1137
+ {props.children}
1138
+ </div>
1139
+ </FocusScope>
1140
+ </Portal>
1141
+ </Show>
1142
+ );
1143
+ }
1144
+
1145
+ export { HiddenDateInput } from "./HiddenDateInput";
1146
+ export type { HiddenDateInputProps } from "./HiddenDateInput";
1147
+
367
1148
  // DatePickerContextValue is already exported at declaration