@proyecto-viviana/solidaria 0.2.4 → 0.2.8

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 (219) hide show
  1. package/LICENSE +21 -0
  2. package/dist/actiongroup/createActionGroup.d.ts +29 -0
  3. package/dist/actiongroup/createActionGroup.d.ts.map +1 -0
  4. package/dist/actiongroup/index.d.ts +2 -0
  5. package/dist/actiongroup/index.d.ts.map +1 -0
  6. package/dist/autocomplete/createAutocomplete.d.ts +6 -2
  7. package/dist/autocomplete/createAutocomplete.d.ts.map +1 -1
  8. package/dist/breadcrumbs/createBreadcrumbs.d.ts +2 -0
  9. package/dist/breadcrumbs/createBreadcrumbs.d.ts.map +1 -1
  10. package/dist/button/createToggleButtonGroup.d.ts +32 -0
  11. package/dist/button/createToggleButtonGroup.d.ts.map +1 -0
  12. package/dist/button/index.d.ts +2 -0
  13. package/dist/button/index.d.ts.map +1 -1
  14. package/dist/calendar/createCalendarCell.d.ts +2 -0
  15. package/dist/calendar/createCalendarCell.d.ts.map +1 -1
  16. package/dist/calendar/createCalendarGrid.d.ts.map +1 -1
  17. package/dist/calendar/createRangeCalendarCell.d.ts +3 -1
  18. package/dist/calendar/createRangeCalendarCell.d.ts.map +1 -1
  19. package/dist/checkbox/createCheckboxGroup.d.ts +5 -1
  20. package/dist/checkbox/createCheckboxGroup.d.ts.map +1 -1
  21. package/dist/collections/index.d.ts +56 -0
  22. package/dist/collections/index.d.ts.map +1 -0
  23. package/dist/color/createColorArea.d.ts.map +1 -1
  24. package/dist/color/createColorSlider.d.ts.map +1 -1
  25. package/dist/color/createColorWheel.d.ts.map +1 -1
  26. package/dist/combobox/createComboBox.d.ts +6 -0
  27. package/dist/combobox/createComboBox.d.ts.map +1 -1
  28. package/dist/datepicker/createDatePicker.d.ts +6 -0
  29. package/dist/datepicker/createDatePicker.d.ts.map +1 -1
  30. package/dist/datepicker/createDateRangePicker.d.ts +40 -0
  31. package/dist/datepicker/createDateRangePicker.d.ts.map +1 -0
  32. package/dist/datepicker/createDateSegment.d.ts +1 -1
  33. package/dist/datepicker/createDateSegment.d.ts.map +1 -1
  34. package/dist/datepicker/createTimeSegment.d.ts +29 -0
  35. package/dist/datepicker/createTimeSegment.d.ts.map +1 -0
  36. package/dist/datepicker/index.d.ts +2 -0
  37. package/dist/datepicker/index.d.ts.map +1 -1
  38. package/dist/disclosure/createDisclosureGroup.d.ts +2 -1
  39. package/dist/disclosure/createDisclosureGroup.d.ts.map +1 -1
  40. package/dist/dnd/createDrag.d.ts.map +1 -1
  41. package/dist/dnd/createDraggableCollection.d.ts +4 -0
  42. package/dist/dnd/createDraggableCollection.d.ts.map +1 -1
  43. package/dist/dnd/createDraggableItem.d.ts.map +1 -1
  44. package/dist/dnd/createDrop.d.ts.map +1 -1
  45. package/dist/dnd/createDroppableCollection.d.ts +32 -1
  46. package/dist/dnd/createDroppableCollection.d.ts.map +1 -1
  47. package/dist/dnd/createDroppableItem.d.ts.map +1 -1
  48. package/dist/dnd/index.d.ts +1 -1
  49. package/dist/dnd/index.d.ts.map +1 -1
  50. package/dist/grid/createGrid.d.ts.map +1 -1
  51. package/dist/gridlist/createGridList.d.ts.map +1 -1
  52. package/dist/index.d.ts +6 -4
  53. package/dist/index.d.ts.map +1 -1
  54. package/dist/index.js +4659 -3452
  55. package/dist/index.js.map +1 -7
  56. package/dist/index.ssr.js +4659 -3452
  57. package/dist/index.ssr.js.map +1 -7
  58. package/dist/interactions/createFocus.d.ts.map +1 -1
  59. package/dist/interactions/createFocusWithin.d.ts.map +1 -1
  60. package/dist/link/createLink.d.ts +10 -0
  61. package/dist/link/createLink.d.ts.map +1 -1
  62. package/dist/listbox/createListBox.d.ts +1 -0
  63. package/dist/listbox/createListBox.d.ts.map +1 -1
  64. package/dist/listbox/createOption.d.ts.map +1 -1
  65. package/dist/menu/createMenu.d.ts +1 -0
  66. package/dist/menu/createMenu.d.ts.map +1 -1
  67. package/dist/meter/createMeter.d.ts.map +1 -1
  68. package/dist/numberfield/createNumberField.d.ts +18 -0
  69. package/dist/numberfield/createNumberField.d.ts.map +1 -1
  70. package/dist/overlays/createModal.d.ts +16 -0
  71. package/dist/overlays/createModal.d.ts.map +1 -1
  72. package/dist/overlays/createOverlay.d.ts.map +1 -1
  73. package/dist/overlays/index.d.ts +1 -1
  74. package/dist/overlays/index.d.ts.map +1 -1
  75. package/dist/popover/createOverlayPosition.d.ts.map +1 -1
  76. package/dist/popover/createPopover.d.ts.map +1 -1
  77. package/dist/progress/createProgressBar.d.ts.map +1 -1
  78. package/dist/radio/createRadioGroup.d.ts +2 -2
  79. package/dist/radio/createRadioGroup.d.ts.map +1 -1
  80. package/dist/searchfield/createSearchField.d.ts.map +1 -1
  81. package/dist/select/createHiddenSelect.d.ts.map +1 -1
  82. package/dist/select/createSelect.d.ts.map +1 -1
  83. package/dist/slider/createSlider.d.ts.map +1 -1
  84. package/dist/table/createTable.d.ts.map +1 -1
  85. package/dist/tabs/createTabs.d.ts +1 -1
  86. package/dist/tabs/createTabs.d.ts.map +1 -1
  87. package/dist/tag/createTag.d.ts.map +1 -1
  88. package/dist/tag/createTagGroup.d.ts.map +1 -1
  89. package/dist/toast/createToast.d.ts +4 -0
  90. package/dist/toast/createToast.d.ts.map +1 -1
  91. package/dist/toast/createToastRegion.d.ts.map +1 -1
  92. package/dist/toolbar/createToolbar.d.ts.map +1 -1
  93. package/dist/tooltip/createTooltipTrigger.d.ts.map +1 -1
  94. package/dist/tree/createTree.d.ts.map +1 -1
  95. package/dist/tree/createTreeItem.d.ts.map +1 -1
  96. package/dist/tree/types.d.ts +4 -0
  97. package/dist/tree/types.d.ts.map +1 -1
  98. package/dist/utils/env.d.ts +1 -1
  99. package/dist/utils/env.d.ts.map +1 -1
  100. package/dist/utils/platform.d.ts.map +1 -1
  101. package/dist/visually-hidden/createVisuallyHidden.d.ts.map +1 -1
  102. package/package.json +8 -6
  103. package/src/actiongroup/createActionGroup.ts +324 -0
  104. package/src/actiongroup/index.ts +8 -0
  105. package/src/autocomplete/createAutocomplete.ts +32 -9
  106. package/src/breadcrumbs/createBreadcrumbs.ts +10 -15
  107. package/src/button/createButton.ts +1 -1
  108. package/src/button/createToggleButtonGroup.ts +128 -0
  109. package/src/button/index.ts +9 -0
  110. package/src/calendar/createCalendarCell.ts +6 -4
  111. package/src/calendar/createCalendarGrid.ts +27 -18
  112. package/src/calendar/createRangeCalendarCell.ts +26 -9
  113. package/src/checkbox/createCheckboxGroup.ts +21 -4
  114. package/src/collections/index.ts +242 -0
  115. package/src/color/createColorArea.ts +380 -314
  116. package/src/color/createColorField.ts +137 -137
  117. package/src/color/createColorSlider.ts +286 -197
  118. package/src/color/createColorSwatch.ts +40 -40
  119. package/src/color/createColorWheel.ts +218 -208
  120. package/src/color/index.ts +24 -24
  121. package/src/color/types.ts +116 -116
  122. package/src/combobox/createComboBox.ts +670 -647
  123. package/src/combobox/index.ts +6 -6
  124. package/src/datepicker/createDatePicker.ts +54 -16
  125. package/src/datepicker/createDateRangePicker.ts +246 -0
  126. package/src/datepicker/createDateSegment.ts +185 -31
  127. package/src/datepicker/createTimeSegment.ts +370 -0
  128. package/src/datepicker/index.ts +14 -0
  129. package/src/dialog/createDialog.ts +120 -120
  130. package/src/dialog/index.ts +2 -2
  131. package/src/dialog/types.ts +19 -19
  132. package/src/disclosure/createDisclosureGroup.ts +5 -2
  133. package/src/dnd/createDrag.ts +224 -209
  134. package/src/dnd/createDraggableCollection.ts +96 -63
  135. package/src/dnd/createDraggableItem.ts +259 -243
  136. package/src/dnd/createDrop.ts +322 -321
  137. package/src/dnd/createDroppableCollection.ts +682 -293
  138. package/src/dnd/createDroppableItem.ts +215 -213
  139. package/src/dnd/index.ts +55 -47
  140. package/src/dnd/types.ts +89 -89
  141. package/src/dnd/utils.ts +294 -294
  142. package/src/focus/createAutoFocus.ts +321 -321
  143. package/src/focus/createFocusRestore.ts +313 -313
  144. package/src/focus/createVirtualFocus.ts +396 -396
  145. package/src/form/createFormValidation.ts +224 -224
  146. package/src/form/index.ts +11 -11
  147. package/src/grid/createGrid.ts +3 -1
  148. package/src/gridlist/createGridList.ts +16 -0
  149. package/src/gridlist/createGridListItem.ts +1 -1
  150. package/src/i18n/NumberFormatter.ts +266 -266
  151. package/src/i18n/createCollator.ts +79 -79
  152. package/src/i18n/createDateFormatter.ts +83 -83
  153. package/src/i18n/createFilter.ts +131 -131
  154. package/src/i18n/createNumberFormatter.ts +52 -52
  155. package/src/i18n/index.ts +40 -40
  156. package/src/i18n/locale.tsx +188 -188
  157. package/src/i18n/utils.ts +99 -99
  158. package/src/index.ts +51 -0
  159. package/src/interactions/createFocus.ts +6 -5
  160. package/src/interactions/createFocusWithin.ts +6 -5
  161. package/src/interactions/createLongPress.ts +174 -174
  162. package/src/interactions/createMove.ts +289 -289
  163. package/src/interactions/createPress.ts +5 -5
  164. package/src/landmark/createLandmark.ts +377 -377
  165. package/src/landmark/index.ts +8 -8
  166. package/src/link/createLink.ts +23 -8
  167. package/src/listbox/createListBox.ts +308 -269
  168. package/src/listbox/createOption.ts +162 -151
  169. package/src/listbox/index.ts +12 -12
  170. package/src/live-announcer/announce.ts +322 -322
  171. package/src/live-announcer/index.ts +9 -9
  172. package/src/menu/createMenu.ts +405 -396
  173. package/src/menu/createMenuItem.ts +149 -149
  174. package/src/menu/createMenuTrigger.ts +88 -88
  175. package/src/menu/index.ts +18 -18
  176. package/src/meter/createMeter.ts +1 -6
  177. package/src/numberfield/createNumberField.ts +311 -268
  178. package/src/numberfield/index.ts +5 -5
  179. package/src/overlays/ariaHideOutside.ts +219 -219
  180. package/src/overlays/createInteractOutside.ts +149 -149
  181. package/src/overlays/createModal.tsx +238 -202
  182. package/src/overlays/createOverlay.ts +165 -155
  183. package/src/overlays/createOverlayTrigger.ts +85 -85
  184. package/src/overlays/createPreventScroll.ts +266 -266
  185. package/src/overlays/index.ts +48 -44
  186. package/src/popover/calculatePosition.ts +6 -6
  187. package/src/popover/createOverlayPosition.ts +7 -4
  188. package/src/popover/createPopover.ts +21 -7
  189. package/src/progress/createProgressBar.ts +6 -1
  190. package/src/radio/createRadioGroup.ts +88 -14
  191. package/src/searchfield/createSearchField.ts +241 -186
  192. package/src/searchfield/index.ts +2 -2
  193. package/src/select/createHiddenSelect.tsx +263 -236
  194. package/src/select/createSelect.ts +373 -395
  195. package/src/select/index.ts +14 -14
  196. package/src/slider/createSlider.ts +364 -349
  197. package/src/slider/index.ts +2 -2
  198. package/src/ssr/index.tsx +370 -370
  199. package/src/table/createTable.ts +3 -1
  200. package/src/table/createTableColumnHeader.ts +1 -1
  201. package/src/table/createTableRow.ts +1 -1
  202. package/src/tabs/createTabs.ts +80 -51
  203. package/src/tag/createTag.ts +135 -6
  204. package/src/tag/createTagGroup.ts +7 -2
  205. package/src/toast/createToast.ts +8 -2
  206. package/src/toast/createToastRegion.ts +0 -1
  207. package/src/toolbar/createToolbar.ts +75 -1
  208. package/src/tooltip/createTooltip.ts +79 -79
  209. package/src/tooltip/createTooltipTrigger.ts +226 -222
  210. package/src/tooltip/index.ts +6 -6
  211. package/src/tree/createTree.ts +261 -246
  212. package/src/tree/createTreeItem.ts +282 -233
  213. package/src/tree/createTreeSelectionCheckbox.ts +68 -68
  214. package/src/tree/index.ts +16 -16
  215. package/src/tree/types.ts +91 -87
  216. package/src/utils/env.ts +55 -54
  217. package/src/utils/platform.ts +16 -6
  218. package/src/visually-hidden/createVisuallyHidden.ts +139 -124
  219. package/src/visually-hidden/index.ts +6 -6
@@ -1,224 +1,224 @@
1
- /**
2
- * createFormValidation hook for solidaria
3
- *
4
- * Connects form validation state to native HTML form validation.
5
- * Handles the invalid event, form reset, and focus management.
6
- *
7
- * Port of react-aria's useFormValidation.
8
- */
9
-
10
- import { type Accessor, createEffect, onCleanup } from 'solid-js';
11
- import {
12
- type FormValidationState,
13
- type ValidationResult,
14
- } from '@proyecto-viviana/solid-stately';
15
- import { setInteractionModality } from '../interactions/createInteractionModality';
16
-
17
- // ============================================
18
- // TYPES
19
- // ============================================
20
-
21
- export type ValidatableElement =
22
- | HTMLInputElement
23
- | HTMLTextAreaElement
24
- | HTMLSelectElement;
25
-
26
- export type ValidationBehavior = 'aria' | 'native';
27
-
28
- export interface FormValidationProps {
29
- /** Validation behavior: 'aria' for realtime, 'native' for on submit. */
30
- validationBehavior?: ValidationBehavior;
31
- /** Custom focus function to call on validation error. */
32
- focus?: () => void;
33
- }
34
-
35
- // ============================================
36
- // HELPERS
37
- // ============================================
38
-
39
- function getValidity(input: ValidatableElement): ValidityState {
40
- // Create a snapshot of the validity state (the native object is live)
41
- const validity = input.validity;
42
- return {
43
- badInput: validity.badInput,
44
- customError: validity.customError,
45
- patternMismatch: validity.patternMismatch,
46
- rangeOverflow: validity.rangeOverflow,
47
- rangeUnderflow: validity.rangeUnderflow,
48
- stepMismatch: validity.stepMismatch,
49
- tooLong: validity.tooLong,
50
- tooShort: validity.tooShort,
51
- typeMismatch: validity.typeMismatch,
52
- valueMissing: validity.valueMissing,
53
- valid: validity.valid,
54
- };
55
- }
56
-
57
- function getNativeValidity(input: ValidatableElement): ValidationResult {
58
- return {
59
- isInvalid: !input.validity.valid,
60
- validationDetails: getValidity(input),
61
- validationErrors: input.validationMessage ? [input.validationMessage] : [],
62
- };
63
- }
64
-
65
- function getFirstInvalidInput(form: HTMLFormElement): ValidatableElement | null {
66
- for (let i = 0; i < form.elements.length; i++) {
67
- const element = form.elements[i] as ValidatableElement;
68
- if (!element.validity.valid) {
69
- return element;
70
- }
71
- }
72
- return null;
73
- }
74
-
75
- // ============================================
76
- // HOOK
77
- // ============================================
78
-
79
- /**
80
- * Connects form validation state to a native HTML form input.
81
- *
82
- * This hook:
83
- * - Sets custom validity on the native input based on validation state
84
- * - Handles the 'invalid' event to commit validation and focus the first invalid input
85
- * - Handles form reset to clear validation state
86
- * - Handles input change to commit validation
87
- *
88
- * @example
89
- * ```tsx
90
- * function MyTextField(props) {
91
- * let inputRef: HTMLInputElement | undefined;
92
- *
93
- * const validationState = createFormValidationState({
94
- * value: props.value,
95
- * validate: props.validate,
96
- * validationBehavior: 'native',
97
- * });
98
- *
99
- * createFormValidation(
100
- * { validationBehavior: 'native' },
101
- * validationState,
102
- * () => inputRef
103
- * );
104
- *
105
- * return (
106
- * <input
107
- * ref={inputRef}
108
- * value={props.value}
109
- * aria-invalid={validationState.displayValidation().isInvalid || undefined}
110
- * />
111
- * );
112
- * }
113
- * ```
114
- */
115
- export function createFormValidation(
116
- props: FormValidationProps,
117
- state: FormValidationState,
118
- ref: Accessor<ValidatableElement | undefined>
119
- ): void {
120
- const validationBehavior = () => props.validationBehavior ?? 'aria';
121
- const focus = () => props.focus;
122
-
123
- // Track whether we should ignore form reset (for React-like programmatic resets)
124
- let isIgnoredReset = false;
125
-
126
- // Set custom validity on the native input
127
- createEffect(() => {
128
- const input = ref();
129
- if (
130
- validationBehavior() === 'native' &&
131
- input &&
132
- !input.disabled
133
- ) {
134
- const realtimeValidation = state.realtimeValidation();
135
- const errorMessage = realtimeValidation.isInvalid
136
- ? realtimeValidation.validationErrors.join(' ') || 'Invalid value.'
137
- : '';
138
- input.setCustomValidity(errorMessage);
139
-
140
- // Prevent default tooltip for validation message
141
- if (!input.hasAttribute('title')) {
142
- input.title = '';
143
- }
144
-
145
- // Update validation with native validity if not already invalid
146
- if (!realtimeValidation.isInvalid) {
147
- state.updateValidation(getNativeValidity(input));
148
- }
149
- }
150
- });
151
-
152
- // Set up event listeners
153
- createEffect(() => {
154
- const input = ref();
155
- if (!input) {
156
- return;
157
- }
158
-
159
- const form = input.form;
160
-
161
- // Handle invalid event
162
- const onInvalid = (e: Event) => {
163
- // Only commit validation if we are not already displaying one
164
- if (!state.displayValidation().isInvalid) {
165
- state.commitValidation();
166
- }
167
-
168
- // Auto focus the first invalid input in a form
169
- if (!e.defaultPrevented && form && getFirstInvalidInput(form) === input) {
170
- const focusFn = focus();
171
- if (focusFn) {
172
- focusFn();
173
- } else {
174
- input.focus();
175
- }
176
- // Always show focus ring
177
- setInteractionModality('keyboard');
178
- }
179
-
180
- // Prevent default browser error UI
181
- e.preventDefault();
182
- };
183
-
184
- // Handle change event
185
- const onChange = () => {
186
- state.commitValidation();
187
- };
188
-
189
- // Handle form reset
190
- const onReset = () => {
191
- if (!isIgnoredReset) {
192
- state.resetValidation();
193
- }
194
- };
195
-
196
- // Patch form.reset to detect programmatic resets
197
- let originalReset: (() => void) | undefined;
198
- if (form) {
199
- originalReset = form.reset.bind(form);
200
- form.reset = () => {
201
- // Ignore programmatic resets outside user events
202
- isIgnoredReset =
203
- !window.event ||
204
- (window.event.type === 'message' &&
205
- window.event.target instanceof MessagePort);
206
- originalReset?.();
207
- isIgnoredReset = false;
208
- };
209
- }
210
-
211
- input.addEventListener('invalid', onInvalid);
212
- input.addEventListener('change', onChange);
213
- form?.addEventListener('reset', onReset);
214
-
215
- onCleanup(() => {
216
- input.removeEventListener('invalid', onInvalid);
217
- input.removeEventListener('change', onChange);
218
- form?.removeEventListener('reset', onReset);
219
- if (form && originalReset) {
220
- form.reset = originalReset;
221
- }
222
- });
223
- });
224
- }
1
+ /**
2
+ * createFormValidation hook for solidaria
3
+ *
4
+ * Connects form validation state to native HTML form validation.
5
+ * Handles the invalid event, form reset, and focus management.
6
+ *
7
+ * Port of react-aria's useFormValidation.
8
+ */
9
+
10
+ import { type Accessor, createEffect, onCleanup } from 'solid-js';
11
+ import {
12
+ type FormValidationState,
13
+ type ValidationResult,
14
+ } from '@proyecto-viviana/solid-stately';
15
+ import { setInteractionModality } from '../interactions/createInteractionModality';
16
+
17
+ // ============================================
18
+ // TYPES
19
+ // ============================================
20
+
21
+ export type ValidatableElement =
22
+ | HTMLInputElement
23
+ | HTMLTextAreaElement
24
+ | HTMLSelectElement;
25
+
26
+ export type ValidationBehavior = 'aria' | 'native';
27
+
28
+ export interface FormValidationProps {
29
+ /** Validation behavior: 'aria' for realtime, 'native' for on submit. */
30
+ validationBehavior?: ValidationBehavior;
31
+ /** Custom focus function to call on validation error. */
32
+ focus?: () => void;
33
+ }
34
+
35
+ // ============================================
36
+ // HELPERS
37
+ // ============================================
38
+
39
+ function getValidity(input: ValidatableElement): ValidityState {
40
+ // Create a snapshot of the validity state (the native object is live)
41
+ const validity = input.validity;
42
+ return {
43
+ badInput: validity.badInput,
44
+ customError: validity.customError,
45
+ patternMismatch: validity.patternMismatch,
46
+ rangeOverflow: validity.rangeOverflow,
47
+ rangeUnderflow: validity.rangeUnderflow,
48
+ stepMismatch: validity.stepMismatch,
49
+ tooLong: validity.tooLong,
50
+ tooShort: validity.tooShort,
51
+ typeMismatch: validity.typeMismatch,
52
+ valueMissing: validity.valueMissing,
53
+ valid: validity.valid,
54
+ };
55
+ }
56
+
57
+ function getNativeValidity(input: ValidatableElement): ValidationResult {
58
+ return {
59
+ isInvalid: !input.validity.valid,
60
+ validationDetails: getValidity(input),
61
+ validationErrors: input.validationMessage ? [input.validationMessage] : [],
62
+ };
63
+ }
64
+
65
+ function getFirstInvalidInput(form: HTMLFormElement): ValidatableElement | null {
66
+ for (let i = 0; i < form.elements.length; i++) {
67
+ const element = form.elements[i] as ValidatableElement;
68
+ if (!element.validity.valid) {
69
+ return element;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+
75
+ // ============================================
76
+ // HOOK
77
+ // ============================================
78
+
79
+ /**
80
+ * Connects form validation state to a native HTML form input.
81
+ *
82
+ * This hook:
83
+ * - Sets custom validity on the native input based on validation state
84
+ * - Handles the 'invalid' event to commit validation and focus the first invalid input
85
+ * - Handles form reset to clear validation state
86
+ * - Handles input change to commit validation
87
+ *
88
+ * @example
89
+ * ```tsx
90
+ * function MyTextField(props) {
91
+ * let inputRef: HTMLInputElement | undefined;
92
+ *
93
+ * const validationState = createFormValidationState({
94
+ * value: props.value,
95
+ * validate: props.validate,
96
+ * validationBehavior: 'native',
97
+ * });
98
+ *
99
+ * createFormValidation(
100
+ * { validationBehavior: 'native' },
101
+ * validationState,
102
+ * () => inputRef
103
+ * );
104
+ *
105
+ * return (
106
+ * <input
107
+ * ref={inputRef}
108
+ * value={props.value}
109
+ * aria-invalid={validationState.displayValidation().isInvalid || undefined}
110
+ * />
111
+ * );
112
+ * }
113
+ * ```
114
+ */
115
+ export function createFormValidation(
116
+ props: FormValidationProps,
117
+ state: FormValidationState,
118
+ ref: Accessor<ValidatableElement | undefined>
119
+ ): void {
120
+ const validationBehavior = () => props.validationBehavior ?? 'aria';
121
+ const focus = () => props.focus;
122
+
123
+ // Track whether we should ignore form reset (for React-like programmatic resets)
124
+ let isIgnoredReset = false;
125
+
126
+ // Set custom validity on the native input
127
+ createEffect(() => {
128
+ const input = ref();
129
+ if (
130
+ validationBehavior() === 'native' &&
131
+ input &&
132
+ !input.disabled
133
+ ) {
134
+ const realtimeValidation = state.realtimeValidation();
135
+ const errorMessage = realtimeValidation.isInvalid
136
+ ? realtimeValidation.validationErrors.join(' ') || 'Invalid value.'
137
+ : '';
138
+ input.setCustomValidity(errorMessage);
139
+
140
+ // Prevent default tooltip for validation message
141
+ if (!input.hasAttribute('title')) {
142
+ input.title = '';
143
+ }
144
+
145
+ // Update validation with native validity if not already invalid
146
+ if (!realtimeValidation.isInvalid) {
147
+ state.updateValidation(getNativeValidity(input));
148
+ }
149
+ }
150
+ });
151
+
152
+ // Set up event listeners
153
+ createEffect(() => {
154
+ const input = ref();
155
+ if (!input) {
156
+ return;
157
+ }
158
+
159
+ const form = input.form;
160
+
161
+ // Handle invalid event
162
+ const onInvalid = (e: Event) => {
163
+ // Only commit validation if we are not already displaying one
164
+ if (!state.displayValidation().isInvalid) {
165
+ state.commitValidation();
166
+ }
167
+
168
+ // Auto focus the first invalid input in a form
169
+ if (!e.defaultPrevented && form && getFirstInvalidInput(form) === input) {
170
+ const focusFn = focus();
171
+ if (focusFn) {
172
+ focusFn();
173
+ } else {
174
+ input.focus();
175
+ }
176
+ // Always show focus ring
177
+ setInteractionModality('keyboard');
178
+ }
179
+
180
+ // Prevent default browser error UI
181
+ e.preventDefault();
182
+ };
183
+
184
+ // Handle change event
185
+ const onChange = () => {
186
+ state.commitValidation();
187
+ };
188
+
189
+ // Handle form reset
190
+ const onReset = () => {
191
+ if (!isIgnoredReset) {
192
+ state.resetValidation();
193
+ }
194
+ };
195
+
196
+ // Patch form.reset to detect programmatic resets
197
+ let originalReset: (() => void) | undefined;
198
+ if (form) {
199
+ originalReset = form.reset.bind(form);
200
+ form.reset = () => {
201
+ // Ignore programmatic resets outside user events
202
+ isIgnoredReset =
203
+ !window.event ||
204
+ (window.event.type === 'message' &&
205
+ window.event.target instanceof MessagePort);
206
+ originalReset?.();
207
+ isIgnoredReset = false;
208
+ };
209
+ }
210
+
211
+ input.addEventListener('invalid', onInvalid);
212
+ input.addEventListener('change', onChange);
213
+ form?.addEventListener('reset', onReset);
214
+
215
+ onCleanup(() => {
216
+ input.removeEventListener('invalid', onInvalid);
217
+ input.removeEventListener('change', onChange);
218
+ form?.removeEventListener('reset', onReset);
219
+ if (form && originalReset) {
220
+ form.reset = originalReset;
221
+ }
222
+ });
223
+ });
224
+ }
package/src/form/index.ts CHANGED
@@ -1,11 +1,11 @@
1
- export {
2
- createFormValidation,
3
- type FormValidationProps,
4
- type ValidatableElement,
5
- type ValidationBehavior,
6
- } from './createFormValidation';
7
-
8
- export {
9
- createFormReset,
10
- type FormResetOptions,
11
- } from './createFormReset';
1
+ export {
2
+ createFormValidation,
3
+ type FormValidationProps,
4
+ type ValidatableElement,
5
+ type ValidationBehavior,
6
+ } from './createFormValidation';
7
+
8
+ export {
9
+ createFormReset,
10
+ type FormResetOptions,
11
+ } from './createFormReset';
@@ -9,6 +9,7 @@ import { createId } from '@proyecto-viviana/solid-stately';
9
9
  import type { GridState, GridCollection, Key } from '@proyecto-viviana/solid-stately';
10
10
  import type { GridProps, GridAria, KeyboardDelegate } from './types';
11
11
  import { GridKeyboardDelegate } from './GridKeyboardDelegate';
12
+ import { useLocale } from '../i18n';
12
13
 
13
14
  // Global map to store grid metadata for child components
14
15
  const gridMap = new WeakMap<
@@ -37,6 +38,7 @@ export function createGrid<T extends object>(
37
38
  ref: Accessor<HTMLElement | null>
38
39
  ): GridAria {
39
40
  const id = createId(props().id);
41
+ const locale = useLocale();
40
42
 
41
43
  // Track focused state
42
44
  const [_isFocused, setIsFocused] = createSignal(false);
@@ -55,7 +57,7 @@ export function createGrid<T extends object>(
55
57
  disabledKeys: s.disabledKeys,
56
58
  ref,
57
59
  focusMode: p.focusMode ?? 'row',
58
- direction: 'ltr', // TODO: get from locale
60
+ direction: locale().direction,
59
61
  });
60
62
  });
61
63
 
@@ -123,6 +123,22 @@ export function createGridList<T extends object, C extends GridCollection<T> = G
123
123
  }
124
124
  break;
125
125
  }
126
+ case ' ':
127
+ case 'Space':
128
+ case 'Spacebar': {
129
+ if (focusedKey != null && s.selectionMode !== 'none' && !s.isDisabled(focusedKey)) {
130
+ e.preventDefault();
131
+ s.toggleSelection(focusedKey);
132
+ }
133
+ break;
134
+ }
135
+ case 'Enter': {
136
+ if (focusedKey != null && !s.isDisabled(focusedKey)) {
137
+ e.preventDefault();
138
+ p.onAction?.(focusedKey);
139
+ }
140
+ break;
141
+ }
126
142
  case 'Escape': {
127
143
  if (s.selectionMode !== 'none') {
128
144
  e.preventDefault();
@@ -106,7 +106,7 @@ export function createGridListItem<T extends object, C extends GridCollection<T>
106
106
  p.onAction();
107
107
  }
108
108
  }
109
- } else if (e.key === ' ') {
109
+ } else if (e.key === ' ' || e.key === 'Space' || e.key === 'Spacebar') {
110
110
  // Space toggles selection
111
111
  if (s.selectionMode !== 'none') {
112
112
  e.preventDefault();