@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,236 +1,263 @@
1
- /**
2
- * Provides a hidden native select element for form integration.
3
- * Based on @react-aria/select useHiddenSelect.
4
- */
5
-
6
- import { type JSX, type Accessor, For, createEffect, onCleanup } from 'solid-js';
7
- import { access, type MaybeAccessor } from '../utils/reactivity';
8
- import { createFormReset } from '../form/createFormReset';
9
- import { createFormValidation } from '../form/createFormValidation';
10
- import type { SelectState, Key, FormValidationState } from '@proyecto-viviana/solid-stately';
11
-
12
- export type ValidationBehavior = 'aria' | 'native';
13
-
14
- export interface AriaHiddenSelectProps<T> {
15
- /** The state object for the select. */
16
- state: SelectState<T>;
17
- /** The name attribute for the hidden select. */
18
- name?: string;
19
- /** Whether the select is disabled. */
20
- isDisabled?: boolean;
21
- /** Whether the select is required. */
22
- isRequired?: boolean;
23
- /** Describes the type of autocomplete functionality the select should provide. */
24
- autoComplete?: string;
25
- /** The `form` attribute to associate the select with a form by ID. */
26
- form?: string;
27
- /** Validation behavior: 'aria' for realtime, 'native' for on submit. */
28
- validationBehavior?: ValidationBehavior;
29
- /** A ref to the trigger element for focus on validation error. */
30
- triggerRef?: Accessor<HTMLElement | null>;
31
- /** Form validation state (optional, for native validation). */
32
- validationState?: FormValidationState;
33
- }
34
-
35
- export interface HiddenSelectAria {
36
- /** Props for the container element. */
37
- containerProps: JSX.HTMLAttributes<HTMLDivElement>;
38
- /** Props for the hidden select element. */
39
- selectProps: JSX.SelectHTMLAttributes<HTMLSelectElement>;
40
- /** Props for the hidden input element (for form submission). */
41
- inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
42
- }
43
-
44
- /**
45
- * Provides the accessibility implementation for a hidden select.
46
- * This is used for native form submission and accessibility on mobile devices.
47
- */
48
- export function createHiddenSelect<T>(
49
- props: MaybeAccessor<AriaHiddenSelectProps<T>>
50
- ): HiddenSelectAria {
51
- const getProps = () => access(props);
52
-
53
- // Track the select element for form reset/validation
54
- let selectRef: HTMLSelectElement | undefined;
55
-
56
- // Set up form reset handler
57
- createEffect(() => {
58
- const p = getProps();
59
- if (!selectRef) return;
60
-
61
- const form = selectRef.form;
62
- if (!form) return;
63
-
64
- const handleReset = () => {
65
- // Reset to default selected key (first key or null)
66
- const defaultKey = p.state.collection().getFirstKey();
67
- p.state.setSelectedKey(defaultKey);
68
- };
69
-
70
- form.addEventListener('reset', handleReset);
71
-
72
- onCleanup(() => {
73
- form.removeEventListener('reset', handleReset);
74
- });
75
- });
76
-
77
- // Set up form validation handler for native validation
78
- createEffect(() => {
79
- const p = getProps();
80
- if (!selectRef || p.validationBehavior !== 'native' || !p.validationState) return;
81
-
82
- createFormValidation(
83
- {
84
- validationBehavior: p.validationBehavior,
85
- focus: () => p.triggerRef?.()?.focus(),
86
- },
87
- p.validationState,
88
- () => selectRef
89
- );
90
- });
91
-
92
- return {
93
- get containerProps() {
94
- return {
95
- 'aria-hidden': true,
96
- 'data-a11y-ignore': 'aria-hidden-focus',
97
- } as JSX.HTMLAttributes<HTMLDivElement>;
98
- },
99
- get selectProps() {
100
- const p = getProps();
101
- const state = p.state;
102
- const selectedKey = state.selectedKey();
103
- const validationBehavior = p.validationBehavior ?? 'aria';
104
-
105
- return {
106
- ref: (el: HTMLSelectElement) => { selectRef = el; },
107
- tabIndex: -1,
108
- autoComplete: p.autoComplete,
109
- disabled: p.isDisabled ?? state.isDisabled,
110
- name: p.name,
111
- form: p.form,
112
- // Add required attribute for native form validation
113
- required: validationBehavior === 'native' && p.isRequired,
114
- value: selectedKey != null ? String(selectedKey) : '',
115
- onChange: (e: Event) => {
116
- const target = e.target as HTMLSelectElement;
117
- state.setSelectedKey(target.value as Key);
118
- },
119
- style: {
120
- position: 'absolute',
121
- top: 0,
122
- left: 0,
123
- width: '100%',
124
- height: '100%',
125
- opacity: 0,
126
- 'font-size': '16px', // Prevents zoom on iOS
127
- border: 'none',
128
- cursor: 'default',
129
- margin: 0,
130
- padding: 0,
131
- 'pointer-events': 'none',
132
- },
133
- } as JSX.SelectHTMLAttributes<HTMLSelectElement>;
134
- },
135
- get inputProps() {
136
- const p = getProps();
137
- const state = p.state;
138
- const selectedKey = state.selectedKey();
139
- const validationBehavior = p.validationBehavior ?? 'aria';
140
-
141
- // For native validation with required, use type="text" with display:none
142
- // so the browser will validate it on form submit
143
- const useTextInput = validationBehavior === 'native' && p.isRequired;
144
-
145
- return {
146
- type: useTextInput ? 'text' : 'hidden',
147
- name: p.name,
148
- form: p.form,
149
- value: selectedKey != null ? String(selectedKey) : '',
150
- disabled: p.isDisabled ?? state.isDisabled,
151
- required: useTextInput ? p.isRequired : undefined,
152
- style: useTextInput ? { display: 'none' } : undefined,
153
- } as JSX.InputHTMLAttributes<HTMLInputElement>;
154
- },
155
- };
156
- }
157
-
158
- export interface HiddenSelectProps<T> {
159
- /** The state object for the select. */
160
- state: SelectState<T>;
161
- /** The name attribute for the hidden select. */
162
- name?: string;
163
- /** Whether the select is disabled. */
164
- isDisabled?: boolean;
165
- /** Whether the select is required. */
166
- isRequired?: boolean;
167
- /** A ref to the trigger element. */
168
- triggerRef?: () => HTMLElement | null;
169
- /** Label for the select. */
170
- label?: string;
171
- /** Describes the type of autocomplete functionality the select should provide. */
172
- autoComplete?: string;
173
- /** The `form` attribute to associate the select with a form by ID. */
174
- form?: string;
175
- /** Validation behavior: 'aria' for realtime, 'native' for on submit. */
176
- validationBehavior?: ValidationBehavior;
177
- /** Form validation state (optional, for native validation). */
178
- validationState?: FormValidationState;
179
- }
180
-
181
- /**
182
- * A component that renders a hidden native select for form submission.
183
- * This is useful on mobile devices where native select behavior is preferred.
184
- */
185
- export function HiddenSelect<T>(props: HiddenSelectProps<T>): JSX.Element {
186
- const { containerProps, selectProps } = createHiddenSelect({
187
- get state() {
188
- return props.state;
189
- },
190
- get name() {
191
- return props.name;
192
- },
193
- get isDisabled() {
194
- return props.isDisabled;
195
- },
196
- get isRequired() {
197
- return props.isRequired;
198
- },
199
- get autoComplete() {
200
- return props.autoComplete;
201
- },
202
- get form() {
203
- return props.form;
204
- },
205
- get validationBehavior() {
206
- return props.validationBehavior;
207
- },
208
- get triggerRef() {
209
- return props.triggerRef;
210
- },
211
- get validationState() {
212
- return props.validationState;
213
- },
214
- });
215
-
216
- const collection = () => props.state.collection();
217
- const selectedKey = () => props.state.selectedKey();
218
-
219
- return (
220
- <div {...containerProps}>
221
- <label>
222
- {props.label}
223
- <select {...selectProps}>
224
- <option />
225
- <For each={Array.from(collection())}>
226
- {(item) => (
227
- <option value={String(item.key)} selected={item.key === selectedKey()}>
228
- {item.textValue}
229
- </option>
230
- )}
231
- </For>
232
- </select>
233
- </label>
234
- </div>
235
- );
236
- }
1
+ /**
2
+ * Provides a hidden native select element for form integration.
3
+ * Based on @react-aria/select useHiddenSelect.
4
+ */
5
+
6
+ import { type JSX, type Accessor, For, createEffect, onCleanup } from 'solid-js';
7
+ import { access, type MaybeAccessor } from '../utils/reactivity';
8
+ import { createFormValidation } from '../form/createFormValidation';
9
+ import type { SelectState, Key, FormValidationState } from '@proyecto-viviana/solid-stately';
10
+
11
+ export type ValidationBehavior = 'aria' | 'native';
12
+
13
+ export interface AriaHiddenSelectProps<T> {
14
+ /** The state object for the select. */
15
+ state: SelectState<T>;
16
+ /** The name attribute for the hidden select. */
17
+ name?: string;
18
+ /** Whether the select is disabled. */
19
+ isDisabled?: boolean;
20
+ /** Whether the select is required. */
21
+ isRequired?: boolean;
22
+ /** Describes the type of autocomplete functionality the select should provide. */
23
+ autoComplete?: string;
24
+ /** The `form` attribute to associate the select with a form by ID. */
25
+ form?: string;
26
+ /** Validation behavior: 'aria' for realtime, 'native' for on submit. */
27
+ validationBehavior?: ValidationBehavior;
28
+ /** A ref to the trigger element for focus on validation error. */
29
+ triggerRef?: Accessor<HTMLElement | null>;
30
+ /** Form validation state (optional, for native validation). */
31
+ validationState?: FormValidationState;
32
+ }
33
+
34
+ export interface HiddenSelectAria {
35
+ /** Props for the container element. */
36
+ containerProps: JSX.HTMLAttributes<HTMLDivElement>;
37
+ /** Props for the hidden select element. */
38
+ selectProps: JSX.SelectHTMLAttributes<HTMLSelectElement>;
39
+ /** Props for the hidden input element (for form submission). */
40
+ inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
41
+ }
42
+
43
+ /**
44
+ * Provides the accessibility implementation for a hidden select.
45
+ * This is used for native form submission and accessibility on mobile devices.
46
+ */
47
+ export function createHiddenSelect<T>(
48
+ props: MaybeAccessor<AriaHiddenSelectProps<T>>
49
+ ): HiddenSelectAria {
50
+ const getProps = () => access(props);
51
+
52
+ // Track the select element for form reset/validation
53
+ let selectRef: HTMLSelectElement | undefined;
54
+
55
+ // Set up form reset handler
56
+ createEffect(() => {
57
+ const p = getProps();
58
+ if (!selectRef) return;
59
+
60
+ const form = selectRef.form;
61
+ if (!form) return;
62
+
63
+ const handleReset = () => {
64
+ // Reset to default selected key (first key or null)
65
+ const defaultKey = p.state.collection().getFirstKey();
66
+ p.state.setSelectedKey(defaultKey);
67
+ };
68
+
69
+ form.addEventListener('reset', handleReset);
70
+
71
+ onCleanup(() => {
72
+ form.removeEventListener('reset', handleReset);
73
+ });
74
+ });
75
+
76
+ // Set up form validation handler for native validation
77
+ createEffect(() => {
78
+ const p = getProps();
79
+ if (!selectRef || p.validationBehavior !== 'native' || !p.validationState) return;
80
+
81
+ createFormValidation(
82
+ {
83
+ validationBehavior: p.validationBehavior,
84
+ focus: () => p.triggerRef?.()?.focus(),
85
+ },
86
+ p.validationState,
87
+ () => selectRef
88
+ );
89
+ });
90
+
91
+ return {
92
+ get containerProps() {
93
+ return {
94
+ 'aria-hidden': true,
95
+ 'data-a11y-ignore': 'aria-hidden-focus',
96
+ } as JSX.HTMLAttributes<HTMLDivElement>;
97
+ },
98
+ get selectProps() {
99
+ const p = getProps();
100
+ const state = p.state;
101
+ const selectedKey = state.selectedKey();
102
+ const selectedKeys = typeof state.selectedKeys === 'function'
103
+ ? state.selectedKeys()
104
+ : (selectedKey != null ? new Set([selectedKey]) : new Set<Key>());
105
+ const validationBehavior = p.validationBehavior ?? 'aria';
106
+ const isMultiple = typeof state.selectionMode === 'function' && state.selectionMode() === 'multiple';
107
+ const multipleValue =
108
+ selectedKeys === 'all' ? Array.from(state.collection()).map((item) => String(item.key)) : Array.from(selectedKeys).map(String);
109
+
110
+ return {
111
+ ref: (el: HTMLSelectElement) => { selectRef = el; },
112
+ tabIndex: -1,
113
+ autoComplete: p.autoComplete,
114
+ disabled: p.isDisabled ?? state.isDisabled,
115
+ multiple: isMultiple || undefined,
116
+ name: p.name,
117
+ form: p.form,
118
+ // Add required attribute for native form validation
119
+ required: validationBehavior === 'native' && p.isRequired,
120
+ value: isMultiple ? multipleValue : (selectedKey != null ? String(selectedKey) : ''),
121
+ onChange: (e: Event) => {
122
+ const target = e.target as HTMLSelectElement;
123
+ if (isMultiple) {
124
+ if (typeof state.setSelectedKeys === 'function') {
125
+ state.setSelectedKeys(Array.from(target.selectedOptions).map((o) => o.value as Key));
126
+ } else {
127
+ const first = target.selectedOptions[0]?.value;
128
+ state.setSelectedKey((first ?? null) as Key | null);
129
+ }
130
+ } else {
131
+ state.setSelectedKey(target.value as Key);
132
+ }
133
+ },
134
+ style: {
135
+ position: 'absolute',
136
+ top: 0,
137
+ left: 0,
138
+ width: '100%',
139
+ height: '100%',
140
+ opacity: 0,
141
+ 'font-size': '16px', // Prevents zoom on iOS
142
+ border: 'none',
143
+ cursor: 'default',
144
+ margin: 0,
145
+ padding: 0,
146
+ 'pointer-events': 'none',
147
+ },
148
+ } as JSX.SelectHTMLAttributes<HTMLSelectElement>;
149
+ },
150
+ get inputProps() {
151
+ const p = getProps();
152
+ const state = p.state;
153
+ const selectedKey = state.selectedKey();
154
+ const validationBehavior = p.validationBehavior ?? 'aria';
155
+
156
+ // For native validation with required, use type="text" with display:none
157
+ // so the browser will validate it on form submit
158
+ const useTextInput = validationBehavior === 'native' && p.isRequired;
159
+
160
+ return {
161
+ type: useTextInput ? 'text' : 'hidden',
162
+ name: p.name,
163
+ form: p.form,
164
+ value: selectedKey != null ? String(selectedKey) : '',
165
+ disabled: p.isDisabled ?? state.isDisabled,
166
+ required: useTextInput ? p.isRequired : undefined,
167
+ style: useTextInput ? { display: 'none' } : undefined,
168
+ } as JSX.InputHTMLAttributes<HTMLInputElement>;
169
+ },
170
+ };
171
+ }
172
+
173
+ export interface HiddenSelectProps<T> {
174
+ /** The state object for the select. */
175
+ state: SelectState<T>;
176
+ /** The name attribute for the hidden select. */
177
+ name?: string;
178
+ /** Whether the select is disabled. */
179
+ isDisabled?: boolean;
180
+ /** Whether the select is required. */
181
+ isRequired?: boolean;
182
+ /** A ref to the trigger element. */
183
+ triggerRef?: () => HTMLElement | null;
184
+ /** Label for the select. */
185
+ label?: string;
186
+ /** Describes the type of autocomplete functionality the select should provide. */
187
+ autoComplete?: string;
188
+ /** The `form` attribute to associate the select with a form by ID. */
189
+ form?: string;
190
+ /** Validation behavior: 'aria' for realtime, 'native' for on submit. */
191
+ validationBehavior?: ValidationBehavior;
192
+ /** Form validation state (optional, for native validation). */
193
+ validationState?: FormValidationState;
194
+ }
195
+
196
+ /**
197
+ * A component that renders a hidden native select for form submission.
198
+ * This is useful on mobile devices where native select behavior is preferred.
199
+ */
200
+ export function HiddenSelect<T>(props: HiddenSelectProps<T>): JSX.Element {
201
+ const { containerProps, selectProps } = createHiddenSelect({
202
+ get state() {
203
+ return props.state;
204
+ },
205
+ get name() {
206
+ return props.name;
207
+ },
208
+ get isDisabled() {
209
+ return props.isDisabled;
210
+ },
211
+ get isRequired() {
212
+ return props.isRequired;
213
+ },
214
+ get autoComplete() {
215
+ return props.autoComplete;
216
+ },
217
+ get form() {
218
+ return props.form;
219
+ },
220
+ get validationBehavior() {
221
+ return props.validationBehavior;
222
+ },
223
+ get triggerRef() {
224
+ return props.triggerRef;
225
+ },
226
+ get validationState() {
227
+ return props.validationState;
228
+ },
229
+ });
230
+
231
+ const collection = () => props.state.collection();
232
+ const selectedKey = () => props.state.selectedKey();
233
+ const selectedKeys = () => typeof props.state.selectedKeys === 'function'
234
+ ? props.state.selectedKeys()
235
+ : (selectedKey() != null ? new Set([selectedKey() as Key]) : new Set<Key>());
236
+ const isMultiple = () => typeof props.state.selectionMode === 'function'
237
+ && props.state.selectionMode() === 'multiple';
238
+
239
+ return (
240
+ <div {...containerProps}>
241
+ <label>
242
+ {props.label}
243
+ <select {...selectProps}>
244
+ <option />
245
+ <For each={Array.from(collection())}>
246
+ {(item) => (
247
+ <option
248
+ value={String(item.key)}
249
+ selected={isMultiple()
250
+ ? selectedKeys() === 'all'
251
+ ? true
252
+ : (selectedKeys() as Set<Key>).has(item.key)
253
+ : item.key === selectedKey()}
254
+ >
255
+ {item.textValue}
256
+ </option>
257
+ )}
258
+ </For>
259
+ </select>
260
+ </label>
261
+ </div>
262
+ );
263
+ }