@proyecto-viviana/solidaria-components 0.2.9 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (222) hide show
  1. package/README.md +39 -272
  2. package/dist/ActionBar.d.ts +21 -13
  3. package/dist/ActionBar.d.ts.map +1 -1
  4. package/dist/ActionGroup.d.ts +8 -8
  5. package/dist/ActionGroup.d.ts.map +1 -1
  6. package/dist/Alert.d.ts +5 -5
  7. package/dist/Alert.d.ts.map +1 -1
  8. package/dist/Autocomplete.d.ts +5 -5
  9. package/dist/Autocomplete.d.ts.map +1 -1
  10. package/dist/Breadcrumbs.d.ts +18 -7
  11. package/dist/Breadcrumbs.d.ts.map +1 -1
  12. package/dist/Button.d.ts +24 -5
  13. package/dist/Button.d.ts.map +1 -1
  14. package/dist/Calendar.d.ts +38 -7
  15. package/dist/Calendar.d.ts.map +1 -1
  16. package/dist/Checkbox.d.ts +32 -7
  17. package/dist/Checkbox.d.ts.map +1 -1
  18. package/dist/Collection.d.ts +19 -14
  19. package/dist/Collection.d.ts.map +1 -1
  20. package/dist/Color.d.ts +103 -14
  21. package/dist/Color.d.ts.map +1 -1
  22. package/dist/ColorEditor.d.ts +6 -6
  23. package/dist/ColorEditor.d.ts.map +1 -1
  24. package/dist/ComboBox.d.ts +85 -19
  25. package/dist/ComboBox.d.ts.map +1 -1
  26. package/dist/ContextualHelpTrigger.d.ts +2 -2
  27. package/dist/ContextualHelpTrigger.d.ts.map +1 -1
  28. package/dist/DateField.d.ts +8 -6
  29. package/dist/DateField.d.ts.map +1 -1
  30. package/dist/DatePicker.d.ts +53 -22
  31. package/dist/DatePicker.d.ts.map +1 -1
  32. package/dist/DateRangePickerContext.d.ts +30 -0
  33. package/dist/DateRangePickerContext.d.ts.map +1 -0
  34. package/dist/Dialog.d.ts +5 -5
  35. package/dist/Dialog.d.ts.map +1 -1
  36. package/dist/Disclosure.d.ts +23 -5
  37. package/dist/Disclosure.d.ts.map +1 -1
  38. package/dist/DragAndDrop.d.ts +6 -6
  39. package/dist/DragAndDrop.d.ts.map +1 -1
  40. package/dist/DragPreview.d.ts +2 -2
  41. package/dist/DragPreview.d.ts.map +1 -1
  42. package/dist/DropZone.d.ts +4 -4
  43. package/dist/DropZone.d.ts.map +1 -1
  44. package/dist/FieldError.d.ts +9 -5
  45. package/dist/FieldError.d.ts.map +1 -1
  46. package/dist/FileTrigger.d.ts +3 -3
  47. package/dist/FileTrigger.d.ts.map +1 -1
  48. package/dist/Focusable.d.ts +2 -2
  49. package/dist/Focusable.d.ts.map +1 -1
  50. package/dist/Form.d.ts +18 -4
  51. package/dist/Form.d.ts.map +1 -1
  52. package/dist/GridList.d.ts +32 -12
  53. package/dist/GridList.d.ts.map +1 -1
  54. package/dist/HiddenDateInput.d.ts +26 -0
  55. package/dist/HiddenDateInput.d.ts.map +1 -0
  56. package/dist/HiddenTimeInput.d.ts +25 -0
  57. package/dist/HiddenTimeInput.d.ts.map +1 -0
  58. package/dist/Icon.d.ts +5 -5
  59. package/dist/Icon.d.ts.map +1 -1
  60. package/dist/Keyboard.d.ts +1 -1
  61. package/dist/Landmark.d.ts +3 -3
  62. package/dist/Landmark.d.ts.map +1 -1
  63. package/dist/Link.d.ts +10 -4
  64. package/dist/Link.d.ts.map +1 -1
  65. package/dist/ListBox.d.ts +32 -12
  66. package/dist/ListBox.d.ts.map +1 -1
  67. package/dist/ListDropTargetDelegate.d.ts +6 -6
  68. package/dist/ListDropTargetDelegate.d.ts.map +1 -1
  69. package/dist/Menu.d.ts +65 -14
  70. package/dist/Menu.d.ts.map +1 -1
  71. package/dist/Meter.d.ts +3 -3
  72. package/dist/Meter.d.ts.map +1 -1
  73. package/dist/Modal.d.ts +5 -5
  74. package/dist/Modal.d.ts.map +1 -1
  75. package/dist/NumberField.d.ts +8 -12
  76. package/dist/NumberField.d.ts.map +1 -1
  77. package/dist/Popover.d.ts +28 -5
  78. package/dist/Popover.d.ts.map +1 -1
  79. package/dist/Pressable.d.ts +2 -2
  80. package/dist/Pressable.d.ts.map +1 -1
  81. package/dist/ProgressBar.d.ts +5 -3
  82. package/dist/ProgressBar.d.ts.map +1 -1
  83. package/dist/RadioGroup.d.ts +43 -9
  84. package/dist/RadioGroup.d.ts.map +1 -1
  85. package/dist/RangeCalendar.d.ts +34 -7
  86. package/dist/RangeCalendar.d.ts.map +1 -1
  87. package/dist/RouterProvider.d.ts +2 -2
  88. package/dist/RouterProvider.d.ts.map +1 -1
  89. package/dist/SearchField.d.ts +23 -20
  90. package/dist/SearchField.d.ts.map +1 -1
  91. package/dist/Select.d.ts +41 -11
  92. package/dist/Select.d.ts.map +1 -1
  93. package/dist/SelectionIndicator.d.ts +3 -3
  94. package/dist/SelectionIndicator.d.ts.map +1 -1
  95. package/dist/Separator.d.ts +9 -3
  96. package/dist/Separator.d.ts.map +1 -1
  97. package/dist/SharedElementTransition.d.ts +6 -4
  98. package/dist/SharedElementTransition.d.ts.map +1 -1
  99. package/dist/Slider.d.ts +12 -8
  100. package/dist/Slider.d.ts.map +1 -1
  101. package/dist/StepList.d.ts +90 -0
  102. package/dist/StepList.d.ts.map +1 -0
  103. package/dist/Switch.d.ts +11 -5
  104. package/dist/Switch.d.ts.map +1 -1
  105. package/dist/Table.d.ts +187 -23
  106. package/dist/Table.d.ts.map +1 -1
  107. package/dist/Tabs.d.ts +45 -9
  108. package/dist/Tabs.d.ts.map +1 -1
  109. package/dist/TagGroup.d.ts +12 -10
  110. package/dist/TagGroup.d.ts.map +1 -1
  111. package/dist/Text.d.ts +2 -2
  112. package/dist/TextField.d.ts +15 -11
  113. package/dist/TextField.d.ts.map +1 -1
  114. package/dist/TimeField.d.ts +6 -6
  115. package/dist/TimeField.d.ts.map +1 -1
  116. package/dist/Toast.d.ts +29 -14
  117. package/dist/Toast.d.ts.map +1 -1
  118. package/dist/ToggleButton.d.ts +11 -5
  119. package/dist/ToggleButton.d.ts.map +1 -1
  120. package/dist/ToggleButtonGroup.d.ts +7 -7
  121. package/dist/ToggleButtonGroup.d.ts.map +1 -1
  122. package/dist/Toolbar.d.ts +7 -3
  123. package/dist/Toolbar.d.ts.map +1 -1
  124. package/dist/Tooltip.d.ts +50 -8
  125. package/dist/Tooltip.d.ts.map +1 -1
  126. package/dist/Tree.d.ts +66 -17
  127. package/dist/Tree.d.ts.map +1 -1
  128. package/dist/Virtualizer.d.ts +12 -12
  129. package/dist/Virtualizer.d.ts.map +1 -1
  130. package/dist/VirtualizerLayouts.d.ts +2 -2
  131. package/dist/VirtualizerLayouts.d.ts.map +1 -1
  132. package/dist/VisuallyHidden.d.ts +1 -1
  133. package/dist/VisuallyHidden.d.ts.map +1 -1
  134. package/dist/contexts.d.ts +5 -1
  135. package/dist/contexts.d.ts.map +1 -1
  136. package/dist/index.d.ts +73 -71
  137. package/dist/index.d.ts.map +1 -1
  138. package/dist/index.js +23247 -18564
  139. package/dist/index.js.map +1 -1
  140. package/dist/index.jsx +18110 -0
  141. package/dist/index.jsx.map +1 -0
  142. package/dist/useDragAndDrop.d.ts +13 -13
  143. package/dist/useDragAndDrop.d.ts.map +1 -1
  144. package/dist/utils.d.ts +2 -2
  145. package/dist/utils.d.ts.map +1 -1
  146. package/dist/virtualizer/Layout.d.ts +1 -1
  147. package/dist/virtualizer/Layout.d.ts.map +1 -1
  148. package/package.json +31 -32
  149. package/src/ActionBar.tsx +75 -72
  150. package/src/ActionGroup.tsx +53 -61
  151. package/src/Alert.tsx +17 -42
  152. package/src/Autocomplete.tsx +39 -44
  153. package/src/Breadcrumbs.tsx +149 -80
  154. package/src/Button.tsx +267 -70
  155. package/src/Calendar.tsx +218 -138
  156. package/src/Checkbox.tsx +413 -121
  157. package/src/Collection.tsx +67 -58
  158. package/src/Color.tsx +803 -380
  159. package/src/ColorEditor.tsx +131 -149
  160. package/src/ComboBox.tsx +414 -249
  161. package/src/ContextualHelpTrigger.tsx +86 -74
  162. package/src/DateField.tsx +185 -91
  163. package/src/DatePicker.tsx +524 -213
  164. package/src/DateRangePickerContext.tsx +44 -0
  165. package/src/Dialog.tsx +156 -118
  166. package/src/Disclosure.tsx +127 -80
  167. package/src/DragAndDrop.tsx +60 -54
  168. package/src/DragPreview.tsx +13 -11
  169. package/src/DropZone.tsx +42 -22
  170. package/src/FieldError.tsx +45 -23
  171. package/src/FileTrigger.tsx +19 -19
  172. package/src/Focusable.tsx +21 -24
  173. package/src/Form.tsx +71 -16
  174. package/src/GridList.tsx +273 -197
  175. package/src/HiddenDateInput.tsx +153 -0
  176. package/src/HiddenTimeInput.tsx +133 -0
  177. package/src/Icon.tsx +22 -43
  178. package/src/Keyboard.tsx +3 -3
  179. package/src/Landmark.tsx +37 -63
  180. package/src/Link.tsx +125 -75
  181. package/src/ListBox.tsx +332 -233
  182. package/src/ListDropTargetDelegate.ts +81 -80
  183. package/src/Menu.tsx +1023 -274
  184. package/src/Meter.tsx +38 -56
  185. package/src/Modal.tsx +243 -175
  186. package/src/NumberField.tsx +139 -143
  187. package/src/Popover.tsx +386 -233
  188. package/src/Pressable.tsx +21 -21
  189. package/src/ProgressBar.tsx +48 -57
  190. package/src/RadioGroup.tsx +524 -122
  191. package/src/RangeCalendar.tsx +157 -90
  192. package/src/RouterProvider.tsx +30 -47
  193. package/src/SearchField.tsx +362 -143
  194. package/src/Select.tsx +656 -233
  195. package/src/SelectionIndicator.tsx +18 -15
  196. package/src/Separator.tsx +47 -49
  197. package/src/SharedElementTransition.tsx +103 -97
  198. package/src/Slider.tsx +138 -98
  199. package/src/StepList.tsx +272 -0
  200. package/src/Switch.tsx +93 -46
  201. package/src/Table.tsx +1308 -342
  202. package/src/Tabs.tsx +324 -103
  203. package/src/TagGroup.tsx +139 -126
  204. package/src/Text.tsx +3 -3
  205. package/src/TextField.tsx +389 -79
  206. package/src/TimeField.tsx +136 -76
  207. package/src/Toast.tsx +209 -157
  208. package/src/ToggleButton.tsx +47 -37
  209. package/src/ToggleButtonGroup.tsx +39 -34
  210. package/src/Toolbar.tsx +54 -69
  211. package/src/Tooltip.tsx +387 -119
  212. package/src/Tree.tsx +651 -368
  213. package/src/Virtualizer.tsx +208 -180
  214. package/src/VirtualizerLayouts.ts +45 -30
  215. package/src/VisuallyHidden.tsx +19 -19
  216. package/src/contexts.ts +29 -37
  217. package/src/index.ts +110 -195
  218. package/src/useDragAndDrop.ts +87 -71
  219. package/src/utils.tsx +40 -55
  220. package/src/virtualizer/Layout.ts +14 -22
  221. package/dist/index.ssr.js +0 -16996
  222. package/dist/index.ssr.js.map +0 -1
@@ -9,21 +9,28 @@ import {
9
9
  type JSX,
10
10
  createContext,
11
11
  createMemo,
12
+ onCleanup,
13
+ onMount,
12
14
  splitProps,
13
15
  useContext,
14
16
  Show,
15
- } from 'solid-js';
17
+ } from "solid-js";
16
18
  import {
17
19
  createSearchField,
18
20
  createFocusRing,
19
21
  createHover,
20
22
  createPress,
23
+ mergeProps,
21
24
  type AriaSearchFieldProps,
22
- } from '@proyecto-viviana/solidaria';
25
+ } from "@proyecto-viviana/solidaria";
23
26
  import {
24
27
  createSearchFieldState,
28
+ VALID_VALIDITY_STATE,
25
29
  type SearchFieldState,
26
- } from '@proyecto-viviana/solid-stately';
30
+ type ValidationResult,
31
+ } from "@proyecto-viviana/solid-stately";
32
+ import { FormContext, type FormProps } from "./Form";
33
+ import { FieldErrorContext, type FieldErrorContextValue } from "./FieldError";
27
34
  import {
28
35
  type RenderChildren,
29
36
  type ClassNameOrFunction,
@@ -31,11 +38,7 @@ import {
31
38
  type SlotProps,
32
39
  useRenderProps,
33
40
  filterDOMProps,
34
- } from './utils';
35
-
36
- // ============================================
37
- // TYPES
38
- // ============================================
41
+ } from "./utils";
39
42
 
40
43
  export interface SearchFieldRenderProps {
41
44
  /** Whether the search field is empty. */
@@ -52,7 +55,7 @@ export interface SearchFieldRenderProps {
52
55
  value: string;
53
56
  }
54
57
 
55
- export interface SearchFieldProps extends Omit<AriaSearchFieldProps, 'label'>, SlotProps {
58
+ export interface SearchFieldProps extends Omit<AriaSearchFieldProps, "label">, SlotProps {
56
59
  /** The current value (controlled). */
57
60
  value?: string;
58
61
  /** The default value (uncontrolled). */
@@ -71,6 +74,8 @@ export interface SearchFieldProps extends Omit<AriaSearchFieldProps, 'label'>, S
71
74
  class?: ClassNameOrFunction<SearchFieldRenderProps>;
72
75
  /** The inline style for the element. */
73
76
  style?: StyleOrFunction<SearchFieldRenderProps>;
77
+ /** Ref for the outer search field element. */
78
+ ref?: (el: HTMLDivElement) => void;
74
79
  }
75
80
 
76
81
  export interface SearchFieldInputRenderProps {
@@ -86,7 +91,8 @@ export interface SearchFieldInputRenderProps {
86
91
  isInvalid: boolean;
87
92
  }
88
93
 
89
- export interface SearchFieldInputProps extends SlotProps, Omit<JSX.InputHTMLAttributes<HTMLInputElement>, 'class' | 'style'> {
94
+ export interface SearchFieldInputProps
95
+ extends SlotProps, Omit<JSX.InputHTMLAttributes<HTMLInputElement>, "class" | "style"> {
90
96
  /** The CSS className for the element. */
91
97
  class?: ClassNameOrFunction<SearchFieldInputRenderProps>;
92
98
  /** The inline style for the element. */
@@ -102,7 +108,10 @@ export interface SearchFieldClearButtonRenderProps {
102
108
  isDisabled: boolean;
103
109
  }
104
110
 
105
- export interface SearchFieldClearButtonProps extends SlotProps, Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, 'class' | 'style' | 'children'> {
111
+ export interface SearchFieldClearButtonProps
112
+ extends
113
+ SlotProps,
114
+ Omit<JSX.ButtonHTMLAttributes<HTMLButtonElement>, "class" | "style" | "children"> {
106
115
  /** The children of the button. */
107
116
  children?: RenderChildren<SearchFieldClearButtonRenderProps>;
108
117
  /** The CSS className for the element. */
@@ -111,85 +120,181 @@ export interface SearchFieldClearButtonProps extends SlotProps, Omit<JSX.ButtonH
111
120
  style?: StyleOrFunction<SearchFieldClearButtonRenderProps>;
112
121
  }
113
122
 
114
- // ============================================
115
- // CONTEXT
116
- // ============================================
117
-
118
- interface SearchFieldContextValue {
119
- state: SearchFieldState;
120
- inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
121
- clearButtonProps: {
122
- 'aria-label': string;
123
+ interface SearchFieldContextValue extends Partial<SearchFieldProps> {
124
+ state?: SearchFieldState;
125
+ inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
126
+ clearButtonProps?: {
127
+ "aria-label": string;
123
128
  tabIndex: number;
124
129
  disabled?: boolean;
125
130
  onMouseDown: (e: MouseEvent) => void;
126
131
  onClick: () => void;
127
132
  };
128
- labelProps: JSX.HTMLAttributes<HTMLElement>;
129
- descriptionProps: JSX.HTMLAttributes<HTMLElement>;
130
- errorMessageProps: JSX.HTMLAttributes<HTMLElement>;
131
- isDisabled: boolean;
132
- isInvalid: boolean;
133
- isRequired: boolean;
134
- isReadOnly: boolean;
135
- setInputRef: (el: HTMLInputElement) => void;
133
+ labelProps?: JSX.HTMLAttributes<HTMLElement>;
134
+ descriptionProps?: JSX.HTMLAttributes<HTMLElement>;
135
+ errorMessageProps?: JSX.HTMLAttributes<HTMLElement>;
136
+ isDisabled?: boolean;
137
+ isInvalid?: boolean;
138
+ isRequired?: boolean;
139
+ isReadOnly?: boolean;
140
+ setInputRef?: (el: HTMLInputElement) => void;
141
+ slots?: Record<string, SearchFieldProps>;
136
142
  }
137
143
 
138
144
  export const SearchFieldContext = createContext<SearchFieldContextValue | null>(null);
139
145
 
140
- // ============================================
141
- // COMPONENTS
142
- // ============================================
146
+ function withFormValidationBehavior(
147
+ props: SearchFieldProps,
148
+ formContext: FormProps | null,
149
+ ): SearchFieldProps {
150
+ if (!formContext?.validationBehavior) {
151
+ return props;
152
+ }
153
+
154
+ return new Proxy(props, {
155
+ get(target, property, receiver) {
156
+ const localValue = Reflect.get(target, property, receiver);
157
+ if (property === "validationBehavior" && localValue === undefined) {
158
+ return formContext.validationBehavior;
159
+ }
160
+
161
+ return localValue;
162
+ },
163
+ has(target, property) {
164
+ return (
165
+ Reflect.has(target, property) ||
166
+ (property === "validationBehavior" && formContext.validationBehavior !== undefined)
167
+ );
168
+ },
169
+ ownKeys(target) {
170
+ const keys = new Set(Reflect.ownKeys(target));
171
+ if (formContext.validationBehavior !== undefined) {
172
+ keys.add("validationBehavior");
173
+ }
174
+
175
+ return Array.from(keys);
176
+ },
177
+ getOwnPropertyDescriptor(target, property) {
178
+ const descriptor = Reflect.getOwnPropertyDescriptor(target, property);
179
+ if (descriptor) {
180
+ return descriptor;
181
+ }
182
+
183
+ if (property === "validationBehavior" && formContext.validationBehavior !== undefined) {
184
+ return {
185
+ enumerable: true,
186
+ configurable: true,
187
+ get: () => formContext.validationBehavior,
188
+ };
189
+ }
190
+
191
+ return undefined;
192
+ },
193
+ });
194
+ }
195
+
196
+ function eventWithCurrentTarget<T extends HTMLElement>(event: Event, element: T): Event {
197
+ return new Proxy(event, {
198
+ get(target, property, receiver) {
199
+ if (property === "target" || property === "currentTarget") {
200
+ return element;
201
+ }
202
+
203
+ const value = Reflect.get(target, property, receiver);
204
+ return typeof value === "function" ? value.bind(target) : value;
205
+ },
206
+ });
207
+ }
208
+
209
+ function clearDelegatedTextEntryHandlers(element: HTMLElement) {
210
+ const delegatedElement = element as HTMLElement & {
211
+ $$input?: unknown;
212
+ $$change?: unknown;
213
+ };
214
+ delete delegatedElement.$$input;
215
+ delete delegatedElement.$$change;
216
+ }
143
217
 
144
218
  /**
145
219
  * A search field allows a user to enter and clear a search query.
146
220
  */
147
221
  export function SearchField(props: SearchFieldProps): JSX.Element {
222
+ const formContext = useContext(FormContext);
223
+ const contextProps = useContext(SearchFieldContext);
224
+ const contextSlotProps = contextProps?.slots?.[props.slot ?? "default"];
225
+ const contextBaseProps = createMemo<SearchFieldProps>(() => {
226
+ if (!contextProps) return {};
227
+ const {
228
+ state: _state,
229
+ inputProps: _inputProps,
230
+ clearButtonProps: _clearButtonProps,
231
+ labelProps: _labelProps,
232
+ descriptionProps: _descriptionProps,
233
+ errorMessageProps: _errorMessageProps,
234
+ isDisabled: _isDisabled,
235
+ isInvalid: _isInvalid,
236
+ isRequired: _isRequired,
237
+ isReadOnly: _isReadOnly,
238
+ setInputRef: _setInputRef,
239
+ slots: _slots,
240
+ ...restContextProps
241
+ } = contextProps;
242
+ return restContextProps as SearchFieldProps;
243
+ });
244
+ const baseProps = (
245
+ contextProps ? mergeProps(contextBaseProps(), contextSlotProps ?? {}, props) : props
246
+ ) as SearchFieldProps;
247
+ const mergedProps = withFormValidationBehavior(baseProps, formContext);
248
+
148
249
  const [local, stateProps, ariaProps, rest] = splitProps(
149
- props,
150
- ['children', 'class', 'style', 'slot'],
151
- ['value', 'defaultValue', 'onChange', 'onSubmit', 'onClear'],
250
+ mergedProps,
251
+ ["children", "class", "style", "slot", "ref"],
252
+ ["value", "defaultValue", "onChange", "onSubmit", "onClear"],
152
253
  [
153
- 'label',
154
- 'aria-label',
155
- 'aria-labelledby',
156
- 'aria-describedby',
157
- 'isDisabled',
158
- 'isReadOnly',
159
- 'isRequired',
160
- 'isInvalid',
161
- 'description',
162
- 'errorMessage',
163
- 'id',
164
- 'autoFocus',
165
- 'name',
166
- 'placeholder',
167
- 'autoComplete',
168
- 'inputMode',
169
- 'autoCorrect',
170
- 'autoCapitalize',
171
- 'spellCheck',
172
- 'maxLength',
173
- 'minLength',
174
- 'pattern',
175
- 'onFocus',
176
- 'onBlur',
177
- 'onFocusChange',
178
- 'onKeyDown',
179
- 'onKeyUp',
180
- 'onCopy',
181
- 'onCut',
182
- 'onPaste',
183
- 'onCompositionStart',
184
- 'onCompositionEnd',
185
- 'onCompositionUpdate',
186
- 'onSelect',
187
- 'onBeforeInput',
188
- 'onInput',
189
- ]
254
+ "label",
255
+ "aria-label",
256
+ "aria-labelledby",
257
+ "aria-describedby",
258
+ "isDisabled",
259
+ "isReadOnly",
260
+ "isRequired",
261
+ "isInvalid",
262
+ "description",
263
+ "errorMessage",
264
+ "id",
265
+ "autoFocus",
266
+ "excludeFromTabOrder",
267
+ "name",
268
+ "form",
269
+ "validationBehavior",
270
+ "type",
271
+ "placeholder",
272
+ "autoComplete",
273
+ "inputMode",
274
+ "enterKeyHint",
275
+ "autoCorrect",
276
+ "autoCapitalize",
277
+ "spellCheck",
278
+ "maxLength",
279
+ "minLength",
280
+ "pattern",
281
+ "onFocus",
282
+ "onBlur",
283
+ "onFocusChange",
284
+ "onKeyDown",
285
+ "onKeyUp",
286
+ "onCopy",
287
+ "onCut",
288
+ "onPaste",
289
+ "onCompositionStart",
290
+ "onCompositionEnd",
291
+ "onCompositionUpdate",
292
+ "onSelect",
293
+ "onBeforeInput",
294
+ "onInput",
295
+ ],
190
296
  );
191
297
 
192
- // Create search field state
193
298
  const state = createSearchFieldState({
194
299
  get value() {
195
300
  return stateProps.value;
@@ -202,13 +307,11 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
202
307
  },
203
308
  });
204
309
 
205
- // Ref for the input
206
310
  let inputRef: HTMLInputElement | undefined;
207
311
  const setInputRef = (el: HTMLInputElement) => {
208
312
  inputRef = el;
209
313
  };
210
314
 
211
- // Create search field aria props
212
315
  const searchFieldAria = createSearchField(
213
316
  {
214
317
  get isDisabled() {
@@ -226,14 +329,14 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
226
329
  get label() {
227
330
  return ariaProps.label;
228
331
  },
229
- get 'aria-label'() {
230
- return ariaProps['aria-label'];
332
+ get "aria-label"() {
333
+ return ariaProps["aria-label"];
231
334
  },
232
- get 'aria-labelledby'() {
233
- return ariaProps['aria-labelledby'];
335
+ get "aria-labelledby"() {
336
+ return ariaProps["aria-labelledby"];
234
337
  },
235
- get 'aria-describedby'() {
236
- return ariaProps['aria-describedby'];
338
+ get "aria-describedby"() {
339
+ return ariaProps["aria-describedby"];
237
340
  },
238
341
  get description() {
239
342
  return ariaProps.description;
@@ -247,15 +350,30 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
247
350
  get name() {
248
351
  return ariaProps.name;
249
352
  },
353
+ get form() {
354
+ return ariaProps.form;
355
+ },
356
+ get validationBehavior() {
357
+ return ariaProps.validationBehavior;
358
+ },
359
+ get type() {
360
+ return ariaProps.type;
361
+ },
250
362
  get autoFocus() {
251
363
  return ariaProps.autoFocus;
252
364
  },
365
+ get excludeFromTabOrder() {
366
+ return ariaProps.excludeFromTabOrder;
367
+ },
253
368
  get autoComplete() {
254
369
  return ariaProps.autoComplete;
255
370
  },
256
371
  get inputMode() {
257
372
  return ariaProps.inputMode;
258
373
  },
374
+ get enterKeyHint() {
375
+ return ariaProps.enterKeyHint;
376
+ },
259
377
  get autoCorrect() {
260
378
  return ariaProps.autoCorrect;
261
379
  },
@@ -324,12 +442,11 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
324
442
  },
325
443
  },
326
444
  state,
327
- () => inputRef ?? null
445
+ () => inputRef ?? null,
328
446
  );
329
447
 
330
- // Render props values
331
448
  const renderValues = createMemo<SearchFieldRenderProps>(() => ({
332
- isEmpty: state.value() === '',
449
+ isEmpty: state.value() === "",
333
450
  isDisabled: ariaProps.isDisabled ?? false,
334
451
  isInvalid: searchFieldAria.isInvalid ?? false,
335
452
  isRequired: ariaProps.isRequired ?? false,
@@ -337,20 +454,61 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
337
454
  value: state.value(),
338
455
  }));
339
456
 
340
- // Resolve render props
341
457
  const renderProps = useRenderProps(
342
458
  {
343
- children: props.children,
459
+ children: local.children,
344
460
  class: local.class,
345
461
  style: local.style,
346
- defaultClassName: 'solidaria-SearchField',
462
+ defaultClassName: "solidaria-SearchField",
347
463
  },
348
- renderValues
464
+ renderValues,
349
465
  );
466
+ const childRenderValues: SearchFieldRenderProps = {
467
+ get isEmpty() {
468
+ return state.value() === "";
469
+ },
470
+ get isDisabled() {
471
+ return ariaProps.isDisabled ?? false;
472
+ },
473
+ get isInvalid() {
474
+ return searchFieldAria.isInvalid ?? false;
475
+ },
476
+ get isRequired() {
477
+ return ariaProps.isRequired ?? false;
478
+ },
479
+ get isReadOnly() {
480
+ return ariaProps.isReadOnly ?? false;
481
+ },
482
+ get value() {
483
+ return state.value();
484
+ },
485
+ };
350
486
 
351
- // Filter DOM props
352
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
487
+ const domProps = createMemo(() =>
488
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
489
+ );
353
490
 
491
+ const fieldValidation = createMemo<ValidationResult>(() => {
492
+ const isInvalid = searchFieldAria.isInvalid;
493
+ const errorMessage = ariaProps.errorMessage;
494
+ const validationErrors = isInvalid && typeof errorMessage === "string" ? [errorMessage] : [];
495
+
496
+ return {
497
+ isInvalid,
498
+ validationErrors,
499
+ validationDetails: isInvalid
500
+ ? { ...VALID_VALIDITY_STATE, customError: true, valid: false }
501
+ : VALID_VALIDITY_STATE,
502
+ };
503
+ });
504
+ const fieldErrorContext: FieldErrorContextValue = {
505
+ get validation() {
506
+ return fieldValidation();
507
+ },
508
+ get errorMessageProps() {
509
+ return searchFieldAria.errorMessageProps;
510
+ },
511
+ };
354
512
  const contextValue: SearchFieldContextValue = {
355
513
  state,
356
514
  get inputProps() {
@@ -382,32 +540,43 @@ export function SearchField(props: SearchFieldProps): JSX.Element {
382
540
  },
383
541
  setInputRef,
384
542
  };
543
+ const fieldChildren = () => {
544
+ const children = local.children;
545
+ return typeof children === "function" ? children(childRenderValues) : children;
546
+ };
385
547
 
386
548
  return (
387
- <SearchFieldContext.Provider value={contextValue}>
388
- <div
389
- {...domProps()}
390
- class={renderProps.class()}
391
- style={renderProps.style()}
392
- data-empty={state.value() === '' || undefined}
393
- data-disabled={ariaProps.isDisabled || undefined}
394
- data-invalid={searchFieldAria.isInvalid || undefined}
395
- data-required={ariaProps.isRequired || undefined}
396
- data-readonly={ariaProps.isReadOnly || undefined}
397
- >
398
- {renderProps.renderChildren()}
399
- </div>
400
- </SearchFieldContext.Provider>
549
+ <FieldErrorContext.Provider value={fieldErrorContext}>
550
+ <SearchFieldContext.Provider value={contextValue}>
551
+ <div
552
+ {...domProps()}
553
+ ref={local.ref}
554
+ class={renderProps.class()}
555
+ style={renderProps.style()}
556
+ data-empty={state.value() === "" || undefined}
557
+ data-disabled={ariaProps.isDisabled || undefined}
558
+ data-invalid={searchFieldAria.isInvalid || undefined}
559
+ data-required={ariaProps.isRequired || undefined}
560
+ data-readonly={ariaProps.isReadOnly || undefined}
561
+ >
562
+ {fieldChildren()}
563
+ </div>
564
+ </SearchFieldContext.Provider>
565
+ </FieldErrorContext.Provider>
401
566
  );
402
567
  }
403
568
 
404
569
  /**
405
570
  * The label for a search field.
406
571
  */
407
- export function SearchFieldLabel(props: { children?: JSX.Element; class?: string; style?: JSX.CSSProperties }): JSX.Element {
572
+ export function SearchFieldLabel(props: {
573
+ children?: JSX.Element;
574
+ class?: string;
575
+ style?: JSX.CSSProperties;
576
+ }): JSX.Element {
408
577
  const context = useContext(SearchFieldContext);
409
578
  if (!context) {
410
- throw new Error('SearchFieldLabel must be used within a SearchField');
579
+ throw new Error("SearchFieldLabel must be used within a SearchField");
411
580
  }
412
581
 
413
582
  const cleanLabelProps = () => {
@@ -418,7 +587,7 @@ export function SearchFieldLabel(props: { children?: JSX.Element; class?: string
418
587
  return (
419
588
  <label
420
589
  {...cleanLabelProps()}
421
- class={props.class ?? 'solidaria-SearchField-label'}
590
+ class={props.class ?? "solidaria-SearchField-label"}
422
591
  style={props.style}
423
592
  >
424
593
  {props.children}
@@ -430,43 +599,39 @@ export function SearchFieldLabel(props: { children?: JSX.Element; class?: string
430
599
  * The input element for a search field.
431
600
  */
432
601
  export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
433
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot']);
602
+ const [local, domProps] = splitProps(props, ["class", "style", "slot"]);
603
+ let inputElement: HTMLInputElement | undefined;
434
604
 
435
605
  const context = useContext(SearchFieldContext);
436
606
  if (!context) {
437
- throw new Error('SearchFieldInput must be used within a SearchField');
607
+ throw new Error("SearchFieldInput must be used within a SearchField");
438
608
  }
439
609
 
440
- // Create focus ring
441
610
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
442
611
 
443
- // Create hover
444
612
  const { isHovered, hoverProps } = createHover({
445
613
  get isDisabled() {
446
614
  return context.isDisabled;
447
615
  },
448
616
  });
449
617
 
450
- // Render props values
451
618
  const renderValues = createMemo<SearchFieldInputRenderProps>(() => ({
452
619
  isFocused: isFocused(),
453
620
  isFocusVisible: isFocusVisible(),
454
621
  isHovered: isHovered(),
455
- isDisabled: context.isDisabled,
456
- isInvalid: context.isInvalid,
622
+ isDisabled: !!context.isDisabled,
623
+ isInvalid: !!context.isInvalid,
457
624
  }));
458
625
 
459
- // Resolve render props
460
626
  const renderProps = useRenderProps(
461
627
  {
462
628
  class: local.class,
463
629
  style: local.style,
464
- defaultClassName: 'solidaria-SearchField-input',
630
+ defaultClassName: "solidaria-SearchField-input",
465
631
  },
466
- renderValues
632
+ renderValues,
467
633
  );
468
634
 
469
- // Remove ref from spread props
470
635
  const cleanInputProps = () => {
471
636
  const { ref: _ref, ...rest } = context.inputProps as Record<string, unknown>;
472
637
  return rest;
@@ -480,13 +645,67 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
480
645
  return rest;
481
646
  };
482
647
 
648
+ const mergedInputProps = () =>
649
+ ({
650
+ ...domProps,
651
+ ...cleanInputProps(),
652
+ ...cleanFocusProps(),
653
+ ...cleanHoverProps(),
654
+ }) as Record<string, unknown>;
655
+
656
+ onMount(() => {
657
+ const element = inputElement;
658
+ if (!element) {
659
+ return;
660
+ }
661
+
662
+ const inputHandler = (event: Event) => {
663
+ const handler = mergedInputProps().onInput as
664
+ | JSX.EventHandler<HTMLInputElement, InputEvent>
665
+ | undefined;
666
+ handler?.(
667
+ eventWithCurrentTarget(event, element) as InputEvent & {
668
+ currentTarget: HTMLInputElement;
669
+ target: Element;
670
+ },
671
+ );
672
+ clearDelegatedTextEntryHandlers(element);
673
+ event.stopPropagation();
674
+ };
675
+ const changeHandler = (event: Event) => {
676
+ const handler = mergedInputProps().onChange as
677
+ | JSX.EventHandler<HTMLInputElement, Event>
678
+ | undefined;
679
+ handler?.(
680
+ eventWithCurrentTarget(event, element) as Event & {
681
+ currentTarget: HTMLInputElement;
682
+ target: Element;
683
+ },
684
+ );
685
+ clearDelegatedTextEntryHandlers(element);
686
+ event.stopPropagation();
687
+ };
688
+
689
+ element.addEventListener("input", inputHandler);
690
+ element.addEventListener("change", changeHandler);
691
+ clearDelegatedTextEntryHandlers(element);
692
+ onCleanup(() => {
693
+ element.removeEventListener("input", inputHandler);
694
+ element.removeEventListener("change", changeHandler);
695
+ });
696
+ });
697
+
483
698
  return (
484
699
  <input
485
- {...domProps}
486
- ref={context.setInputRef}
487
- {...cleanInputProps()}
488
- {...cleanFocusProps()}
489
- {...cleanHoverProps()}
700
+ {...mergedInputProps()}
701
+ ref={(element) => {
702
+ inputElement = element;
703
+ context.setInputRef?.(element);
704
+ const ref = (domProps as { ref?: unknown }).ref;
705
+ if (typeof ref === "function") {
706
+ ref(element);
707
+ }
708
+ }}
490
709
  class={renderProps.class()}
491
710
  style={renderProps.style()}
492
711
  data-focused={isFocused() || undefined}
@@ -502,52 +721,51 @@ export function SearchFieldInput(props: SearchFieldInputProps): JSX.Element {
502
721
  * The clear button for a search field.
503
722
  */
504
723
  export function SearchFieldClearButton(props: SearchFieldClearButtonProps): JSX.Element {
505
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
724
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
506
725
 
507
726
  const context = useContext(SearchFieldContext);
508
727
  if (!context) {
509
- throw new Error('SearchFieldClearButton must be used within a SearchField');
728
+ throw new Error("SearchFieldClearButton must be used within a SearchField");
510
729
  }
511
730
 
512
- // Create press
731
+ const isDisabled = () => !!(context.isDisabled || context.isReadOnly);
732
+ const isEmpty = () => (context.state?.value() ?? "") === "";
733
+ const clear = () => {
734
+ if (!isDisabled() && !isEmpty()) {
735
+ context.clearButtonProps?.onClick();
736
+ }
737
+ };
738
+
513
739
  const { isPressed, pressProps } = createPress({
514
740
  get isDisabled() {
515
741
  return context.isDisabled || context.isReadOnly;
516
742
  },
517
- onPress: () => {
518
- context.clearButtonProps.onClick();
519
- },
743
+ onClick: clear,
744
+ onPress: clear,
520
745
  });
521
746
 
522
- // Create hover
523
747
  const { isHovered, hoverProps } = createHover({
524
748
  get isDisabled() {
525
749
  return context.isDisabled || context.isReadOnly;
526
750
  },
527
751
  });
528
752
 
529
- const isDisabled = () => context.isDisabled || context.isReadOnly;
530
- const isEmpty = () => context.state.value() === '';
531
-
532
- // Render props values
533
753
  const renderValues = createMemo<SearchFieldClearButtonRenderProps>(() => ({
534
754
  isPressed: isPressed(),
535
755
  isHovered: isHovered(),
536
756
  isDisabled: isDisabled(),
537
757
  }));
538
758
 
539
- // Resolve render props
540
759
  const renderProps = useRenderProps(
541
760
  {
542
761
  children: props.children,
543
762
  class: local.class,
544
763
  style: local.style,
545
- defaultClassName: 'solidaria-SearchField-clear',
764
+ defaultClassName: "solidaria-SearchField-clear",
546
765
  },
547
- renderValues
766
+ renderValues,
548
767
  );
549
768
 
550
- // Remove ref from spread props
551
769
  const cleanPressProps = () => {
552
770
  const { ref: _ref, ...rest } = pressProps as Record<string, unknown>;
553
771
  return rest;
@@ -557,17 +775,19 @@ export function SearchFieldClearButton(props: SearchFieldClearButtonProps): JSX.
557
775
  return rest;
558
776
  };
559
777
 
560
- // Only show clear button when there's a value
561
778
  return (
562
779
  <Show when={!isEmpty()}>
563
780
  <button
564
781
  {...domProps}
565
782
  type="button"
566
- aria-label={context.clearButtonProps['aria-label']}
567
- tabIndex={context.clearButtonProps.tabIndex}
568
- disabled={context.clearButtonProps.disabled}
569
- onMouseDown={context.clearButtonProps.onMouseDown}
783
+ aria-label={context.clearButtonProps?.["aria-label"] ?? "Clear search"}
784
+ tabIndex={context.clearButtonProps?.tabIndex ?? -1}
785
+ disabled={context.clearButtonProps?.disabled}
786
+ onMouseDown={context.clearButtonProps?.onMouseDown}
570
787
  {...cleanPressProps()}
788
+ onPointerUp={clear}
789
+ onMouseUp={clear}
790
+ onClick={clear}
571
791
  {...cleanHoverProps()}
572
792
  class={renderProps.class()}
573
793
  style={renderProps.style()}
@@ -581,7 +801,6 @@ export function SearchFieldClearButton(props: SearchFieldClearButtonProps): JSX.
581
801
  );
582
802
  }
583
803
 
584
- // Attach sub-components
585
804
  SearchField.Label = SearchFieldLabel;
586
805
  SearchField.Input = SearchFieldInput;
587
806
  SearchField.ClearButton = SearchFieldClearButton;