@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
package/src/TimeField.tsx CHANGED
@@ -14,18 +14,20 @@ import {
14
14
  useContext,
15
15
  For,
16
16
  Show,
17
- } from 'solid-js';
17
+ } from "solid-js";
18
18
  import {
19
19
  createTimeField,
20
+ createTimeSegment,
20
21
  type AriaTimeFieldProps,
21
- } from '@proyecto-viviana/solidaria';
22
+ } from "@proyecto-viviana/solidaria";
22
23
  import {
23
24
  createTimeFieldState,
25
+ access,
24
26
  type TimeFieldState,
25
27
  type TimeFieldStateProps,
26
28
  type TimeSegment as TimeSegmentType,
27
29
  type TimeValue,
28
- } from '@proyecto-viviana/solid-stately';
30
+ } from "@proyecto-viviana/solid-stately";
29
31
  import {
30
32
  type RenderChildren,
31
33
  type ClassNameOrFunction,
@@ -34,11 +36,9 @@ import {
34
36
  useRenderProps,
35
37
  dataAttr,
36
38
  useIsHydrated,
37
- } from './utils';
38
-
39
- // ============================================
40
- // TYPES
41
- // ============================================
39
+ } from "./utils";
40
+ import { HiddenTimeInput } from "./HiddenTimeInput";
41
+ import { FormContext, type FormProps } from "./Form";
42
42
 
43
43
  export interface TimeFieldRenderProps {
44
44
  /** Whether the field is disabled. */
@@ -52,8 +52,9 @@ export interface TimeFieldRenderProps {
52
52
  }
53
53
 
54
54
  export interface TimeFieldProps<T extends TimeValue = TimeValue>
55
- extends Omit<AriaTimeFieldProps, 'id' | 'isDisabled' | 'isReadOnly' | 'isRequired'>,
56
- Omit<TimeFieldStateProps<T>, 'locale'>,
55
+ extends
56
+ Omit<AriaTimeFieldProps, "id" | "isDisabled" | "isReadOnly" | "isRequired">,
57
+ Omit<TimeFieldStateProps<T>, "locale">,
57
58
  SlotProps {
58
59
  /** The children of the component. */
59
60
  children?: JSX.Element | ((segment: TimeSegmentType) => JSX.Element);
@@ -89,7 +90,7 @@ export interface TimeSegmentRenderProps {
89
90
  /** Whether the segment is a placeholder. */
90
91
  isPlaceholder: boolean;
91
92
  /** The segment type. */
92
- type: TimeSegmentType['type'];
93
+ type: TimeSegmentType["type"];
93
94
  /** The text to display. */
94
95
  text: string;
95
96
  }
@@ -105,23 +106,77 @@ export interface TimeSegmentProps extends SlotProps {
105
106
  style?: StyleOrFunction<TimeSegmentRenderProps>;
106
107
  }
107
108
 
108
- // ============================================
109
- // CONTEXT
110
- // ============================================
109
+ export interface TimeFieldContextValue {
110
+ state: TimeFieldState<TimeValue>;
111
+ aria: {
112
+ labelProps: Record<string, unknown>;
113
+ inputProps: Record<string, unknown>;
114
+ descriptionProps: Record<string, unknown>;
115
+ errorMessageProps: Record<string, unknown>;
116
+ };
117
+ }
111
118
 
112
- export const TimeFieldContext = createContext<TimeFieldState<TimeValue> | null>(null);
119
+ export const TimeFieldContext = createContext<TimeFieldContextValue | null>(null);
120
+ export const TimeFieldStateContext = createContext<TimeFieldState<TimeValue> | null>(null);
113
121
 
114
- export function useTimeFieldContext(): TimeFieldState<TimeValue> {
122
+ function withFormValidationBehavior<P extends object>(props: P, formContext: FormProps | null): P {
123
+ if (!formContext?.validationBehavior) {
124
+ return props;
125
+ }
126
+
127
+ return new Proxy(props, {
128
+ get(target, property, receiver) {
129
+ const localValue = Reflect.get(target, property, receiver);
130
+ if (property === "validationBehavior" && localValue === undefined) {
131
+ return formContext.validationBehavior;
132
+ }
133
+
134
+ return localValue;
135
+ },
136
+ has(target, property) {
137
+ return (
138
+ Reflect.has(target, property) ||
139
+ (property === "validationBehavior" && formContext.validationBehavior !== undefined)
140
+ );
141
+ },
142
+ ownKeys(target) {
143
+ const keys = new Set(Reflect.ownKeys(target));
144
+ if (formContext.validationBehavior !== undefined) {
145
+ keys.add("validationBehavior");
146
+ }
147
+
148
+ return Array.from(keys);
149
+ },
150
+ getOwnPropertyDescriptor(target, property) {
151
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
152
+ if (descriptor) {
153
+ return descriptor;
154
+ }
155
+
156
+ if (property === "validationBehavior" && formContext.validationBehavior !== undefined) {
157
+ return {
158
+ enumerable: true,
159
+ configurable: true,
160
+ get: () => formContext.validationBehavior,
161
+ };
162
+ }
163
+
164
+ return undefined;
165
+ },
166
+ });
167
+ }
168
+
169
+ function useTimeFieldContextValue(): TimeFieldContextValue {
115
170
  const context = useContext(TimeFieldContext);
116
171
  if (!context) {
117
- throw new Error('TimeField components must be used within a TimeField');
172
+ throw new Error("TimeField components must be used within a TimeField");
118
173
  }
119
174
  return context;
120
175
  }
121
176
 
122
- // ============================================
123
- // TIME FIELD COMPONENT
124
- // ============================================
177
+ export function useTimeFieldContext(): TimeFieldState<TimeValue> {
178
+ return useTimeFieldContextValue().state;
179
+ }
125
180
 
126
181
  /**
127
182
  * A time field allows users to enter and edit time values using a keyboard.
@@ -136,57 +191,75 @@ export function useTimeFieldContext(): TimeFieldState<TimeValue> {
136
191
  * </TimeField>
137
192
  * ```
138
193
  */
139
- export function TimeField<T extends TimeValue = TimeValue>(
140
- props: TimeFieldProps<T>
141
- ): JSX.Element {
194
+ export function TimeField<T extends TimeValue = TimeValue>(props: TimeFieldProps<T>): JSX.Element {
142
195
  // Use hydration-safe pattern for client-only rendering
143
196
  const isHydrated = useIsHydrated();
197
+ const formContext = useContext(FormContext);
144
198
 
145
199
  return (
146
200
  <Show
147
201
  when={isHydrated()}
148
- fallback={<div class="solidaria-TimeField solidaria-TimeField--placeholder" aria-hidden="true" />}
202
+ fallback={
203
+ <div class="solidaria-TimeField solidaria-TimeField--placeholder" aria-hidden="true" />
204
+ }
149
205
  >
150
- <TimeFieldInner {...props} />
206
+ <TimeFieldInner {...props} __formContext={formContext} />
151
207
  </Show>
152
208
  );
153
209
  }
154
210
 
211
+ type TimeFieldInnerProps<T extends TimeValue = TimeValue> = TimeFieldProps<T> & {
212
+ __formContext?: FormProps | null;
213
+ };
214
+
155
215
  /**
156
216
  * Internal TimeField component that renders after client mount.
157
217
  */
158
218
  function TimeFieldInner<T extends TimeValue = TimeValue>(
159
- props: TimeFieldProps<T>
219
+ props: TimeFieldInnerProps<T>,
160
220
  ): JSX.Element {
221
+ const formContext = props.__formContext ?? useContext(FormContext);
222
+ const mergedProps = withFormValidationBehavior(props, formContext);
161
223
  const [local, stateProps, rest] = splitProps(
162
- props,
163
- ['children', 'class', 'style', 'slot'],
224
+ mergedProps,
225
+ ["children", "class", "style", "slot", "__formContext"],
164
226
  [
165
- 'value',
166
- 'defaultValue',
167
- 'onChange',
168
- 'minValue',
169
- 'maxValue',
170
- 'isDisabled',
171
- 'isReadOnly',
172
- 'isRequired',
173
- 'locale',
174
- 'granularity',
175
- 'hourCycle',
176
- 'validationState',
177
- 'placeholderValue',
178
- ]
227
+ "value",
228
+ "defaultValue",
229
+ "onChange",
230
+ "minValue",
231
+ "maxValue",
232
+ "isInvalid",
233
+ "isDisabled",
234
+ "isReadOnly",
235
+ "isRequired",
236
+ "locale",
237
+ "granularity",
238
+ "hourCycle",
239
+ "shouldForceLeadingZeros",
240
+ "validationState",
241
+ "validationBehavior",
242
+ "validate",
243
+ "description",
244
+ "errorMessage",
245
+ "placeholderValue",
246
+ ],
179
247
  );
180
248
 
181
249
  const [fieldRef, setFieldRef] = createSignal<HTMLDivElement | null>(null);
182
250
 
183
- // Create time field state
184
251
  const state = createTimeFieldState(stateProps);
185
252
 
186
- // Create time field ARIA props
187
- const fieldAria = createTimeField(rest, state as unknown as TimeFieldState<TimeValue>, fieldRef);
253
+ const fieldAria = createTimeField(
254
+ () => ({
255
+ ...(rest as Record<string, unknown>),
256
+ description: stateProps.description,
257
+ errorMessage: stateProps.errorMessage,
258
+ }),
259
+ state as unknown as TimeFieldState<TimeValue>,
260
+ fieldRef,
261
+ );
188
262
 
189
- // Render props values
190
263
  const renderValues = createMemo<TimeFieldRenderProps>(() => ({
191
264
  isDisabled: state.isDisabled(),
192
265
  isReadOnly: state.isReadOnly(),
@@ -194,64 +267,92 @@ function TimeFieldInner<T extends TimeValue = TimeValue>(
194
267
  isInvalid: state.isInvalid(),
195
268
  }));
196
269
 
197
- // Resolve render props
198
270
  const renderProps = useRenderProps(
199
271
  {
200
272
  class: local.class,
201
273
  style: local.style,
202
- defaultClassName: 'solidaria-TimeField',
274
+ defaultClassName: "solidaria-TimeField",
203
275
  },
204
- renderValues
276
+ renderValues,
205
277
  );
206
278
 
279
+ const validationBehavior = () =>
280
+ (stateProps as { validationBehavior?: "aria" | "native" }).validationBehavior ??
281
+ formContext?.validationBehavior ??
282
+ "native";
283
+
207
284
  return (
208
- <TimeFieldContext.Provider value={state as unknown as TimeFieldState<TimeValue>}>
209
- <div
210
- ref={setFieldRef}
211
- {...fieldAria.fieldProps}
212
- class={renderProps.class()}
213
- style={renderProps.style()}
214
- data-disabled={dataAttr(state.isDisabled())}
215
- data-readonly={dataAttr(state.isReadOnly())}
216
- data-required={dataAttr(state.isRequired())}
217
- data-invalid={dataAttr(state.isInvalid())}
285
+ <TimeFieldStateContext.Provider value={state as unknown as TimeFieldState<TimeValue>}>
286
+ <TimeFieldContext.Provider
287
+ value={{
288
+ state: state as unknown as TimeFieldState<TimeValue>,
289
+ aria: {
290
+ labelProps: fieldAria.labelProps,
291
+ inputProps: fieldAria.inputProps,
292
+ descriptionProps: fieldAria.descriptionProps,
293
+ errorMessageProps: fieldAria.errorMessageProps,
294
+ },
295
+ }}
218
296
  >
219
- {props.children as JSX.Element}
220
- </div>
221
- </TimeFieldContext.Provider>
297
+ <div
298
+ ref={setFieldRef}
299
+ {...fieldAria.fieldProps}
300
+ class={renderProps.class()}
301
+ style={renderProps.style()}
302
+ data-disabled={dataAttr(state.isDisabled())}
303
+ data-readonly={dataAttr(state.isReadOnly())}
304
+ data-required={dataAttr(state.isRequired())}
305
+ data-invalid={dataAttr(state.isInvalid())}
306
+ >
307
+ {local.children as JSX.Element}
308
+ </div>
309
+ <Show when={(rest as Record<string, unknown>).name}>
310
+ <HiddenTimeInput
311
+ name={(rest as Record<string, unknown>).name as string | undefined}
312
+ form={(rest as Record<string, unknown>).form as string | undefined}
313
+ value={state.value()}
314
+ autoComplete={(rest as Record<string, unknown>).autoComplete as string | undefined}
315
+ isDisabled={state.isDisabled()}
316
+ isRequired={state.isRequired()}
317
+ validationBehavior={validationBehavior()}
318
+ validationState={state}
319
+ focus={() => {
320
+ fieldRef()?.querySelector<HTMLElement>('[role="spinbutton"]')?.focus();
321
+ }}
322
+ minValue={access(stateProps.minValue) as TimeValue | undefined}
323
+ maxValue={access(stateProps.maxValue) as TimeValue | undefined}
324
+ granularity={state.granularity}
325
+ />
326
+ </Show>
327
+ </TimeFieldContext.Provider>
328
+ </TimeFieldStateContext.Provider>
222
329
  );
223
330
  }
224
331
 
225
- // ============================================
226
- // TIME INPUT COMPONENT
227
- // ============================================
228
-
229
332
  /**
230
333
  * The input area containing time segments.
231
334
  */
232
335
  export function TimeInput(props: TimeInputProps): JSX.Element {
233
- const state = useTimeFieldContext();
336
+ const { state, aria } = useTimeFieldContextValue();
234
337
  const [isFocused, setIsFocused] = createSignal(false);
235
338
 
236
- // Render props values
237
339
  const renderValues = createMemo<TimeInputRenderProps>(() => ({
238
340
  isDisabled: state.isDisabled(),
239
341
  isFocused: isFocused(),
240
342
  }));
241
343
 
242
- // Resolve render props
243
344
  const renderProps = useRenderProps(
244
345
  {
245
346
  class: props.class,
246
347
  style: props.style,
247
- defaultClassName: 'solidaria-TimeInput',
348
+ defaultClassName: "solidaria-TimeInput",
248
349
  },
249
- renderValues
350
+ renderValues,
250
351
  );
251
352
 
252
353
  return (
253
354
  <div
254
- role="presentation"
355
+ {...aria.inputProps}
255
356
  class={renderProps.class()}
256
357
  style={renderProps.style()}
257
358
  data-disabled={dataAttr(state.isDisabled())}
@@ -259,172 +360,59 @@ export function TimeInput(props: TimeInputProps): JSX.Element {
259
360
  onFocusIn={() => setIsFocused(true)}
260
361
  onFocusOut={() => setIsFocused(false)}
261
362
  >
262
- <For each={state.segments()}>
263
- {(segment) => props.children?.(segment)}
264
- </For>
363
+ <For each={state.segments()}>{(segment) => props.children?.(segment)}</For>
265
364
  </div>
266
365
  );
267
366
  }
268
367
 
269
- // ============================================
270
- // TIME SEGMENT COMPONENT
271
- // ============================================
272
-
273
368
  /**
274
369
  * A segment of a time field (hour, minute, second, AM/PM).
275
370
  */
276
371
  export function TimeSegment(props: TimeSegmentProps): JSX.Element {
277
372
  const state = useTimeFieldContext();
278
- const [_segmentRef, setSegmentRef] = createSignal<HTMLDivElement | null>(null);
279
-
280
- // Create segment ARIA props
281
- // We use a simplified version for time segments
282
- const [isFocused, setIsFocused] = createSignal(false);
283
- const [enteredKeys, setEnteredKeys] = createSignal('');
284
-
285
- const isEditable = createMemo(() => {
286
- const seg = props.segment;
287
- return seg.isEditable && !state.isDisabled() && !state.isReadOnly();
288
- });
289
-
290
- const handleKeyDown = (e: KeyboardEvent) => {
291
- if (!isEditable()) return;
292
-
293
- const seg = props.segment;
294
- const type = seg.type;
295
-
296
- if (type === 'literal') return;
297
-
298
- switch (e.key) {
299
- case 'ArrowUp':
300
- e.preventDefault();
301
- state.incrementSegment(type);
302
- break;
303
- case 'ArrowDown':
304
- e.preventDefault();
305
- state.decrementSegment(type);
306
- break;
307
- case 'Backspace':
308
- case 'Delete':
309
- e.preventDefault();
310
- state.clearSegment(type);
311
- setEnteredKeys('');
312
- break;
313
- default:
314
- if (/^\d$/.test(e.key)) {
315
- e.preventDefault();
316
- const newKeys = enteredKeys() + e.key;
317
- const numValue = parseInt(newKeys, 10);
318
- const maxValue = seg.maxValue ?? 59;
319
- const minValue = seg.minValue ?? 0;
320
-
321
- if (numValue <= maxValue) {
322
- state.setSegment(type, numValue);
323
- if (numValue * 10 > maxValue || newKeys.length >= 2) {
324
- setEnteredKeys('');
325
- } else {
326
- setEnteredKeys(newKeys);
327
- }
328
- } else {
329
- const singleValue = parseInt(e.key, 10);
330
- if (singleValue >= minValue && singleValue <= maxValue) {
331
- state.setSegment(type, singleValue);
332
- }
333
- setEnteredKeys(e.key);
334
- }
335
- }
336
- break;
337
- }
338
- };
373
+ const [segmentRef, setSegmentRef] = createSignal<HTMLDivElement | null>(null);
339
374
 
340
- const handleFocus = () => {
341
- setIsFocused(true);
342
- setEnteredKeys('');
343
- };
344
-
345
- const handleBlur = () => {
346
- setIsFocused(false);
347
- setEnteredKeys('');
348
- };
349
-
350
- // Segment props
351
- const segmentProps = createMemo(() => {
352
- const seg = props.segment;
353
- const type = seg.type;
354
-
355
- if (type === 'literal') {
356
- return {
357
- 'aria-hidden': true,
358
- };
359
- }
360
-
361
- return {
362
- role: 'spinbutton' as const,
363
- tabIndex: isEditable() ? 0 : -1,
364
- 'aria-label': getTimeSegmentLabel(type),
365
- 'aria-valuenow': seg.value,
366
- 'aria-valuemin': seg.minValue,
367
- 'aria-valuemax': seg.maxValue,
368
- 'aria-valuetext': seg.isPlaceholder ? seg.placeholder : seg.text,
369
- 'aria-readonly': state.isReadOnly() || undefined,
370
- 'aria-disabled': state.isDisabled() || undefined,
371
- 'aria-invalid': state.isInvalid() || undefined,
372
- contentEditable: isEditable(),
373
- inputMode: 'numeric' as const,
374
- autoCorrect: 'off',
375
- enterKeyHint: 'next' as const,
376
- spellCheck: false,
377
- onKeyDown: handleKeyDown,
378
- onFocus: handleFocus,
379
- onBlur: handleBlur,
380
- onMouseDown: (e: MouseEvent) => {
381
- e.preventDefault();
382
- },
383
- };
384
- });
385
-
386
- const text = createMemo(() => {
387
- const seg = props.segment;
388
- return seg.isPlaceholder ? seg.placeholder : seg.text;
389
- });
375
+ const segmentAria = createTimeSegment(
376
+ { segment: props.segment },
377
+ state as unknown as TimeFieldState,
378
+ segmentRef,
379
+ );
390
380
 
391
- // Render props values
392
381
  const renderValues = createMemo<TimeSegmentRenderProps>(() => ({
393
- isFocused: isFocused(),
394
- isEditable: isEditable(),
395
- isPlaceholder: props.segment.isPlaceholder,
382
+ isFocused: segmentAria.isFocused,
383
+ isEditable: segmentAria.isEditable,
384
+ isPlaceholder: segmentAria.isPlaceholder,
396
385
  type: props.segment.type,
397
- text: text(),
386
+ text: segmentAria.text,
398
387
  }));
399
388
 
400
- // Resolve render props
401
389
  const renderProps = useRenderProps(
402
390
  {
403
391
  children: props.children,
404
392
  class: props.class,
405
393
  style: props.style,
406
- defaultClassName: 'solidaria-TimeSegment',
394
+ defaultClassName: "solidaria-TimeSegment",
407
395
  },
408
- renderValues
396
+ renderValues,
409
397
  );
410
398
 
411
399
  // Determine children content - avoid Show for SSR hydration compatibility
412
400
  const getChildren = () => {
413
- if (typeof props.children === 'function') {
401
+ if (typeof props.children === "function") {
414
402
  return renderProps.renderChildren();
415
403
  }
416
- return text();
404
+ return segmentAria.text;
417
405
  };
418
406
 
419
407
  return (
420
408
  <div
421
409
  ref={setSegmentRef}
422
- {...segmentProps()}
410
+ {...segmentAria.segmentProps}
423
411
  class={renderProps.class()}
424
412
  style={renderProps.style()}
425
- data-focused={dataAttr(isFocused())}
426
- data-editable={dataAttr(isEditable())}
427
- data-placeholder={dataAttr(props.segment.isPlaceholder)}
413
+ data-focused={dataAttr(segmentAria.isFocused)}
414
+ data-editable={dataAttr(segmentAria.isEditable)}
415
+ data-placeholder={dataAttr(segmentAria.isPlaceholder)}
428
416
  data-type={props.segment.type}
429
417
  >
430
418
  {getChildren()}
@@ -432,24 +420,46 @@ export function TimeSegment(props: TimeSegmentProps): JSX.Element {
432
420
  );
433
421
  }
434
422
 
435
- // ============================================
436
- // HELPER FUNCTIONS
437
- // ============================================
438
-
439
- function getTimeSegmentLabel(type: TimeSegmentType['type']): string {
440
- switch (type) {
441
- case 'hour':
442
- return 'Hour';
443
- case 'minute':
444
- return 'Minute';
445
- case 'second':
446
- return 'Second';
447
- case 'dayPeriod':
448
- return 'AM/PM';
449
- default:
450
- return '';
451
- }
423
+ export interface TimeFieldLabelProps {
424
+ children?: JSX.Element;
425
+ class?: string;
426
+ }
427
+
428
+ export function TimeFieldLabel(props: TimeFieldLabelProps): JSX.Element {
429
+ const { aria } = useTimeFieldContextValue();
430
+ return (
431
+ <span {...aria.labelProps} class={props.class}>
432
+ {props.children}
433
+ </span>
434
+ );
435
+ }
436
+
437
+ export interface TimeFieldDescriptionProps {
438
+ children?: JSX.Element;
439
+ class?: string;
440
+ }
441
+
442
+ export function TimeFieldDescription(props: TimeFieldDescriptionProps): JSX.Element {
443
+ const { aria } = useTimeFieldContextValue();
444
+ return (
445
+ <p {...aria.descriptionProps} class={props.class}>
446
+ {props.children}
447
+ </p>
448
+ );
449
+ }
450
+
451
+ export interface TimeFieldErrorMessageProps {
452
+ children?: JSX.Element;
453
+ class?: string;
454
+ }
455
+
456
+ export function TimeFieldErrorMessage(props: TimeFieldErrorMessageProps): JSX.Element {
457
+ const { aria } = useTimeFieldContextValue();
458
+ return (
459
+ <p {...aria.errorMessageProps} class={props.class}>
460
+ {props.children}
461
+ </p>
462
+ );
452
463
  }
453
464
 
454
- // Re-export types
455
465
  export type { TimeFieldState, TimeSegmentType, TimeValue };