@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
package/src/Color.tsx CHANGED
@@ -13,9 +13,10 @@ import {
13
13
  createSignal,
14
14
  onCleanup,
15
15
  splitProps,
16
+ untrack,
16
17
  useContext,
17
18
  Show,
18
- } from 'solid-js';
19
+ } from "solid-js";
19
20
  import {
20
21
  createColorSlider,
21
22
  createColorArea,
@@ -31,7 +32,7 @@ import {
31
32
  type AriaColorAreaOptions,
32
33
  type AriaColorWheelOptions,
33
34
  type AriaColorFieldOptions,
34
- } from '@proyecto-viviana/solidaria';
35
+ } from "@proyecto-viviana/solidaria";
35
36
  import {
36
37
  createListState,
37
38
  createColorSliderState,
@@ -42,13 +43,14 @@ import {
42
43
  type Color,
43
44
  type ColorChannel,
44
45
  type ColorFormat,
46
+ type ColorSpace,
45
47
  type ColorSliderState,
46
48
  type ColorAreaState,
47
49
  type ColorWheelState,
48
50
  type ColorFieldState,
49
51
  type ListState,
50
52
  type Key,
51
- } from '@proyecto-viviana/solid-stately';
53
+ } from "@proyecto-viviana/solid-stately";
52
54
  import {
53
55
  type RenderChildren,
54
56
  type ClassNameOrFunction,
@@ -56,7 +58,7 @@ import {
56
58
  type SlotProps,
57
59
  useRenderProps,
58
60
  filterDOMProps,
59
- } from './utils';
61
+ } from "./utils";
60
62
 
61
63
  interface ColorPickerChannelContextValue {
62
64
  value?: Color | string;
@@ -86,10 +88,6 @@ const ColorPickerStateContextInternal = createContext<ColorPickerStateContextVal
86
88
  const ColorSwatchContextInternal = createContext<{ color?: Color | string } | null>(null);
87
89
  const ColorSwatchPickerContextInternal = createContext<ColorSwatchPickerContextValue | null>(null);
88
90
 
89
- // ============================================
90
- // COLOR SLIDER
91
- // ============================================
92
-
93
91
  export interface ColorSliderRenderProps {
94
92
  /** Whether the slider is disabled. */
95
93
  isDisabled: boolean;
@@ -97,13 +95,19 @@ export interface ColorSliderRenderProps {
97
95
  isDragging: boolean;
98
96
  /** The color channel being controlled. */
99
97
  channel: ColorChannel;
98
+ /** The slider orientation. */
99
+ orientation: "horizontal" | "vertical";
100
100
  /** The current value. */
101
101
  value: number;
102
+ /** The formatted current value. */
103
+ valueLabel: string;
102
104
  /** The current color. */
103
105
  color: Color;
106
+ /** The default inline styles applied by the color slider hook. */
107
+ defaultStyle: JSX.CSSProperties;
104
108
  }
105
109
 
106
- export interface ColorSliderProps extends Omit<AriaColorSliderOptions, 'channel'>, SlotProps {
110
+ export interface ColorSliderProps extends Omit<AriaColorSliderOptions, "channel">, SlotProps {
107
111
  /** The current color value (controlled). */
108
112
  value?: Color | string;
109
113
  /** The default color value (uncontrolled). */
@@ -112,6 +116,8 @@ export interface ColorSliderProps extends Omit<AriaColorSliderOptions, 'channel'
112
116
  onChange?: (color: Color) => void;
113
117
  /** Handler called when dragging ends. */
114
118
  onChangeEnd?: (color: Color) => void;
119
+ /** Color space used for channel values. */
120
+ colorSpace?: ColorSpace;
115
121
  /** The color channel to control. */
116
122
  channel: ColorChannel;
117
123
  /** A visible label for the slider. */
@@ -129,6 +135,10 @@ export interface ColorSliderTrackRenderProps {
129
135
  isDisabled: boolean;
130
136
  /** Whether the slider is being dragged. */
131
137
  isDragging: boolean;
138
+ /** The slider orientation. */
139
+ orientation: "horizontal" | "vertical";
140
+ /** The default inline styles applied by the color slider hook. */
141
+ defaultStyle: JSX.CSSProperties;
132
142
  }
133
143
 
134
144
  export interface ColorSliderTrackProps extends SlotProps {
@@ -140,6 +150,24 @@ export interface ColorSliderTrackProps extends SlotProps {
140
150
  style?: StyleOrFunction<ColorSliderTrackRenderProps>;
141
151
  }
142
152
 
153
+ export interface ColorSliderLabelProps extends SlotProps {
154
+ /** The label contents. */
155
+ children?: JSX.Element;
156
+ /** The CSS className for the element. */
157
+ class?: string;
158
+ /** The inline style for the element. */
159
+ style?: JSX.CSSProperties;
160
+ }
161
+
162
+ export interface ColorSliderOutputProps extends SlotProps {
163
+ /** The output contents. Defaults to the formatted slider value. */
164
+ children?: JSX.Element | ((renderProps: ColorSliderRenderProps) => JSX.Element);
165
+ /** The CSS className for the element. */
166
+ class?: string;
167
+ /** The inline style for the element. */
168
+ style?: JSX.CSSProperties;
169
+ }
170
+
143
171
  export interface ColorSliderThumbRenderProps {
144
172
  /** Whether the slider is disabled. */
145
173
  isDisabled: boolean;
@@ -151,6 +179,10 @@ export interface ColorSliderThumbRenderProps {
151
179
  isFocusVisible: boolean;
152
180
  /** Whether the thumb is hovered. */
153
181
  isHovered: boolean;
182
+ /** The current display color. */
183
+ color: Color;
184
+ /** The default inline styles applied by the color slider hook. */
185
+ defaultStyle: JSX.CSSProperties;
154
186
  }
155
187
 
156
188
  export interface ColorSliderThumbProps extends SlotProps {
@@ -160,16 +192,20 @@ export interface ColorSliderThumbProps extends SlotProps {
160
192
  class?: ClassNameOrFunction<ColorSliderThumbRenderProps>;
161
193
  /** The inline style for the element. */
162
194
  style?: StyleOrFunction<ColorSliderThumbRenderProps>;
195
+ /** Ref callback for the thumb element. */
196
+ ref?: (element: HTMLDivElement) => void;
163
197
  }
164
198
 
165
- // Context
166
199
  interface ColorSliderContextValue {
167
200
  state: ColorSliderState;
168
201
  trackProps: JSX.HTMLAttributes<HTMLDivElement>;
169
202
  thumbProps: JSX.HTMLAttributes<HTMLDivElement>;
170
203
  inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
204
+ outputProps: JSX.HTMLAttributes<HTMLOutputElement>;
205
+ labelProps: JSX.LabelHTMLAttributes<HTMLLabelElement>;
171
206
  trackRef: HTMLDivElement | undefined;
172
207
  setTrackRef: (el: HTMLDivElement) => void;
208
+ setInputRef: (el: HTMLInputElement) => void;
173
209
  }
174
210
 
175
211
  export const ColorSliderContext = createContext<ColorSliderContextValue | null>(null);
@@ -181,9 +217,20 @@ export function ColorSlider(props: ColorSliderProps): JSX.Element {
181
217
  const pickerContext = useContext(ColorPickerContextInternal);
182
218
  const [local, stateProps, ariaProps, rest] = splitProps(
183
219
  props,
184
- ['children', 'class', 'style', 'slot', 'label'],
185
- ['value', 'defaultValue', 'onChange', 'onChangeEnd', 'channel'],
186
- ['aria-label', 'aria-labelledby', 'aria-describedby', 'isDisabled', 'channelName']
220
+ ["children", "class", "style", "slot", "label"],
221
+ ["value", "defaultValue", "onChange", "onChangeEnd", "channel", "colorSpace"],
222
+ [
223
+ "id",
224
+ "aria-label",
225
+ "aria-labelledby",
226
+ "aria-describedby",
227
+ "aria-details",
228
+ "isDisabled",
229
+ "name",
230
+ "form",
231
+ "orientation",
232
+ "channelName",
233
+ ],
187
234
  );
188
235
 
189
236
  // Create color slider state
@@ -193,66 +240,90 @@ export function ColorSlider(props: ColorSliderProps): JSX.Element {
193
240
  onChange: stateProps.onChange ?? pickerContext?.onChange,
194
241
  onChangeEnd: stateProps.onChangeEnd,
195
242
  channel: stateProps.channel,
243
+ colorSpace: stateProps.colorSpace,
244
+ orientation: ariaProps.orientation,
196
245
  isDisabled: ariaProps.isDisabled,
197
246
  }));
198
247
 
199
- // Track ref
200
248
  let trackRef: HTMLDivElement | undefined;
201
249
  const setTrackRef = (el: HTMLDivElement) => {
202
250
  trackRef = el;
203
251
  };
252
+ let inputRef: HTMLInputElement | undefined;
253
+ const setInputRef = (el: HTMLInputElement) => {
254
+ inputRef = el;
255
+ };
204
256
 
205
257
  // Create color slider aria props
206
- const {
207
- trackProps,
208
- thumbProps,
209
- inputProps,
210
- labelProps,
211
- } = createColorSlider(
258
+ const colorSliderAria = createColorSlider(
212
259
  () => ({
260
+ id: ariaProps.id,
213
261
  channel: stateProps.channel,
214
- 'aria-label': ariaProps['aria-label'],
215
- 'aria-labelledby': ariaProps['aria-labelledby'],
216
- 'aria-describedby': ariaProps['aria-describedby'],
262
+ label: local.label,
263
+ "aria-label": ariaProps["aria-label"],
264
+ "aria-labelledby": ariaProps["aria-labelledby"],
265
+ "aria-describedby": ariaProps["aria-describedby"],
266
+ "aria-details": ariaProps["aria-details"],
217
267
  isDisabled: ariaProps.isDisabled,
268
+ name: ariaProps.name,
269
+ form: ariaProps.form,
270
+ orientation: ariaProps.orientation,
218
271
  channelName: ariaProps.channelName,
219
272
  }),
220
273
  () => state,
221
- () => trackRef ?? null
274
+ () => trackRef ?? null,
275
+ () => inputRef ?? null,
222
276
  );
223
277
 
224
- // Render props values
225
278
  const renderValues = createMemo<ColorSliderRenderProps>(() => ({
226
279
  isDisabled: state.isDisabled,
227
280
  isDragging: state.isDragging,
228
281
  channel: state.channel,
282
+ orientation: state.orientation,
229
283
  value: state.getThumbValue(),
284
+ valueLabel: state.getThumbValueLabel(),
230
285
  color: state.value,
286
+ defaultStyle: (colorSliderAria.trackProps as { style?: JSX.CSSProperties }).style ?? {},
231
287
  }));
232
288
 
233
- // Resolve render props
234
289
  const renderProps = useRenderProps(
235
290
  {
236
291
  children: props.children,
237
292
  class: local.class,
238
293
  style: local.style,
239
- defaultClassName: 'solidaria-ColorSlider',
294
+ defaultClassName: "solidaria-ColorSlider",
240
295
  },
241
- renderValues
296
+ renderValues,
242
297
  );
243
298
 
244
- // Filter DOM props
245
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
299
+ const domProps = createMemo(() =>
300
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
301
+ );
246
302
 
247
303
  return (
248
304
  <ColorSliderContext.Provider
249
305
  value={{
250
306
  state,
251
- trackProps,
252
- thumbProps,
253
- inputProps,
254
- trackRef,
307
+ get trackProps() {
308
+ return colorSliderAria.trackProps;
309
+ },
310
+ get thumbProps() {
311
+ return colorSliderAria.thumbProps;
312
+ },
313
+ get inputProps() {
314
+ return colorSliderAria.inputProps;
315
+ },
316
+ get outputProps() {
317
+ return colorSliderAria.outputProps;
318
+ },
319
+ get labelProps() {
320
+ return colorSliderAria.labelProps;
321
+ },
322
+ get trackRef() {
323
+ return trackRef;
324
+ },
255
325
  setTrackRef,
326
+ setInputRef,
256
327
  }}
257
328
  >
258
329
  <div
@@ -261,58 +332,115 @@ export function ColorSlider(props: ColorSliderProps): JSX.Element {
261
332
  style={renderProps.style()}
262
333
  data-disabled={state.isDisabled || undefined}
263
334
  data-dragging={state.isDragging || undefined}
264
- data-channel={state.channel}
335
+ data-orientation={state.orientation}
336
+ slot={local.slot || undefined}
265
337
  >
266
- {/* Label */}
267
- <Show when={local.label}>
268
- <label {...labelProps}>{local.label}</label>
269
- </Show>
270
-
271
338
  {renderProps.renderChildren()}
272
339
  </div>
273
340
  </ColorSliderContext.Provider>
274
341
  );
275
342
  }
276
343
 
344
+ /**
345
+ * The label element of a color slider.
346
+ */
347
+ export function ColorSliderLabel(props: ColorSliderLabelProps): JSX.Element {
348
+ const [local, domProps] = splitProps(props, ["class", "slot", "children"]);
349
+
350
+ const context = useContext(ColorSliderContext);
351
+ if (!context) {
352
+ throw new Error("ColorSliderLabel must be used within a ColorSlider");
353
+ }
354
+
355
+ const labelProps = () => {
356
+ const { ref: _ref, ...rest } = context.labelProps as Record<string, unknown>;
357
+ return rest;
358
+ };
359
+
360
+ return (
361
+ <label {...domProps} {...labelProps()} class={local.class}>
362
+ {local.children}
363
+ </label>
364
+ );
365
+ }
366
+
367
+ /**
368
+ * The output element of a color slider.
369
+ */
370
+ export function ColorSliderOutput(props: ColorSliderOutputProps): JSX.Element {
371
+ const [local, domProps] = splitProps(props, ["class", "slot", "children"]);
372
+
373
+ const context = useContext(ColorSliderContext);
374
+ if (!context) {
375
+ throw new Error("ColorSliderOutput must be used within a ColorSlider");
376
+ }
377
+
378
+ const state = context.state;
379
+
380
+ const renderValues = createMemo<ColorSliderRenderProps>(() => ({
381
+ isDisabled: state.isDisabled,
382
+ isDragging: state.isDragging,
383
+ channel: state.channel,
384
+ orientation: state.orientation,
385
+ value: state.getThumbValue(),
386
+ valueLabel: state.getThumbValueLabel(),
387
+ color: state.value,
388
+ defaultStyle: (context.trackProps as { style?: JSX.CSSProperties }).style ?? {},
389
+ }));
390
+
391
+ const children = () =>
392
+ typeof local.children === "function"
393
+ ? local.children(renderValues())
394
+ : (local.children ?? renderValues().valueLabel);
395
+
396
+ return (
397
+ <output {...domProps} {...context.outputProps} class={local.class}>
398
+ {children()}
399
+ </output>
400
+ );
401
+ }
402
+
277
403
  /**
278
404
  * The track element of a color slider.
279
405
  */
280
406
  export function ColorSliderTrack(props: ColorSliderTrackProps): JSX.Element {
281
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
407
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
282
408
 
283
409
  const context = useContext(ColorSliderContext);
284
410
  if (!context) {
285
- throw new Error('ColorSliderTrack must be used within a ColorSlider');
411
+ throw new Error("ColorSliderTrack must be used within a ColorSlider");
286
412
  }
287
413
 
288
- const { state, trackProps, setTrackRef } = context;
414
+ const state = context.state;
289
415
 
290
- // Render props values
291
416
  const renderValues = createMemo<ColorSliderTrackRenderProps>(() => ({
292
417
  isDisabled: state.isDisabled,
293
418
  isDragging: state.isDragging,
419
+ orientation: state.orientation,
420
+ defaultStyle: (context.trackProps as { style?: JSX.CSSProperties }).style ?? {},
294
421
  }));
295
422
 
296
- // Resolve render props
297
423
  const renderProps = useRenderProps(
298
424
  {
299
425
  children: props.children,
300
426
  class: local.class,
301
427
  style: local.style,
302
- defaultClassName: 'solidaria-ColorSlider-track',
428
+ defaultClassName: "solidaria-ColorSlider-track",
303
429
  },
304
- renderValues
430
+ renderValues,
305
431
  );
306
432
 
307
- // Clean props
308
433
  const cleanTrackProps = () => {
309
- const { ref: _ref, style: _trackStyle, ...rest } = trackProps as Record<string, unknown>;
434
+ const {
435
+ ref: _ref,
436
+ style: _trackStyle,
437
+ ...rest
438
+ } = context.trackProps as Record<string, unknown>;
310
439
  return rest;
311
440
  };
312
441
 
313
- // Merge styles
314
442
  const mergedStyle = () => {
315
- const trackStyle = (trackProps as { style?: Record<string, string> }).style || {};
443
+ const trackStyle = (context.trackProps as { style?: Record<string, string> }).style || {};
316
444
  const renderStyle = renderProps.style() || {};
317
445
  return { ...trackStyle, ...renderStyle };
318
446
  };
@@ -320,12 +448,13 @@ export function ColorSliderTrack(props: ColorSliderTrackProps): JSX.Element {
320
448
  return (
321
449
  <div
322
450
  {...domProps}
323
- ref={setTrackRef}
451
+ ref={context.setTrackRef}
324
452
  {...cleanTrackProps()}
325
453
  class={renderProps.class()}
326
454
  style={mergedStyle()}
327
455
  data-disabled={state.isDisabled || undefined}
328
456
  data-dragging={state.isDragging || undefined}
457
+ data-orientation={state.orientation}
329
458
  >
330
459
  {renderProps.renderChildren()}
331
460
  </div>
@@ -336,48 +465,49 @@ export function ColorSliderTrack(props: ColorSliderTrackProps): JSX.Element {
336
465
  * The thumb element of a color slider.
337
466
  */
338
467
  export function ColorSliderThumb(props: ColorSliderThumbProps): JSX.Element {
339
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
468
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children", "ref"]);
340
469
 
341
470
  const context = useContext(ColorSliderContext);
342
471
  if (!context) {
343
- throw new Error('ColorSliderThumb must be used within a ColorSlider');
472
+ throw new Error("ColorSliderThumb must be used within a ColorSlider");
344
473
  }
345
474
 
346
- const { state, thumbProps, inputProps } = context;
475
+ const state = context.state;
347
476
 
348
- // Create focus ring
349
477
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
350
478
 
351
- // Create hover
352
479
  const { isHovered, hoverProps } = createHover({
353
480
  get isDisabled() {
354
481
  return state.isDisabled;
355
482
  },
356
483
  });
357
484
 
358
- // Render props values
359
485
  const renderValues = createMemo<ColorSliderThumbRenderProps>(() => ({
360
486
  isDisabled: state.isDisabled,
361
487
  isDragging: state.isDragging,
362
488
  isFocused: isFocused(),
363
489
  isFocusVisible: isFocusVisible(),
364
490
  isHovered: isHovered(),
491
+ color: state.getDisplayColor(),
492
+ defaultStyle: (context.thumbProps as { style?: JSX.CSSProperties }).style ?? {},
365
493
  }));
366
494
 
367
- // Resolve render props
368
495
  const renderProps = useRenderProps(
369
496
  {
370
497
  children: props.children,
371
498
  class: local.class,
372
499
  style: local.style,
373
- defaultClassName: 'solidaria-ColorSlider-thumb',
500
+ defaultClassName: "solidaria-ColorSlider-thumb",
374
501
  },
375
- renderValues
502
+ renderValues,
376
503
  );
377
504
 
378
- // Clean props
379
505
  const cleanThumbProps = () => {
380
- const { ref: _ref, style: _thumbStyle, ...rest } = thumbProps as Record<string, unknown>;
506
+ const {
507
+ ref: _ref,
508
+ style: _thumbStyle,
509
+ ...rest
510
+ } = context.thumbProps as Record<string, unknown>;
381
511
  return rest;
382
512
  };
383
513
  const cleanFocusProps = () => {
@@ -390,14 +520,13 @@ export function ColorSliderThumb(props: ColorSliderThumbProps): JSX.Element {
390
520
  };
391
521
  const mergedInputProps = () => {
392
522
  return mergeProps(
393
- inputProps as Record<string, unknown>,
394
- cleanFocusProps()
523
+ context.inputProps as Record<string, unknown>,
524
+ cleanFocusProps(),
395
525
  ) as JSX.InputHTMLAttributes<HTMLInputElement>;
396
526
  };
397
527
 
398
- // Merge styles
399
528
  const mergedStyle = () => {
400
- const thumbStyle = (thumbProps as { style?: Record<string, string> }).style || {};
529
+ const thumbStyle = (context.thumbProps as { style?: Record<string, string> }).style || {};
401
530
  const renderStyle = renderProps.style() || {};
402
531
  return { ...thumbStyle, ...renderStyle };
403
532
  };
@@ -407,6 +536,7 @@ export function ColorSliderThumb(props: ColorSliderThumbProps): JSX.Element {
407
536
  {...domProps}
408
537
  {...cleanThumbProps()}
409
538
  {...cleanHoverProps()}
539
+ ref={local.ref}
410
540
  class={renderProps.class()}
411
541
  style={mergedStyle()}
412
542
  data-disabled={state.isDisabled || undefined}
@@ -415,19 +545,16 @@ export function ColorSliderThumb(props: ColorSliderThumbProps): JSX.Element {
415
545
  data-focus-visible={isFocusVisible() || undefined}
416
546
  data-hovered={isHovered() || undefined}
417
547
  >
418
- <input {...mergedInputProps()} />
548
+ <input ref={context.setInputRef} {...mergedInputProps()} />
419
549
  {renderProps.renderChildren()}
420
550
  </div>
421
551
  );
422
552
  }
423
553
 
424
- // Attach sub-components
425
554
  ColorSlider.Track = ColorSliderTrack;
426
555
  ColorSlider.Thumb = ColorSliderThumb;
427
-
428
- // ============================================
429
- // COLOR AREA
430
- // ============================================
556
+ ColorSlider.Label = ColorSliderLabel;
557
+ ColorSlider.Output = ColorSliderOutput;
431
558
 
432
559
  export interface ColorAreaRenderProps {
433
560
  /** Whether the area is disabled. */
@@ -440,6 +567,8 @@ export interface ColorAreaRenderProps {
440
567
  yChannel: ColorChannel;
441
568
  /** The current color. */
442
569
  color: Color;
570
+ /** The default inline styles applied by the color area hook. */
571
+ defaultStyle: JSX.CSSProperties;
443
572
  }
444
573
 
445
574
  export interface ColorAreaProps extends AriaColorAreaOptions, SlotProps {
@@ -482,6 +611,8 @@ export interface ColorAreaThumbRenderProps {
482
611
  isDisabled: boolean;
483
612
  /** Whether the thumb is being dragged. */
484
613
  isDragging: boolean;
614
+ /** The current display color. */
615
+ color: Color;
485
616
  /** Whether the thumb is focused. */
486
617
  isFocused: boolean;
487
618
  /** Whether the thumb has keyboard focus. */
@@ -497,9 +628,10 @@ export interface ColorAreaThumbProps extends SlotProps {
497
628
  class?: ClassNameOrFunction<ColorAreaThumbRenderProps>;
498
629
  /** The inline style for the element. */
499
630
  style?: StyleOrFunction<ColorAreaThumbRenderProps>;
631
+ /** Ref callback for the thumb element. */
632
+ ref?: (element: HTMLDivElement) => void;
500
633
  }
501
634
 
502
- // Context
503
635
  interface ColorAreaContextValue {
504
636
  state: ColorAreaState;
505
637
  colorAreaProps: JSX.HTMLAttributes<HTMLDivElement>;
@@ -520,9 +652,19 @@ export function ColorArea(props: ColorAreaProps): JSX.Element {
520
652
  const pickerContext = useContext(ColorPickerContextInternal);
521
653
  const [local, stateProps, ariaProps, rest] = splitProps(
522
654
  props,
523
- ['children', 'class', 'style', 'slot'],
524
- ['value', 'defaultValue', 'onChange', 'onChangeEnd', 'xChannel', 'yChannel'],
525
- ['aria-label', 'aria-labelledby', 'aria-describedby', 'isDisabled']
655
+ ["children", "class", "style", "slot"],
656
+ ["value", "defaultValue", "onChange", "onChangeEnd", "xChannel", "yChannel", "colorSpace"],
657
+ [
658
+ "id",
659
+ "aria-label",
660
+ "aria-labelledby",
661
+ "aria-describedby",
662
+ "aria-details",
663
+ "isDisabled",
664
+ "xName",
665
+ "yName",
666
+ "form",
667
+ ],
526
668
  );
527
669
 
528
670
  // Create color area state
@@ -533,6 +675,7 @@ export function ColorArea(props: ColorAreaProps): JSX.Element {
533
675
  onChangeEnd: stateProps.onChangeEnd,
534
676
  xChannel: stateProps.xChannel,
535
677
  yChannel: stateProps.yChannel,
678
+ colorSpace: stateProps.colorSpace,
536
679
  isDisabled: ariaProps.isDisabled,
537
680
  }));
538
681
 
@@ -543,55 +686,83 @@ export function ColorArea(props: ColorAreaProps): JSX.Element {
543
686
  };
544
687
 
545
688
  // Create color area aria props
546
- const {
547
- colorAreaProps,
548
- gradientProps,
549
- thumbProps,
550
- xInputProps,
551
- yInputProps,
552
- } = createColorArea(
689
+ const colorAreaAria = createColorArea(
553
690
  () => ({
554
- 'aria-label': ariaProps['aria-label'],
555
- 'aria-labelledby': ariaProps['aria-labelledby'],
556
- 'aria-describedby': ariaProps['aria-describedby'],
691
+ id: ariaProps.id,
692
+ "aria-label": ariaProps["aria-label"],
693
+ "aria-labelledby": ariaProps["aria-labelledby"],
694
+ "aria-describedby": ariaProps["aria-describedby"],
695
+ "aria-details": ariaProps["aria-details"],
557
696
  isDisabled: ariaProps.isDisabled,
697
+ xName: ariaProps.xName,
698
+ yName: ariaProps.yName,
699
+ form: ariaProps.form,
558
700
  }),
559
701
  () => state,
560
- () => areaRef ?? null
702
+ () => areaRef ?? null,
561
703
  );
562
704
 
563
- // Render props values
564
705
  const renderValues = createMemo<ColorAreaRenderProps>(() => ({
565
706
  isDisabled: state.isDisabled,
566
707
  isDragging: state.isDragging,
567
708
  xChannel: state.xChannel,
568
709
  yChannel: state.yChannel,
569
710
  color: state.value,
711
+ defaultStyle: (colorAreaAria.colorAreaProps as { style?: JSX.CSSProperties }).style ?? {},
570
712
  }));
571
713
 
572
- // Resolve render props
714
+ const childRenderValues: ColorAreaRenderProps = {
715
+ get isDisabled() {
716
+ return state.isDisabled;
717
+ },
718
+ get isDragging() {
719
+ return state.isDragging;
720
+ },
721
+ get xChannel() {
722
+ return state.xChannel;
723
+ },
724
+ get yChannel() {
725
+ return state.yChannel;
726
+ },
727
+ get color() {
728
+ return state.value;
729
+ },
730
+ get defaultStyle() {
731
+ return (colorAreaAria.colorAreaProps as { style?: JSX.CSSProperties }).style ?? {};
732
+ },
733
+ };
734
+
735
+ const colorAreaChildren = () => {
736
+ const children = props.children;
737
+ return typeof children === "function" ? children(childRenderValues) : children;
738
+ };
739
+
573
740
  const renderProps = useRenderProps(
574
741
  {
575
742
  children: props.children,
576
743
  class: local.class,
577
744
  style: local.style,
578
- defaultClassName: 'solidaria-ColorArea',
745
+ defaultClassName: "solidaria-ColorArea",
579
746
  },
580
- renderValues
747
+ renderValues,
581
748
  );
582
749
 
583
- // Filter DOM props
584
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
750
+ const domProps = createMemo(() =>
751
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
752
+ );
585
753
 
586
- // Clean props
587
754
  const cleanColorAreaProps = () => {
588
- const { ref: _ref, style: _areaStyle, ...rest } = colorAreaProps as Record<string, unknown>;
755
+ const {
756
+ ref: _ref,
757
+ style: _areaStyle,
758
+ ...rest
759
+ } = colorAreaAria.colorAreaProps as Record<string, unknown>;
589
760
  return rest;
590
761
  };
591
762
 
592
- // Merge styles
593
763
  const mergedStyle = () => {
594
- const areaStyle = (colorAreaProps as { style?: Record<string, string> }).style || {};
764
+ const areaStyle =
765
+ (colorAreaAria.colorAreaProps as { style?: Record<string, string> }).style || {};
595
766
  const renderStyle = renderProps.style() || {};
596
767
  return { ...areaStyle, ...renderStyle };
597
768
  };
@@ -600,11 +771,21 @@ export function ColorArea(props: ColorAreaProps): JSX.Element {
600
771
  <ColorAreaContext.Provider
601
772
  value={{
602
773
  state,
603
- colorAreaProps,
604
- gradientProps,
605
- thumbProps,
606
- xInputProps,
607
- yInputProps,
774
+ get colorAreaProps() {
775
+ return colorAreaAria.colorAreaProps;
776
+ },
777
+ get gradientProps() {
778
+ return colorAreaAria.gradientProps;
779
+ },
780
+ get thumbProps() {
781
+ return colorAreaAria.thumbProps;
782
+ },
783
+ get xInputProps() {
784
+ return colorAreaAria.xInputProps;
785
+ },
786
+ get yInputProps() {
787
+ return colorAreaAria.yInputProps;
788
+ },
608
789
  areaRef,
609
790
  setAreaRef,
610
791
  }}
@@ -615,10 +796,11 @@ export function ColorArea(props: ColorAreaProps): JSX.Element {
615
796
  {...cleanColorAreaProps()}
616
797
  class={renderProps.class()}
617
798
  style={mergedStyle()}
799
+ slot={local.slot ?? undefined}
618
800
  data-disabled={state.isDisabled || undefined}
619
801
  data-dragging={state.isDragging || undefined}
620
802
  >
621
- {renderProps.renderChildren()}
803
+ {colorAreaChildren()}
622
804
  </div>
623
805
  </ColorAreaContext.Provider>
624
806
  );
@@ -628,40 +810,40 @@ export function ColorArea(props: ColorAreaProps): JSX.Element {
628
810
  * The gradient background of a color area.
629
811
  */
630
812
  export function ColorAreaGradient(props: ColorAreaGradientProps): JSX.Element {
631
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
813
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
632
814
 
633
815
  const context = useContext(ColorAreaContext);
634
816
  if (!context) {
635
- throw new Error('ColorAreaGradient must be used within a ColorArea');
817
+ throw new Error("ColorAreaGradient must be used within a ColorArea");
636
818
  }
637
819
 
638
- const { state, gradientProps } = context;
820
+ const { state } = context;
639
821
 
640
- // Render props values
641
822
  const renderValues = createMemo<ColorAreaGradientRenderProps>(() => ({
642
823
  isDisabled: state.isDisabled,
643
824
  }));
644
825
 
645
- // Resolve render props
646
826
  const renderProps = useRenderProps(
647
827
  {
648
828
  children: props.children,
649
829
  class: local.class,
650
830
  style: local.style,
651
- defaultClassName: 'solidaria-ColorArea-gradient',
831
+ defaultClassName: "solidaria-ColorArea-gradient",
652
832
  },
653
- renderValues
833
+ renderValues,
654
834
  );
655
835
 
656
- // Clean props
657
836
  const cleanGradientProps = () => {
658
- const { ref: _ref, style: _gradStyle, ...rest } = gradientProps as Record<string, unknown>;
837
+ const {
838
+ ref: _ref,
839
+ style: _gradStyle,
840
+ ...rest
841
+ } = context.gradientProps as Record<string, unknown>;
659
842
  return rest;
660
843
  };
661
844
 
662
- // Merge styles
663
845
  const mergedStyle = () => {
664
- const gradStyle = (gradientProps as { style?: Record<string, string> }).style || {};
846
+ const gradStyle = (context.gradientProps as { style?: Record<string, string> }).style || {};
665
847
  const renderStyle = renderProps.style() || {};
666
848
  return { ...gradStyle, ...renderStyle };
667
849
  };
@@ -683,48 +865,50 @@ export function ColorAreaGradient(props: ColorAreaGradientProps): JSX.Element {
683
865
  * The thumb element of a color area.
684
866
  */
685
867
  export function ColorAreaThumb(props: ColorAreaThumbProps): JSX.Element {
686
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
868
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children", "ref"]);
687
869
 
688
870
  const context = useContext(ColorAreaContext);
689
871
  if (!context) {
690
- throw new Error('ColorAreaThumb must be used within a ColorArea');
872
+ throw new Error("ColorAreaThumb must be used within a ColorArea");
691
873
  }
692
874
 
693
- const { state, thumbProps, xInputProps, yInputProps } = context;
875
+ const { state } = context;
694
876
 
695
- // Create focus ring
696
877
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
878
+ let xInputRef: HTMLInputElement | undefined;
879
+ let yInputRef: HTMLInputElement | undefined;
697
880
 
698
- // Create hover
699
881
  const { isHovered, hoverProps } = createHover({
700
882
  get isDisabled() {
701
883
  return state.isDisabled;
702
884
  },
703
885
  });
704
886
 
705
- // Render props values
706
887
  const renderValues = createMemo<ColorAreaThumbRenderProps>(() => ({
707
888
  isDisabled: state.isDisabled,
708
889
  isDragging: state.isDragging,
890
+ color: state.getDisplayColor(),
709
891
  isFocused: isFocused(),
710
892
  isFocusVisible: isFocusVisible(),
711
893
  isHovered: isHovered(),
712
894
  }));
713
895
 
714
- // Resolve render props
715
896
  const renderProps = useRenderProps(
716
897
  {
717
898
  children: props.children,
718
899
  class: local.class,
719
900
  style: local.style,
720
- defaultClassName: 'solidaria-ColorArea-thumb',
901
+ defaultClassName: "solidaria-ColorArea-thumb",
721
902
  },
722
- renderValues
903
+ renderValues,
723
904
  );
724
905
 
725
- // Clean props
726
906
  const cleanThumbProps = () => {
727
- const { ref: _ref, style: _thumbStyle, ...rest } = thumbProps as Record<string, unknown>;
907
+ const {
908
+ ref: _ref,
909
+ style: _thumbStyle,
910
+ ...rest
911
+ } = context.thumbProps as Record<string, unknown>;
728
912
  return rest;
729
913
  };
730
914
  const cleanFocusProps = () => {
@@ -736,28 +920,44 @@ export function ColorAreaThumb(props: ColorAreaThumbProps): JSX.Element {
736
920
  return rest;
737
921
  };
738
922
  const mergedXInputProps = () => {
739
- return mergeProps(
740
- xInputProps as Record<string, unknown>,
741
- cleanFocusProps()
742
- ) as JSX.InputHTMLAttributes<HTMLInputElement>;
923
+ const { value: _value, ...inputProps } = context.xInputProps as Record<string, unknown>;
924
+ return mergeProps(inputProps, cleanFocusProps()) as JSX.InputHTMLAttributes<HTMLInputElement>;
743
925
  };
744
926
  const mergedYInputProps = () => {
745
- return mergeProps(
746
- yInputProps as Record<string, unknown>,
747
- cleanFocusProps()
748
- ) as JSX.InputHTMLAttributes<HTMLInputElement>;
927
+ const { value: _value, ...inputProps } = context.yInputProps as Record<string, unknown>;
928
+ return mergeProps(inputProps, cleanFocusProps()) as JSX.InputHTMLAttributes<HTMLInputElement>;
749
929
  };
750
930
 
751
- // Merge styles
752
931
  const mergedStyle = () => {
753
- const thumbStyle = (thumbProps as { style?: Record<string, string> }).style || {};
932
+ const thumbStyle = (context.thumbProps as { style?: Record<string, string> }).style || {};
754
933
  const renderStyle = renderProps.style() || {};
755
934
  return { ...thumbStyle, ...renderStyle };
756
935
  };
757
936
 
937
+ const syncInputValue = (input: HTMLInputElement | undefined, value: number) => {
938
+ const nextValue = String(value);
939
+ const update = () => {
940
+ if (input && input.value !== nextValue) {
941
+ input.value = nextValue;
942
+ }
943
+ };
944
+
945
+ update();
946
+ queueMicrotask(update);
947
+ };
948
+
949
+ createEffect(() => {
950
+ syncInputValue(xInputRef, state.getXValue());
951
+ });
952
+
953
+ createEffect(() => {
954
+ syncInputValue(yInputRef, state.getYValue());
955
+ });
956
+
758
957
  return (
759
958
  <div
760
959
  {...domProps}
960
+ ref={local.ref}
761
961
  {...cleanThumbProps()}
762
962
  {...cleanHoverProps()}
763
963
  class={renderProps.class()}
@@ -768,21 +968,28 @@ export function ColorAreaThumb(props: ColorAreaThumbProps): JSX.Element {
768
968
  data-focus-visible={isFocusVisible() || undefined}
769
969
  data-hovered={isHovered() || undefined}
770
970
  >
771
- <input {...mergedXInputProps()} />
772
- <input {...mergedYInputProps()} />
971
+ <input
972
+ {...mergedXInputProps()}
973
+ ref={(el) => {
974
+ xInputRef = el;
975
+ syncInputValue(el, state.getXValue());
976
+ }}
977
+ />
978
+ <input
979
+ {...mergedYInputProps()}
980
+ ref={(el) => {
981
+ yInputRef = el;
982
+ syncInputValue(el, state.getYValue());
983
+ }}
984
+ />
773
985
  {renderProps.renderChildren()}
774
986
  </div>
775
987
  );
776
988
  }
777
989
 
778
- // Attach sub-components
779
990
  ColorArea.Gradient = ColorAreaGradient;
780
991
  ColorArea.Thumb = ColorAreaThumb;
781
992
 
782
- // ============================================
783
- // COLOR WHEEL
784
- // ============================================
785
-
786
993
  export interface ColorWheelRenderProps {
787
994
  /** Whether the wheel is disabled. */
788
995
  isDisabled: boolean;
@@ -792,6 +999,8 @@ export interface ColorWheelRenderProps {
792
999
  hue: number;
793
1000
  /** The current color. */
794
1001
  color: Color;
1002
+ /** The default inline styles applied by the color wheel hook. */
1003
+ defaultStyle: JSX.CSSProperties;
795
1004
  }
796
1005
 
797
1006
  export interface ColorWheelProps extends AriaColorWheelOptions, SlotProps {
@@ -816,6 +1025,8 @@ export interface ColorWheelTrackRenderProps {
816
1025
  isDisabled: boolean;
817
1026
  /** Whether the wheel is being dragged. */
818
1027
  isDragging: boolean;
1028
+ /** The default inline styles applied by the color wheel hook. */
1029
+ defaultStyle: JSX.CSSProperties;
819
1030
  }
820
1031
 
821
1032
  export interface ColorWheelTrackProps extends SlotProps {
@@ -832,12 +1043,16 @@ export interface ColorWheelThumbRenderProps {
832
1043
  isDisabled: boolean;
833
1044
  /** Whether the thumb is being dragged. */
834
1045
  isDragging: boolean;
1046
+ /** The current display color. */
1047
+ color: Color;
835
1048
  /** Whether the thumb is focused. */
836
1049
  isFocused: boolean;
837
1050
  /** Whether the thumb has keyboard focus. */
838
1051
  isFocusVisible: boolean;
839
1052
  /** Whether the thumb is hovered. */
840
1053
  isHovered: boolean;
1054
+ /** The default inline styles applied by the color wheel hook. */
1055
+ defaultStyle: JSX.CSSProperties;
841
1056
  }
842
1057
 
843
1058
  export interface ColorWheelThumbProps extends SlotProps {
@@ -847,9 +1062,10 @@ export interface ColorWheelThumbProps extends SlotProps {
847
1062
  class?: ClassNameOrFunction<ColorWheelThumbRenderProps>;
848
1063
  /** The inline style for the element. */
849
1064
  style?: StyleOrFunction<ColorWheelThumbRenderProps>;
1065
+ /** Ref callback for the thumb element. */
1066
+ ref?: (element: HTMLDivElement) => void;
850
1067
  }
851
1068
 
852
- // Context
853
1069
  interface ColorWheelContextValue {
854
1070
  state: ColorWheelState;
855
1071
  trackProps: JSX.HTMLAttributes<HTMLDivElement>;
@@ -868,9 +1084,21 @@ export function ColorWheel(props: ColorWheelProps): JSX.Element {
868
1084
  const pickerContext = useContext(ColorPickerContextInternal);
869
1085
  const [local, stateProps, ariaProps, rest] = splitProps(
870
1086
  props,
871
- ['children', 'class', 'style', 'slot'],
872
- ['value', 'defaultValue', 'onChange', 'onChangeEnd'],
873
- ['aria-label', 'aria-labelledby', 'aria-describedby', 'isDisabled']
1087
+ ["children", "class", "style", "slot"],
1088
+ ["value", "defaultValue", "onChange", "onChangeEnd"],
1089
+ [
1090
+ "id",
1091
+ "aria-label",
1092
+ "aria-labelledby",
1093
+ "aria-describedby",
1094
+ "aria-details",
1095
+ "aria-errormessage",
1096
+ "isDisabled",
1097
+ "name",
1098
+ "form",
1099
+ "outerRadius",
1100
+ "innerRadius",
1101
+ ],
874
1102
  );
875
1103
 
876
1104
  // Create color wheel state
@@ -882,65 +1110,77 @@ export function ColorWheel(props: ColorWheelProps): JSX.Element {
882
1110
  isDisabled: ariaProps.isDisabled,
883
1111
  }));
884
1112
 
885
- // Wheel ref
886
1113
  let wheelRef: HTMLDivElement | undefined;
887
1114
  const setWheelRef = (el: HTMLDivElement) => {
888
1115
  wheelRef = el;
889
1116
  };
890
1117
 
891
1118
  // Create color wheel aria props
892
- const {
893
- trackProps,
894
- thumbProps,
895
- inputProps,
896
- } = createColorWheel(
1119
+ const colorWheelAria = createColorWheel(
897
1120
  () => ({
898
- 'aria-label': ariaProps['aria-label'],
899
- 'aria-labelledby': ariaProps['aria-labelledby'],
900
- 'aria-describedby': ariaProps['aria-describedby'],
1121
+ id: ariaProps.id,
1122
+ "aria-label": ariaProps["aria-label"],
1123
+ "aria-labelledby": ariaProps["aria-labelledby"],
1124
+ "aria-describedby": ariaProps["aria-describedby"],
1125
+ "aria-details": ariaProps["aria-details"],
1126
+ "aria-errormessage": ariaProps["aria-errormessage"],
901
1127
  isDisabled: ariaProps.isDisabled,
1128
+ name: ariaProps.name,
1129
+ form: ariaProps.form,
1130
+ outerRadius: ariaProps.outerRadius,
1131
+ innerRadius: ariaProps.innerRadius,
902
1132
  }),
903
1133
  () => state,
904
- () => wheelRef ?? null
1134
+ () => wheelRef ?? null,
905
1135
  );
906
1136
 
907
- // Render props values
908
1137
  const renderValues = createMemo<ColorWheelRenderProps>(() => ({
909
1138
  isDisabled: state.isDisabled,
910
1139
  isDragging: state.isDragging,
911
1140
  hue: state.getHue(),
912
1141
  color: state.value,
1142
+ defaultStyle: { position: "relative" },
913
1143
  }));
914
1144
 
915
- // Resolve render props
916
1145
  const renderProps = useRenderProps(
917
1146
  {
918
1147
  children: props.children,
919
1148
  class: local.class,
920
1149
  style: local.style,
921
- defaultClassName: 'solidaria-ColorWheel',
1150
+ defaultClassName: "solidaria-ColorWheel",
922
1151
  },
923
- renderValues
1152
+ renderValues,
924
1153
  );
925
1154
 
926
- // Filter DOM props
927
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
1155
+ const domProps = createMemo(() =>
1156
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
1157
+ );
928
1158
 
929
1159
  return (
930
1160
  <ColorWheelContext.Provider
931
1161
  value={{
932
1162
  state,
933
- trackProps,
934
- thumbProps,
935
- inputProps,
936
- wheelRef,
1163
+ get trackProps() {
1164
+ return colorWheelAria.trackProps;
1165
+ },
1166
+ get thumbProps() {
1167
+ return colorWheelAria.thumbProps;
1168
+ },
1169
+ get inputProps() {
1170
+ return colorWheelAria.inputProps;
1171
+ },
1172
+ get wheelRef() {
1173
+ return wheelRef;
1174
+ },
937
1175
  setWheelRef,
938
1176
  }}
939
1177
  >
940
1178
  <div
1179
+ ref={setWheelRef}
941
1180
  {...domProps()}
942
1181
  class={renderProps.class()}
943
1182
  style={renderProps.style()}
1183
+ slot={local.slot || undefined}
944
1184
  data-disabled={state.isDisabled || undefined}
945
1185
  data-dragging={state.isDragging || undefined}
946
1186
  >
@@ -954,41 +1194,42 @@ export function ColorWheel(props: ColorWheelProps): JSX.Element {
954
1194
  * The track element of a color wheel.
955
1195
  */
956
1196
  export function ColorWheelTrack(props: ColorWheelTrackProps): JSX.Element {
957
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
1197
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children"]);
958
1198
 
959
1199
  const context = useContext(ColorWheelContext);
960
1200
  if (!context) {
961
- throw new Error('ColorWheelTrack must be used within a ColorWheel');
1201
+ throw new Error("ColorWheelTrack must be used within a ColorWheel");
962
1202
  }
963
1203
 
964
- const { state, trackProps, setWheelRef } = context;
1204
+ const state = context.state;
965
1205
 
966
- // Render props values
967
1206
  const renderValues = createMemo<ColorWheelTrackRenderProps>(() => ({
968
1207
  isDisabled: state.isDisabled,
969
1208
  isDragging: state.isDragging,
1209
+ defaultStyle: (context.trackProps as { style?: JSX.CSSProperties }).style ?? {},
970
1210
  }));
971
1211
 
972
- // Resolve render props
973
1212
  const renderProps = useRenderProps(
974
1213
  {
975
1214
  children: props.children,
976
1215
  class: local.class,
977
1216
  style: local.style,
978
- defaultClassName: 'solidaria-ColorWheel-track',
1217
+ defaultClassName: "solidaria-ColorWheel-track",
979
1218
  },
980
- renderValues
1219
+ renderValues,
981
1220
  );
982
1221
 
983
- // Clean props
984
1222
  const cleanTrackProps = () => {
985
- const { ref: _ref, style: _trackStyle, ...rest } = trackProps as Record<string, unknown>;
1223
+ const {
1224
+ ref: _ref,
1225
+ style: _trackStyle,
1226
+ ...rest
1227
+ } = context.trackProps as Record<string, unknown>;
986
1228
  return rest;
987
1229
  };
988
1230
 
989
- // Merge styles
990
1231
  const mergedStyle = () => {
991
- const trackStyle = (trackProps as { style?: Record<string, string> }).style || {};
1232
+ const trackStyle = (context.trackProps as { style?: Record<string, string> }).style || {};
992
1233
  const renderStyle = renderProps.style() || {};
993
1234
  return { ...trackStyle, ...renderStyle };
994
1235
  };
@@ -996,7 +1237,6 @@ export function ColorWheelTrack(props: ColorWheelTrackProps): JSX.Element {
996
1237
  return (
997
1238
  <div
998
1239
  {...domProps}
999
- ref={setWheelRef}
1000
1240
  {...cleanTrackProps()}
1001
1241
  class={renderProps.class()}
1002
1242
  style={mergedStyle()}
@@ -1012,48 +1252,49 @@ export function ColorWheelTrack(props: ColorWheelTrackProps): JSX.Element {
1012
1252
  * The thumb element of a color wheel.
1013
1253
  */
1014
1254
  export function ColorWheelThumb(props: ColorWheelThumbProps): JSX.Element {
1015
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
1255
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children", "ref"]);
1016
1256
 
1017
1257
  const context = useContext(ColorWheelContext);
1018
1258
  if (!context) {
1019
- throw new Error('ColorWheelThumb must be used within a ColorWheel');
1259
+ throw new Error("ColorWheelThumb must be used within a ColorWheel");
1020
1260
  }
1021
1261
 
1022
- const { state, thumbProps, inputProps } = context;
1262
+ const state = context.state;
1023
1263
 
1024
- // Create focus ring
1025
1264
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
1026
1265
 
1027
- // Create hover
1028
1266
  const { isHovered, hoverProps } = createHover({
1029
1267
  get isDisabled() {
1030
1268
  return state.isDisabled;
1031
1269
  },
1032
1270
  });
1033
1271
 
1034
- // Render props values
1035
1272
  const renderValues = createMemo<ColorWheelThumbRenderProps>(() => ({
1036
1273
  isDisabled: state.isDisabled,
1037
1274
  isDragging: state.isDragging,
1275
+ color: state.getDisplayColor(),
1038
1276
  isFocused: isFocused(),
1039
1277
  isFocusVisible: isFocusVisible(),
1040
1278
  isHovered: isHovered(),
1279
+ defaultStyle: (context.thumbProps as { style?: JSX.CSSProperties }).style ?? {},
1041
1280
  }));
1042
1281
 
1043
- // Resolve render props
1044
1282
  const renderProps = useRenderProps(
1045
1283
  {
1046
1284
  children: props.children,
1047
1285
  class: local.class,
1048
1286
  style: local.style,
1049
- defaultClassName: 'solidaria-ColorWheel-thumb',
1287
+ defaultClassName: "solidaria-ColorWheel-thumb",
1050
1288
  },
1051
- renderValues
1289
+ renderValues,
1052
1290
  );
1053
1291
 
1054
- // Clean props
1055
1292
  const cleanThumbProps = () => {
1056
- const { ref: _ref, style: _thumbStyle, ...rest } = thumbProps as Record<string, unknown>;
1293
+ const {
1294
+ ref: _ref,
1295
+ style: _thumbStyle,
1296
+ ...rest
1297
+ } = context.thumbProps as Record<string, unknown>;
1057
1298
  return rest;
1058
1299
  };
1059
1300
  const cleanFocusProps = () => {
@@ -1066,14 +1307,13 @@ export function ColorWheelThumb(props: ColorWheelThumbProps): JSX.Element {
1066
1307
  };
1067
1308
  const mergedInputProps = () => {
1068
1309
  return mergeProps(
1069
- inputProps as Record<string, unknown>,
1070
- cleanFocusProps()
1310
+ context.inputProps as Record<string, unknown>,
1311
+ cleanFocusProps(),
1071
1312
  ) as JSX.InputHTMLAttributes<HTMLInputElement>;
1072
1313
  };
1073
1314
 
1074
- // Merge styles
1075
1315
  const mergedStyle = () => {
1076
- const thumbStyle = (thumbProps as { style?: Record<string, string> }).style || {};
1316
+ const thumbStyle = (context.thumbProps as { style?: Record<string, string> }).style || {};
1077
1317
  const renderStyle = renderProps.style() || {};
1078
1318
  return { ...thumbStyle, ...renderStyle };
1079
1319
  };
@@ -1081,6 +1321,7 @@ export function ColorWheelThumb(props: ColorWheelThumbProps): JSX.Element {
1081
1321
  return (
1082
1322
  <div
1083
1323
  {...domProps}
1324
+ ref={local.ref}
1084
1325
  {...cleanThumbProps()}
1085
1326
  {...cleanHoverProps()}
1086
1327
  class={renderProps.class()}
@@ -1097,25 +1338,22 @@ export function ColorWheelThumb(props: ColorWheelThumbProps): JSX.Element {
1097
1338
  );
1098
1339
  }
1099
1340
 
1100
- // Attach sub-components
1101
1341
  ColorWheel.Track = ColorWheelTrack;
1102
1342
  ColorWheel.Thumb = ColorWheelThumb;
1103
1343
 
1104
- // ============================================
1105
- // COLOR FIELD
1106
- // ============================================
1107
-
1108
1344
  export interface ColorFieldRenderProps {
1109
1345
  /** Whether the field is disabled. */
1110
1346
  isDisabled: boolean;
1111
1347
  /** Whether the field is read-only. */
1112
1348
  isReadOnly: boolean;
1349
+ /** Whether the field is required. */
1350
+ isRequired: boolean;
1113
1351
  /** Whether the input value is invalid. */
1114
1352
  isInvalid: boolean;
1115
1353
  /** The current color value (null if invalid). */
1116
1354
  color: Color | null;
1117
- /** The color channel being edited (if single channel mode). */
1118
- channel: ColorChannel | undefined;
1355
+ /** The color channel being edited, or "hex" for full color mode. */
1356
+ channel: ColorChannel | "hex";
1119
1357
  }
1120
1358
 
1121
1359
  export interface ColorFieldProps extends AriaColorFieldOptions, SlotProps {
@@ -1127,10 +1365,16 @@ export interface ColorFieldProps extends AriaColorFieldOptions, SlotProps {
1127
1365
  onChange?: (color: Color | null) => void;
1128
1366
  /** The color channel to edit (for single channel mode). */
1129
1367
  channel?: ColorChannel;
1368
+ /** The color space to use for channel mode. */
1369
+ colorSpace?: ColorSpace;
1130
1370
  /** The color format for parsing/displaying. */
1131
1371
  colorFormat?: ColorFormat;
1132
1372
  /** A visible label for the field. */
1133
1373
  label?: JSX.Element;
1374
+ /** Description text for the field. */
1375
+ description?: JSX.Element;
1376
+ /** Error message for the field. */
1377
+ errorMessage?: JSX.Element;
1134
1378
  /** The children of the component. */
1135
1379
  children?: RenderChildren<ColorFieldRenderProps>;
1136
1380
  /** The CSS className for the element. */
@@ -1161,13 +1405,18 @@ export interface ColorFieldInputProps extends SlotProps {
1161
1405
  class?: ClassNameOrFunction<ColorFieldInputRenderProps>;
1162
1406
  /** The inline style for the element. */
1163
1407
  style?: StyleOrFunction<ColorFieldInputRenderProps>;
1408
+ /** Ref callback for the input element. */
1409
+ ref?: (element: HTMLInputElement) => void;
1164
1410
  }
1165
1411
 
1166
- // Context
1167
1412
  interface ColorFieldContextValue {
1168
1413
  state: ColorFieldState;
1169
1414
  inputProps: JSX.InputHTMLAttributes<HTMLInputElement>;
1170
1415
  labelProps: JSX.LabelHTMLAttributes<HTMLLabelElement>;
1416
+ descriptionProps: JSX.HTMLAttributes<HTMLElement>;
1417
+ errorMessageProps: JSX.HTMLAttributes<HTMLElement>;
1418
+ setInputRef: (el: HTMLInputElement) => void;
1419
+ setLabelElement: (isPresent: boolean) => void;
1171
1420
  }
1172
1421
 
1173
1422
  export const ColorFieldContext = createContext<ColorFieldContextValue | null>(null);
@@ -1179,92 +1428,218 @@ export function ColorField(props: ColorFieldProps): JSX.Element {
1179
1428
  const pickerContext = useContext(ColorPickerContextInternal);
1180
1429
  const [local, stateProps, ariaProps, rest] = splitProps(
1181
1430
  props,
1182
- ['children', 'class', 'style', 'slot', 'label'],
1183
- ['value', 'defaultValue', 'onChange', 'channel', 'colorFormat'],
1184
- ['aria-label', 'aria-labelledby', 'aria-describedby', 'isDisabled', 'isReadOnly']
1431
+ ["children", "class", "style", "slot", "label", "description", "errorMessage"],
1432
+ ["value", "defaultValue", "onChange", "channel", "colorSpace", "colorFormat"],
1433
+ [
1434
+ "id",
1435
+ "aria-label",
1436
+ "aria-labelledby",
1437
+ "aria-describedby",
1438
+ "aria-details",
1439
+ "aria-errormessage",
1440
+ "name",
1441
+ "form",
1442
+ "isWheelDisabled",
1443
+ "isDisabled",
1444
+ "isReadOnly",
1445
+ "isRequired",
1446
+ "isInvalid",
1447
+ "validationBehavior",
1448
+ "autoFocus",
1449
+ "excludeFromTabOrder",
1450
+ "placeholder",
1451
+ ],
1185
1452
  );
1453
+ const [hasRegisteredLabelElement, setHasRegisteredLabelElement] = createSignal(false);
1186
1454
 
1187
1455
  // Create color field state
1188
1456
  const state = createColorFieldState(() => ({
1189
1457
  value: stateProps.value ?? pickerContext?.value,
1190
1458
  defaultValue: stateProps.defaultValue,
1191
- onChange: stateProps.onChange ?? ((color) => {
1192
- if (color) {
1193
- pickerContext?.onChange?.(color);
1194
- }
1195
- }),
1459
+ onChange:
1460
+ stateProps.onChange ??
1461
+ ((color) => {
1462
+ if (color) {
1463
+ pickerContext?.onChange?.(color);
1464
+ }
1465
+ }),
1196
1466
  channel: stateProps.channel,
1467
+ colorSpace: stateProps.colorSpace,
1197
1468
  colorFormat: stateProps.colorFormat,
1198
1469
  isDisabled: ariaProps.isDisabled,
1199
1470
  isReadOnly: ariaProps.isReadOnly,
1471
+ isInvalid: ariaProps.isInvalid,
1472
+ isRequired: ariaProps.isRequired,
1200
1473
  }));
1201
1474
 
1202
1475
  // Input ref
1203
1476
  let inputRef: HTMLInputElement | undefined;
1477
+ const setInputRef = (el: HTMLInputElement) => {
1478
+ inputRef = el;
1479
+ };
1204
1480
 
1205
1481
  // Create color field aria props
1206
- const {
1207
- inputProps,
1208
- labelProps,
1209
- } = createColorField(
1482
+ const colorFieldAria = createColorField(
1210
1483
  () => ({
1211
- 'aria-label': ariaProps['aria-label'],
1212
- 'aria-labelledby': ariaProps['aria-labelledby'],
1213
- 'aria-describedby': ariaProps['aria-describedby'],
1484
+ id: ariaProps.id,
1485
+ "aria-label": ariaProps["aria-label"],
1486
+ "aria-labelledby": ariaProps["aria-labelledby"],
1487
+ "aria-describedby": ariaProps["aria-describedby"],
1488
+ "aria-details": ariaProps["aria-details"],
1489
+ "aria-errormessage": ariaProps["aria-errormessage"],
1490
+ name: ariaProps.name,
1491
+ form: ariaProps.form,
1492
+ isWheelDisabled: ariaProps.isWheelDisabled,
1214
1493
  isDisabled: ariaProps.isDisabled,
1215
1494
  isReadOnly: ariaProps.isReadOnly,
1495
+ isRequired: ariaProps.isRequired,
1496
+ isInvalid: ariaProps.isInvalid,
1497
+ validationBehavior: ariaProps.validationBehavior,
1498
+ autoFocus: ariaProps.autoFocus,
1499
+ excludeFromTabOrder: ariaProps.excludeFromTabOrder,
1500
+ placeholder: ariaProps.placeholder,
1216
1501
  channel: stateProps.channel,
1502
+ colorSpace: stateProps.colorSpace,
1217
1503
  }),
1218
1504
  () => state,
1219
- () => inputRef ?? null
1505
+ () => inputRef ?? null,
1506
+ );
1507
+
1508
+ const describedBy = () => {
1509
+ const invalid = ariaProps.isInvalid || state.isInvalid;
1510
+ const ids = [
1511
+ ariaProps["aria-describedby"],
1512
+ local.description && !invalid ? colorFieldAria.descriptionProps.id : undefined,
1513
+ invalid && local.errorMessage ? colorFieldAria.errorMessageProps.id : undefined,
1514
+ ];
1515
+ return ids.filter(Boolean).join(" ") || undefined;
1516
+ };
1517
+
1518
+ const fieldInputProps = () => {
1519
+ const labelledBy =
1520
+ ariaProps["aria-labelledby"] ??
1521
+ (!ariaProps["aria-label"] && (local.label || hasRegisteredLabelElement())
1522
+ ? colorFieldAria.labelProps.id
1523
+ : undefined);
1524
+ return {
1525
+ ...colorFieldAria.inputProps,
1526
+ "aria-label": labelledBy ? undefined : colorFieldAria.inputProps["aria-label"],
1527
+ "aria-labelledby": labelledBy,
1528
+ "aria-describedby": describedBy(),
1529
+ "aria-errormessage":
1530
+ (ariaProps.isInvalid || state.isInvalid) && local.errorMessage
1531
+ ? colorFieldAria.errorMessageProps.id
1532
+ : colorFieldAria.inputProps["aria-errormessage"],
1533
+ } as JSX.InputHTMLAttributes<HTMLInputElement>;
1534
+ };
1535
+
1536
+ const hiddenInputValue = createMemo(() =>
1537
+ Number.isNaN(state.numberValue) ? "" : String(state.numberValue),
1220
1538
  );
1221
1539
 
1222
- // Render props values
1223
1540
  const renderValues = createMemo<ColorFieldRenderProps>(() => ({
1224
1541
  isDisabled: state.isDisabled,
1225
1542
  isReadOnly: state.isReadOnly,
1543
+ isRequired: state.isRequired,
1226
1544
  isInvalid: state.isInvalid,
1227
1545
  color: state.value,
1228
- channel: state.channel,
1546
+ channel: state.channel ?? "hex",
1229
1547
  }));
1230
1548
 
1231
- // Resolve render props
1232
1549
  const renderProps = useRenderProps(
1233
1550
  {
1234
1551
  children: props.children,
1235
1552
  class: local.class,
1236
1553
  style: local.style,
1237
- defaultClassName: 'solidaria-ColorField',
1554
+ defaultClassName: "solidaria-ColorField",
1238
1555
  },
1239
- renderValues
1556
+ renderValues,
1240
1557
  );
1241
1558
 
1242
- // Filter DOM props
1243
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
1559
+ const childRenderValues: ColorFieldRenderProps = {
1560
+ get isDisabled() {
1561
+ return state.isDisabled;
1562
+ },
1563
+ get isReadOnly() {
1564
+ return state.isReadOnly;
1565
+ },
1566
+ get isRequired() {
1567
+ return state.isRequired;
1568
+ },
1569
+ get isInvalid() {
1570
+ return state.isInvalid;
1571
+ },
1572
+ get color() {
1573
+ return state.value;
1574
+ },
1575
+ get channel() {
1576
+ return state.channel ?? "hex";
1577
+ },
1578
+ };
1579
+
1580
+ let hasRenderedChildren = false;
1581
+ let renderedChildren: JSX.Element;
1582
+ const renderChildren = () => {
1583
+ if (!hasRenderedChildren) {
1584
+ const children = local.children;
1585
+ renderedChildren =
1586
+ typeof children === "function" ? untrack(() => children(childRenderValues)) : children;
1587
+ hasRenderedChildren = true;
1588
+ }
1589
+ return renderedChildren;
1590
+ };
1591
+
1592
+ const domProps = createMemo(() =>
1593
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
1594
+ );
1244
1595
 
1245
1596
  return (
1246
1597
  <ColorFieldContext.Provider
1247
1598
  value={{
1248
1599
  state,
1249
- inputProps,
1250
- labelProps,
1600
+ get inputProps() {
1601
+ return fieldInputProps();
1602
+ },
1603
+ get labelProps() {
1604
+ return colorFieldAria.labelProps;
1605
+ },
1606
+ get descriptionProps() {
1607
+ return colorFieldAria.descriptionProps;
1608
+ },
1609
+ get errorMessageProps() {
1610
+ return colorFieldAria.errorMessageProps;
1611
+ },
1612
+ setInputRef,
1613
+ setLabelElement: setHasRegisteredLabelElement,
1251
1614
  }}
1252
1615
  >
1253
- <div
1254
- {...domProps()}
1255
- class={renderProps.class()}
1256
- style={renderProps.style()}
1257
- data-disabled={state.isDisabled || undefined}
1258
- data-readonly={state.isReadOnly || undefined}
1259
- data-invalid={state.isInvalid || undefined}
1260
- >
1261
- {/* Label */}
1262
- <Show when={local.label}>
1263
- <label {...labelProps}>{local.label}</label>
1616
+ <>
1617
+ <div
1618
+ {...domProps()}
1619
+ class={renderProps.class()}
1620
+ style={renderProps.style()}
1621
+ slot={local.slot ?? undefined}
1622
+ data-disabled={state.isDisabled || undefined}
1623
+ data-readonly={state.isReadOnly || undefined}
1624
+ data-invalid={state.isInvalid || undefined}
1625
+ data-required={state.isRequired || undefined}
1626
+ data-channel={state.channel ?? "hex"}
1627
+ >
1628
+ <Show when={local.label}>
1629
+ <label {...colorFieldAria.labelProps}>{local.label}</label>
1630
+ </Show>
1631
+
1632
+ {renderChildren()}
1633
+ </div>
1634
+ <Show when={state.channel && ariaProps.name}>
1635
+ <input
1636
+ type="hidden"
1637
+ name={ariaProps.name}
1638
+ form={ariaProps.form}
1639
+ value={hiddenInputValue()}
1640
+ />
1264
1641
  </Show>
1265
-
1266
- {renderProps.renderChildren()}
1267
- </div>
1642
+ </>
1268
1643
  </ColorFieldContext.Provider>
1269
1644
  );
1270
1645
  }
@@ -1273,26 +1648,24 @@ export function ColorField(props: ColorFieldProps): JSX.Element {
1273
1648
  * The input element of a color field.
1274
1649
  */
1275
1650
  export function ColorFieldInput(props: ColorFieldInputProps): JSX.Element {
1276
- const [local, domProps] = splitProps(props, ['class', 'style', 'slot', 'children']);
1651
+ const [local, domProps] = splitProps(props, ["class", "style", "slot", "children", "ref"]);
1277
1652
 
1278
1653
  const context = useContext(ColorFieldContext);
1279
1654
  if (!context) {
1280
- throw new Error('ColorFieldInput must be used within a ColorField');
1655
+ throw new Error("ColorFieldInput must be used within a ColorField");
1281
1656
  }
1282
1657
 
1283
- const { state, inputProps } = context;
1658
+ const state = context.state;
1659
+ const inputValue = createMemo(() => state.inputValue);
1284
1660
 
1285
- // Create focus ring
1286
1661
  const { isFocused, isFocusVisible, focusProps } = createFocusRing();
1287
1662
 
1288
- // Create hover
1289
1663
  const { isHovered, hoverProps } = createHover({
1290
1664
  get isDisabled() {
1291
1665
  return state.isDisabled;
1292
1666
  },
1293
1667
  });
1294
1668
 
1295
- // Render props values
1296
1669
  const renderValues = createMemo<ColorFieldInputRenderProps>(() => ({
1297
1670
  isDisabled: state.isDisabled,
1298
1671
  isReadOnly: state.isReadOnly,
@@ -1302,20 +1675,23 @@ export function ColorFieldInput(props: ColorFieldInputProps): JSX.Element {
1302
1675
  isHovered: isHovered(),
1303
1676
  }));
1304
1677
 
1305
- // Resolve render props
1306
1678
  const renderProps = useRenderProps(
1307
1679
  {
1308
1680
  children: props.children,
1309
1681
  class: local.class,
1310
1682
  style: local.style,
1311
- defaultClassName: 'solidaria-ColorField-input',
1683
+ defaultClassName: "solidaria-ColorField-input",
1312
1684
  },
1313
- renderValues
1685
+ renderValues,
1314
1686
  );
1315
1687
 
1316
- // Clean props
1317
1688
  const cleanInputProps = () => {
1318
- const { ref: _ref, style: _inputStyle, ...rest } = inputProps as Record<string, unknown>;
1689
+ const {
1690
+ ref: _ref,
1691
+ style: _inputStyle,
1692
+ value: _value,
1693
+ ...rest
1694
+ } = context.inputProps as Record<string, unknown>;
1319
1695
  return rest;
1320
1696
  };
1321
1697
  const cleanFocusProps = () => {
@@ -1333,8 +1709,13 @@ export function ColorFieldInput(props: ColorFieldInputProps): JSX.Element {
1333
1709
  {...cleanInputProps()}
1334
1710
  {...cleanFocusProps()}
1335
1711
  {...cleanHoverProps()}
1712
+ ref={(el) => {
1713
+ context.setInputRef(el);
1714
+ local.ref?.(el);
1715
+ }}
1336
1716
  class={renderProps.class()}
1337
1717
  style={renderProps.style()}
1718
+ value={inputValue()}
1338
1719
  data-disabled={state.isDisabled || undefined}
1339
1720
  data-readonly={state.isReadOnly || undefined}
1340
1721
  data-invalid={state.isInvalid || undefined}
@@ -1345,13 +1726,8 @@ export function ColorFieldInput(props: ColorFieldInputProps): JSX.Element {
1345
1726
  );
1346
1727
  }
1347
1728
 
1348
- // Attach sub-components
1349
1729
  ColorField.Input = ColorFieldInput;
1350
1730
 
1351
- // ============================================
1352
- // COLOR SWATCH
1353
- // ============================================
1354
-
1355
1731
  export interface ColorSwatchRenderProps {
1356
1732
  /** The color being displayed. */
1357
1733
  color: Color;
@@ -1362,8 +1738,16 @@ export interface ColorSwatchRenderProps {
1362
1738
  export interface ColorSwatchProps extends SlotProps {
1363
1739
  /** The color to display. */
1364
1740
  color?: Color | string;
1741
+ /** Localized color name override. */
1742
+ colorName?: string;
1365
1743
  /** Accessible label for the swatch. */
1366
- 'aria-label'?: string;
1744
+ "aria-label"?: string;
1745
+ /** ID of element that labels the swatch. */
1746
+ "aria-labelledby"?: string;
1747
+ /** ID of element that describes the swatch. */
1748
+ "aria-describedby"?: string;
1749
+ /** ID of element that provides detailed information about the swatch. */
1750
+ "aria-details"?: string;
1367
1751
  /** The children of the component. */
1368
1752
  children?: RenderChildren<ColorSwatchRenderProps>;
1369
1753
  /** The CSS className for the element. */
@@ -1380,63 +1764,61 @@ export function ColorSwatch(props: ColorSwatchProps): JSX.Element {
1380
1764
  const pickerContext = useContext(ColorPickerContextInternal);
1381
1765
  const [local, ariaProps, rest] = splitProps(
1382
1766
  props,
1383
- ['children', 'class', 'style', 'slot', 'color'],
1384
- ['aria-label']
1767
+ ["children", "class", "style", "slot", "color", "colorName"],
1768
+ ["aria-label", "aria-labelledby", "aria-describedby", "aria-details"],
1385
1769
  );
1386
1770
 
1387
1771
  const resolvedColor = createMemo<Color | string>(() => {
1388
- return local.color ?? swatchContext?.color ?? pickerContext?.value ?? '#0000';
1772
+ return local.color ?? swatchContext?.color ?? pickerContext?.value ?? "#fff0";
1389
1773
  });
1390
1774
 
1391
- // Create color swatch aria props
1392
- const { swatchProps } = createColorSwatch(() => ({
1775
+ const swatchAria = createColorSwatch(() => ({
1776
+ id: (rest as Record<string, unknown>).id as string | undefined,
1777
+ slot: local.slot,
1393
1778
  color: resolvedColor(),
1394
- 'aria-label': ariaProps['aria-label'],
1779
+ colorName: local.colorName,
1780
+ "aria-label": ariaProps["aria-label"],
1781
+ "aria-labelledby": ariaProps["aria-labelledby"],
1782
+ "aria-describedby": ariaProps["aria-describedby"],
1783
+ "aria-details": ariaProps["aria-details"],
1395
1784
  }));
1396
1785
 
1397
- // Normalize color
1398
- const color = createMemo(() => normalizeColor(resolvedColor()));
1399
-
1400
- // Render props values
1401
1786
  const renderValues = createMemo<ColorSwatchRenderProps>(() => ({
1402
- color: color(),
1403
- colorValue: color().toString('css'),
1787
+ color: swatchAria.color,
1788
+ colorValue: swatchAria.color.toString("css"),
1404
1789
  }));
1405
1790
 
1406
- // Resolve render props
1407
1791
  const renderProps = useRenderProps(
1408
1792
  {
1409
1793
  children: props.children,
1410
1794
  class: local.class,
1411
1795
  style: local.style,
1412
- defaultClassName: 'solidaria-ColorSwatch',
1796
+ defaultClassName: "solidaria-ColorSwatch",
1413
1797
  },
1414
- renderValues
1798
+ renderValues,
1415
1799
  );
1416
1800
 
1417
- // Filter DOM props
1418
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
1801
+ const domProps = createMemo(() =>
1802
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
1803
+ );
1419
1804
 
1420
- // Clean props
1421
1805
  const cleanSwatchProps = () => {
1422
- const { ref: _ref, style: _swatchStyle, ...rest } = swatchProps as Record<string, unknown>;
1806
+ const {
1807
+ ref: _ref,
1808
+ style: _swatchStyle,
1809
+ ...rest
1810
+ } = swatchAria.swatchProps as Record<string, unknown>;
1423
1811
  return rest;
1424
1812
  };
1425
1813
 
1426
- // Merge styles
1427
1814
  const mergedStyle = () => {
1428
- const swatchStyle = (swatchProps as { style?: Record<string, string> }).style || {};
1815
+ const swatchStyle = (swatchAria.swatchProps as { style?: Record<string, string> }).style || {};
1429
1816
  const renderStyle = renderProps.style() || {};
1430
1817
  return { ...swatchStyle, ...renderStyle };
1431
1818
  };
1432
1819
 
1433
1820
  return (
1434
- <div
1435
- {...domProps()}
1436
- {...cleanSwatchProps()}
1437
- class={renderProps.class()}
1438
- style={mergedStyle()}
1439
- >
1821
+ <div {...domProps()} {...cleanSwatchProps()} class={renderProps.class()} style={mergedStyle()}>
1440
1822
  {renderProps.renderChildren()}
1441
1823
  </div>
1442
1824
  );
@@ -1481,10 +1863,12 @@ export interface ColorSwatchPickerRenderProps {
1481
1863
  /** The currently selected color. */
1482
1864
  selectedColor: Color;
1483
1865
  /** Item arrangement mode. */
1484
- layout: 'grid' | 'stack';
1866
+ layout: "grid" | "stack";
1485
1867
  }
1486
1868
 
1487
1869
  export interface ColorSwatchPickerProps extends SlotProps {
1870
+ /** The element's unique identifier. */
1871
+ id?: string;
1488
1872
  /** The current color value (controlled). */
1489
1873
  value?: Color | string;
1490
1874
  /** The default color value (uncontrolled). */
@@ -1492,13 +1876,15 @@ export interface ColorSwatchPickerProps extends SlotProps {
1492
1876
  /** Handler called when the selected color changes. */
1493
1877
  onChange?: (color: Color) => void;
1494
1878
  /** Accessible label for the swatch picker. */
1495
- 'aria-label'?: string;
1879
+ "aria-label"?: string;
1496
1880
  /** ID of element that labels the swatch picker. */
1497
- 'aria-labelledby'?: string;
1881
+ "aria-labelledby"?: string;
1498
1882
  /** ID of element that describes the swatch picker. */
1499
- 'aria-describedby'?: string;
1883
+ "aria-describedby"?: string;
1884
+ /** ID of element that provides detailed information about the swatch picker. */
1885
+ "aria-details"?: string;
1500
1886
  /** Whether swatches are arranged as a grid or stack. */
1501
- layout?: 'grid' | 'stack';
1887
+ layout?: "grid" | "stack";
1502
1888
  /** The children (ColorSwatchPickerItem elements). */
1503
1889
  children?: JSX.Element;
1504
1890
  /** The CSS className for the element. */
@@ -1528,7 +1914,7 @@ export interface ColorSwatchPickerItemProps extends SlotProps {
1528
1914
  /** Whether this item is disabled. */
1529
1915
  isDisabled?: boolean;
1530
1916
  /** Accessible label for this item. */
1531
- 'aria-label'?: string;
1917
+ "aria-label"?: string;
1532
1918
  /** The children of the swatch item. */
1533
1919
  children?: RenderChildren<ColorSwatchPickerItemRenderProps>;
1534
1920
  /** The CSS className for the element. */
@@ -1539,17 +1925,17 @@ export interface ColorSwatchPickerItemProps extends SlotProps {
1539
1925
 
1540
1926
  export function ColorPicker(props: ColorPickerProps): JSX.Element {
1541
1927
  const [local] = splitProps(props, [
1542
- 'value',
1543
- 'defaultValue',
1544
- 'onChange',
1545
- 'children',
1546
- 'class',
1547
- 'style',
1548
- 'slot',
1928
+ "value",
1929
+ "defaultValue",
1930
+ "onChange",
1931
+ "children",
1932
+ "class",
1933
+ "style",
1934
+ "slot",
1549
1935
  ]);
1550
1936
 
1551
1937
  const [internalColor, setInternalColor] = createSignal<Color>(
1552
- normalizeColor(local.defaultValue ?? '#ff0000')
1938
+ normalizeColor(local.defaultValue ?? "#ff0000"),
1553
1939
  );
1554
1940
 
1555
1941
  const color = createMemo<Color>(() => {
@@ -1575,9 +1961,9 @@ export function ColorPicker(props: ColorPickerProps): JSX.Element {
1575
1961
  children: local.children,
1576
1962
  class: local.class,
1577
1963
  style: local.style,
1578
- defaultClassName: 'solidaria-ColorPicker',
1964
+ defaultClassName: "solidaria-ColorPicker",
1579
1965
  },
1580
- renderValues
1966
+ renderValues,
1581
1967
  );
1582
1968
 
1583
1969
  return (
@@ -1606,23 +1992,25 @@ export function ColorPicker(props: ColorPickerProps): JSX.Element {
1606
1992
  export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1607
1993
  const pickerContext = useContext(ColorPickerContextInternal);
1608
1994
  const [local, rest] = splitProps(props, [
1609
- 'value',
1610
- 'defaultValue',
1611
- 'onChange',
1612
- 'aria-label',
1613
- 'aria-labelledby',
1614
- 'aria-describedby',
1615
- 'layout',
1616
- 'children',
1617
- 'class',
1618
- 'style',
1619
- 'slot',
1995
+ "value",
1996
+ "defaultValue",
1997
+ "onChange",
1998
+ "id",
1999
+ "aria-label",
2000
+ "aria-labelledby",
2001
+ "aria-describedby",
2002
+ "aria-details",
2003
+ "layout",
2004
+ "children",
2005
+ "class",
2006
+ "style",
2007
+ "slot",
1620
2008
  ]);
1621
2009
 
1622
2010
  const [itemMap, setItemMap] = createSignal<Map<string, ColorSwatchPickerItemData>>(new Map());
1623
2011
  const [itemOrder, setItemOrder] = createSignal<string[]>([]);
1624
2012
  const [internalColor, setInternalColor] = createSignal<Color>(
1625
- normalizeColor(local.defaultValue ?? pickerContext?.value ?? '#ff0000')
2013
+ normalizeColor(local.defaultValue ?? pickerContext?.value ?? "#ff0000"),
1626
2014
  );
1627
2015
 
1628
2016
  const selectedColor = createMemo<Color>(() => {
@@ -1635,8 +2023,10 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1635
2023
  return internalColor();
1636
2024
  });
1637
2025
 
1638
- const selectedKey = createMemo(() => selectedColor().toString('hexa'));
1639
- const isControlled = createMemo(() => local.value !== undefined || pickerContext?.value !== undefined);
2026
+ const selectedKey = createMemo(() => selectedColor().toString("hexa"));
2027
+ const isControlled = createMemo(
2028
+ () => local.value !== undefined || pickerContext?.value !== undefined,
2029
+ );
1640
2030
 
1641
2031
  const registerItem = (item: ColorSwatchPickerItemData) => {
1642
2032
  setItemMap((prev) => {
@@ -1677,13 +2067,13 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1677
2067
  get getDisabled() {
1678
2068
  return (item: ColorSwatchPickerItemData) => !!item.isDisabled;
1679
2069
  },
1680
- selectionMode: 'single',
2070
+ selectionMode: "single",
1681
2071
  disallowEmptySelection: true,
1682
2072
  get selectedKeys() {
1683
2073
  return [selectedKey()];
1684
2074
  },
1685
2075
  onSelectionChange(keys) {
1686
- if (keys === 'all') return;
2076
+ if (keys === "all") return;
1687
2077
  const key = keys.values().next().value as string | undefined;
1688
2078
  if (!key) return;
1689
2079
  const item = itemMap().get(key);
@@ -1697,28 +2087,31 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1697
2087
 
1698
2088
  const listBoxAria = createListBox(
1699
2089
  () => ({
1700
- 'aria-label': local['aria-label'] ?? (!local['aria-labelledby'] ? 'Color swatch picker' : undefined),
1701
- 'aria-labelledby': local['aria-labelledby'],
1702
- 'aria-describedby': local['aria-describedby'],
2090
+ id: local.id,
2091
+ "aria-label":
2092
+ local["aria-label"] ?? (!local["aria-labelledby"] ? "Color swatch picker" : undefined),
2093
+ "aria-labelledby": local["aria-labelledby"],
2094
+ "aria-describedby": local["aria-describedby"],
2095
+ "aria-details": local["aria-details"],
1703
2096
  shouldFocusWrap: true,
1704
2097
  }),
1705
- state
2098
+ state,
1706
2099
  );
1707
2100
 
1708
- const resolveDirection = (): 'ltr' | 'rtl' => {
1709
- if (typeof document === 'undefined') return 'ltr';
2101
+ const resolveDirection = (): "ltr" | "rtl" => {
2102
+ if (typeof document === "undefined") return "ltr";
1710
2103
  const rootDir = document.dir;
1711
- return rootDir === 'rtl' ? 'rtl' : 'ltr';
2104
+ return rootDir === "rtl" ? "rtl" : "ltr";
1712
2105
  };
1713
2106
 
1714
- const findNextEnabledKey = (from: Key | null, direction: 'next' | 'prev') => {
2107
+ const findNextEnabledKey = (from: Key | null, direction: "next" | "prev") => {
1715
2108
  const collection = state.collection();
1716
- const getAdjacent = direction === 'next'
1717
- ? (key: Key) => collection.getKeyAfter(key)
1718
- : (key: Key) => collection.getKeyBefore(key);
1719
- const getBoundary = direction === 'next'
1720
- ? () => collection.getFirstKey()
1721
- : () => collection.getLastKey();
2109
+ const getAdjacent =
2110
+ direction === "next"
2111
+ ? (key: Key) => collection.getKeyAfter(key)
2112
+ : (key: Key) => collection.getKeyBefore(key);
2113
+ const getBoundary =
2114
+ direction === "next" ? () => collection.getFirstKey() : () => collection.getLastKey();
1722
2115
 
1723
2116
  let key = from != null ? getAdjacent(from) : getBoundary();
1724
2117
  while (key != null && state.isDisabled(key)) {
@@ -1728,14 +2121,14 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1728
2121
  return key;
1729
2122
  };
1730
2123
 
1731
- const getBoundaryEnabledKey = (direction: 'next' | 'prev') => {
2124
+ const getBoundaryEnabledKey = (direction: "next" | "prev") => {
1732
2125
  const collection = state.collection();
1733
- const getAdjacent = direction === 'next'
1734
- ? (key: Key) => collection.getKeyAfter(key)
1735
- : (key: Key) => collection.getKeyBefore(key);
1736
- const getBoundary = direction === 'next'
1737
- ? () => collection.getFirstKey()
1738
- : () => collection.getLastKey();
2126
+ const getAdjacent =
2127
+ direction === "next"
2128
+ ? (key: Key) => collection.getKeyAfter(key)
2129
+ : (key: Key) => collection.getKeyBefore(key);
2130
+ const getBoundary =
2131
+ direction === "next" ? () => collection.getFirstKey() : () => collection.getLastKey();
1739
2132
 
1740
2133
  let key = getBoundary();
1741
2134
  while (key != null && state.isDisabled(key)) {
@@ -1745,7 +2138,10 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1745
2138
  return key;
1746
2139
  };
1747
2140
 
1748
- const getOptionElementForKey = (listbox: HTMLElement | null, key: Key | null): HTMLElement | null => {
2141
+ const getOptionElementForKey = (
2142
+ listbox: HTMLElement | null,
2143
+ key: Key | null,
2144
+ ): HTMLElement | null => {
1749
2145
  if (!listbox || key == null) return null;
1750
2146
  const keyString = String(key);
1751
2147
  for (const optionElement of listbox.querySelectorAll<HTMLElement>('[role="option"]')) {
@@ -1760,7 +2156,7 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1760
2156
  listbox: HTMLElement | null,
1761
2157
  key: Key,
1762
2158
  nextKey: (current: Key) => Key | null,
1763
- shouldSkip: (prevRect: DOMRect, itemRect: DOMRect) => boolean
2159
+ shouldSkip: (prevRect: DOMRect, itemRect: DOMRect) => boolean,
1764
2160
  ): Key | null => {
1765
2161
  let candidate: Key | null = key;
1766
2162
  const previousRect = getOptionElementForKey(listbox, candidate)?.getBoundingClientRect();
@@ -1787,53 +2183,69 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1787
2183
  return null;
1788
2184
  };
1789
2185
 
1790
- const isSameRow = (prevRect: DOMRect, itemRect: DOMRect) => prevRect.y === itemRect.y || prevRect.x !== itemRect.x;
2186
+ const isSameRow = (prevRect: DOMRect, itemRect: DOMRect) =>
2187
+ prevRect.y === itemRect.y || prevRect.x !== itemRect.x;
1791
2188
  const getGridKeyBelow = (listbox: HTMLElement | null, key: Key) =>
1792
- findGridKey(listbox, key, (current) => findNextEnabledKey(current, 'next'), isSameRow);
2189
+ findGridKey(listbox, key, (current) => findNextEnabledKey(current, "next"), isSameRow);
1793
2190
  const getGridKeyAbove = (listbox: HTMLElement | null, key: Key) =>
1794
- findGridKey(listbox, key, (current) => findNextEnabledKey(current, 'prev'), isSameRow);
2191
+ findGridKey(listbox, key, (current) => findNextEnabledKey(current, "prev"), isSameRow);
1795
2192
  const getGridKeyRightOf = (key: Key) =>
1796
- resolveDirection() === 'rtl' ? findNextEnabledKey(key, 'prev') : findNextEnabledKey(key, 'next');
2193
+ resolveDirection() === "rtl"
2194
+ ? findNextEnabledKey(key, "prev")
2195
+ : findNextEnabledKey(key, "next");
1797
2196
  const getGridKeyLeftOf = (key: Key) =>
1798
- resolveDirection() === 'rtl' ? findNextEnabledKey(key, 'next') : findNextEnabledKey(key, 'prev');
2197
+ resolveDirection() === "rtl"
2198
+ ? findNextEnabledKey(key, "next")
2199
+ : findNextEnabledKey(key, "prev");
1799
2200
 
1800
2201
  const handleGridKeyDown = (e: KeyboardEvent): boolean => {
1801
- if ((local.layout ?? 'grid') !== 'grid') return false;
1802
- if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft' && e.key !== 'ArrowDown' && e.key !== 'ArrowUp') {
2202
+ if ((local.layout ?? "grid") !== "grid") return false;
2203
+ if (
2204
+ e.key !== "ArrowRight" &&
2205
+ e.key !== "ArrowLeft" &&
2206
+ e.key !== "ArrowDown" &&
2207
+ e.key !== "ArrowUp"
2208
+ ) {
1803
2209
  return false;
1804
2210
  }
1805
2211
 
1806
2212
  const listbox = e.currentTarget as HTMLElement | null;
1807
2213
  const focusedKey = state.focusedKey();
1808
- const initialKey = focusedKey ?? (e.key === 'ArrowUp' || e.key === 'ArrowLeft'
1809
- ? getBoundaryEnabledKey('prev')
1810
- : getBoundaryEnabledKey('next'));
2214
+ const initialKey =
2215
+ focusedKey ??
2216
+ (e.key === "ArrowUp" || e.key === "ArrowLeft"
2217
+ ? getBoundaryEnabledKey("prev")
2218
+ : getBoundaryEnabledKey("next"));
1811
2219
  if (initialKey == null) return false;
1812
2220
 
1813
2221
  let nextKey: Key | null = null;
1814
2222
  switch (e.key) {
1815
- case 'ArrowDown':
1816
- nextKey = getGridKeyBelow(listbox, initialKey) ?? getBoundaryEnabledKey('next');
2223
+ case "ArrowDown":
2224
+ nextKey = getGridKeyBelow(listbox, initialKey) ?? getBoundaryEnabledKey("next");
1817
2225
  break;
1818
- case 'ArrowUp':
1819
- nextKey = getGridKeyAbove(listbox, initialKey) ?? getBoundaryEnabledKey('prev');
2226
+ case "ArrowUp":
2227
+ nextKey = getGridKeyAbove(listbox, initialKey) ?? getBoundaryEnabledKey("prev");
1820
2228
  break;
1821
- case 'ArrowRight':
1822
- nextKey = getGridKeyRightOf(initialKey) ?? (
1823
- resolveDirection() === 'rtl' ? getBoundaryEnabledKey('prev') : getBoundaryEnabledKey('next')
1824
- );
2229
+ case "ArrowRight":
2230
+ nextKey =
2231
+ getGridKeyRightOf(initialKey) ??
2232
+ (resolveDirection() === "rtl"
2233
+ ? getBoundaryEnabledKey("prev")
2234
+ : getBoundaryEnabledKey("next"));
1825
2235
  break;
1826
- case 'ArrowLeft':
1827
- nextKey = getGridKeyLeftOf(initialKey) ?? (
1828
- resolveDirection() === 'rtl' ? getBoundaryEnabledKey('next') : getBoundaryEnabledKey('prev')
1829
- );
2236
+ case "ArrowLeft":
2237
+ nextKey =
2238
+ getGridKeyLeftOf(initialKey) ??
2239
+ (resolveDirection() === "rtl"
2240
+ ? getBoundaryEnabledKey("next")
2241
+ : getBoundaryEnabledKey("prev"));
1830
2242
  break;
1831
2243
  }
1832
2244
 
1833
2245
  if (nextKey == null) return false;
1834
2246
 
1835
2247
  state.setFocusedKey(nextKey);
1836
- if (state.selectionMode() === 'single') {
2248
+ if (state.selectionMode() === "single") {
1837
2249
  state.replaceSelection(nextKey);
1838
2250
  }
1839
2251
 
@@ -1862,25 +2274,31 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1862
2274
  });
1863
2275
 
1864
2276
  const { isFocused, isFocusVisible, focusProps } = createFocusRing({ within: true });
1865
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
2277
+ const domProps = createMemo(() =>
2278
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
2279
+ );
1866
2280
  const renderValues = createMemo<ColorSwatchPickerRenderProps>(() => ({
1867
2281
  isFocused: state.isFocused() || isFocused(),
1868
2282
  isFocusVisible: isFocusVisible(),
1869
2283
  selectedColor: selectedColor(),
1870
- layout: local.layout ?? 'grid',
2284
+ layout: local.layout ?? "grid",
1871
2285
  }));
1872
2286
 
1873
2287
  const renderProps = useRenderProps(
1874
2288
  {
1875
2289
  class: local.class,
1876
2290
  style: local.style,
1877
- defaultClassName: 'solidaria-ColorSwatchPicker',
2291
+ defaultClassName: "solidaria-ColorSwatchPicker",
1878
2292
  },
1879
- renderValues
2293
+ renderValues,
1880
2294
  );
1881
2295
 
1882
2296
  const cleanListBoxProps = () => {
1883
- const { ref: _ref, onKeyDown: _onKeyDown, ...restListBoxProps } = listBoxAria.listBoxProps as Record<string, unknown>;
2297
+ const {
2298
+ ref: _ref,
2299
+ onKeyDown: _onKeyDown,
2300
+ ...restListBoxProps
2301
+ } = listBoxAria.listBoxProps as Record<string, unknown>;
1884
2302
  return restListBoxProps;
1885
2303
  };
1886
2304
  const cleanFocusProps = () => {
@@ -1897,12 +2315,15 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1897
2315
  }}
1898
2316
  >
1899
2317
  <div
1900
- {...mergeProps(domProps(), cleanListBoxProps(), cleanFocusProps(), { onKeyDown: onColorSwatchPickerKeyDown })}
2318
+ {...mergeProps(domProps(), cleanListBoxProps(), cleanFocusProps(), {
2319
+ onKeyDown: onColorSwatchPickerKeyDown,
2320
+ })}
1901
2321
  class={renderProps.class()}
1902
2322
  style={renderProps.style()}
2323
+ slot={local.slot ?? undefined}
1903
2324
  data-focused={state.isFocused() || undefined}
1904
2325
  data-focus-visible={isFocusVisible() || undefined}
1905
- data-layout={local.layout ?? 'grid'}
2326
+ data-layout={local.layout ?? "grid"}
1906
2327
  >
1907
2328
  {local.children}
1908
2329
  </div>
@@ -1913,19 +2334,19 @@ export function ColorSwatchPicker(props: ColorSwatchPickerProps): JSX.Element {
1913
2334
  export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps): JSX.Element {
1914
2335
  const context = useContext(ColorSwatchPickerContextInternal);
1915
2336
  if (!context) {
1916
- throw new Error('ColorSwatchPickerItem must be used within a ColorSwatchPicker');
2337
+ throw new Error("ColorSwatchPickerItem must be used within a ColorSwatchPicker");
1917
2338
  }
1918
2339
 
1919
2340
  const [local, ariaProps, rest] = splitProps(
1920
2341
  props,
1921
- ['children', 'class', 'style', 'slot', 'color'],
1922
- ['isDisabled', 'aria-label']
2342
+ ["children", "class", "style", "slot", "color"],
2343
+ ["isDisabled", "aria-label"],
1923
2344
  );
1924
2345
 
1925
2346
  const color = createMemo(() => normalizeColor(local.color));
1926
- const key = createMemo(() => color().toString('hexa'));
2347
+ const key = createMemo(() => color().toString("hexa"));
1927
2348
  const textValue = createMemo(() => {
1928
- const locale = globalThis.navigator?.language ?? 'en-US';
2349
+ const locale = globalThis.navigator?.language ?? "en-US";
1929
2350
  return color().getColorName(locale);
1930
2351
  });
1931
2352
 
@@ -1944,9 +2365,9 @@ export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps): JSX.El
1944
2365
  () => ({
1945
2366
  key: key(),
1946
2367
  isDisabled: ariaProps.isDisabled,
1947
- 'aria-label': ariaProps['aria-label'] ?? textValue(),
2368
+ "aria-label": ariaProps["aria-label"] ?? textValue(),
1948
2369
  }),
1949
- context.state
2370
+ context.state,
1950
2371
  );
1951
2372
 
1952
2373
  const renderValues = createMemo<ColorSwatchPickerItemRenderProps>(() => ({
@@ -1963,12 +2384,14 @@ export function ColorSwatchPickerItem(props: ColorSwatchPickerItemProps): JSX.El
1963
2384
  children: local.children,
1964
2385
  class: local.class,
1965
2386
  style: local.style,
1966
- defaultClassName: 'solidaria-ColorSwatchPickerItem',
2387
+ defaultClassName: "solidaria-ColorSwatchPickerItem",
1967
2388
  },
1968
- renderValues
2389
+ renderValues,
1969
2390
  );
1970
2391
 
1971
- const domProps = createMemo(() => filterDOMProps(rest as Record<string, unknown>, { global: true }));
2392
+ const domProps = createMemo(() =>
2393
+ filterDOMProps(rest as Record<string, unknown>, { global: true }),
2394
+ );
1972
2395
  const cleanOptionProps = () => {
1973
2396
  const { ref: _ref, ...restOptionProps } = optionAria.optionProps as Record<string, unknown>;
1974
2397
  return restOptionProps;