@proyecto-viviana/solid-stately 0.2.7 → 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 (242) hide show
  1. package/README.md +30 -283
  2. package/dist/autocomplete/createAutocompleteState.d.ts +2 -2
  3. package/dist/autocomplete/index.d.ts +1 -1
  4. package/dist/calendar/createCalendarState.d.ts +22 -7
  5. package/dist/calendar/createDateFieldState.d.ts +30 -7
  6. package/dist/calendar/createDatePickerState.d.ts +60 -0
  7. package/dist/calendar/createRangeCalendarState.d.ts +27 -8
  8. package/dist/calendar/createTimeFieldState.d.ts +34 -7
  9. package/dist/calendar/index.d.ts +7 -6
  10. package/dist/checkbox/createCheckboxGroupState.d.ts +6 -6
  11. package/dist/checkbox/index.d.ts +1 -1
  12. package/dist/collections/ListCollection.d.ts +1 -1
  13. package/dist/collections/createListState.d.ts +8 -8
  14. package/dist/collections/createMenuState.d.ts +7 -7
  15. package/dist/collections/createSelectionState.d.ts +5 -5
  16. package/dist/collections/index.d.ts +5 -5
  17. package/dist/collections/types.d.ts +12 -12
  18. package/dist/color/Color.d.ts +1 -1
  19. package/dist/color/createColorAreaState.d.ts +4 -2
  20. package/dist/color/createColorFieldState.d.ts +38 -12
  21. package/dist/color/createColorSliderState.d.ts +9 -2
  22. package/dist/color/createColorWheelState.d.ts +13 -2
  23. package/dist/color/getColorChannels.d.ts +1 -1
  24. package/dist/color/index.d.ts +6 -6
  25. package/dist/color/types.d.ts +4 -4
  26. package/dist/combobox/createComboBoxState.d.ts +27 -13
  27. package/dist/combobox/index.d.ts +1 -1
  28. package/dist/data/createAsyncList.d.ts +4 -4
  29. package/dist/data/createListData.d.ts +2 -2
  30. package/dist/data/createTreeData.d.ts +6 -2
  31. package/dist/data/index.d.ts +3 -3
  32. package/dist/datepicker/index.d.ts +2 -4
  33. package/dist/disclosure/createDisclosureState.d.ts +2 -2
  34. package/dist/disclosure/index.d.ts +1 -1
  35. package/dist/dnd/createDragState.d.ts +2 -2
  36. package/dist/dnd/createDraggableCollectionState.d.ts +2 -2
  37. package/dist/dnd/createDropState.d.ts +2 -2
  38. package/dist/dnd/createDroppableCollectionState.d.ts +3 -3
  39. package/dist/dnd/index.d.ts +5 -5
  40. package/dist/dnd/types.d.ts +16 -16
  41. package/dist/form/createFormValidationState.d.ts +3 -3
  42. package/dist/form/index.d.ts +1 -1
  43. package/dist/grid/createGridState.d.ts +2 -2
  44. package/dist/grid/index.d.ts +2 -2
  45. package/dist/grid/types.d.ts +13 -11
  46. package/dist/index.d.ts +31 -30
  47. package/dist/index.js +7924 -7238
  48. package/dist/index.js.map +1 -1
  49. package/dist/menu/index.d.ts +2 -2
  50. package/dist/numberfield/createNumberFieldState.d.ts +2 -2
  51. package/dist/numberfield/index.d.ts +1 -1
  52. package/dist/overlays/createOverlayTriggerState.d.ts +2 -2
  53. package/dist/overlays/index.d.ts +1 -1
  54. package/dist/radio/createRadioGroupState.d.ts +7 -7
  55. package/dist/radio/index.d.ts +1 -1
  56. package/dist/searchfield/createSearchFieldState.d.ts +2 -2
  57. package/dist/searchfield/index.d.ts +2 -2
  58. package/dist/select/createSelectState.d.ts +8 -8
  59. package/dist/select/index.d.ts +1 -1
  60. package/dist/selection/index.d.ts +5 -5
  61. package/dist/slider/createSliderState.d.ts +3 -3
  62. package/dist/slider/index.d.ts +2 -2
  63. package/dist/steplist/createStepListState.d.ts +48 -0
  64. package/dist/steplist/index.d.ts +1 -0
  65. package/dist/table/TableCollection.d.ts +3 -3
  66. package/dist/table/createTableColumnResizeState.d.ts +54 -0
  67. package/dist/table/createTableState.d.ts +2 -2
  68. package/dist/table/index.d.ts +5 -3
  69. package/dist/table/types.d.ts +10 -10
  70. package/dist/tabs/createTabListState.d.ts +5 -5
  71. package/dist/tabs/index.d.ts +1 -1
  72. package/dist/textfield/createTextFieldState.d.ts +2 -2
  73. package/dist/textfield/index.d.ts +1 -1
  74. package/dist/toast/createToastState.d.ts +8 -2
  75. package/dist/toast/index.d.ts +1 -1
  76. package/dist/toggle/createToggleGroupState.d.ts +4 -4
  77. package/dist/toggle/createToggleState.d.ts +2 -2
  78. package/dist/toggle/index.d.ts +2 -2
  79. package/dist/tooltip/createTooltipTriggerState.d.ts +2 -2
  80. package/dist/tooltip/index.d.ts +1 -1
  81. package/dist/tree/TreeCollection.d.ts +2 -2
  82. package/dist/tree/createTreeState.d.ts +2 -2
  83. package/dist/tree/index.d.ts +3 -3
  84. package/dist/tree/types.d.ts +19 -13
  85. package/dist/utils/index.d.ts +1 -1
  86. package/dist/utils/reactivity.d.ts +1 -1
  87. package/package.json +29 -30
  88. package/src/autocomplete/createAutocompleteState.d.ts +19 -17
  89. package/src/autocomplete/createAutocompleteState.ts +24 -30
  90. package/src/autocomplete/index.d.ts +6 -2
  91. package/src/autocomplete/index.ts +1 -1
  92. package/src/calendar/createCalendarState.d.ts +139 -117
  93. package/src/calendar/createCalendarState.ts +221 -74
  94. package/src/calendar/createDateFieldState.d.ts +130 -95
  95. package/src/calendar/createDateFieldState.ts +263 -151
  96. package/src/calendar/createDatePickerState.ts +347 -0
  97. package/src/calendar/createRangeCalendarState.d.ts +154 -129
  98. package/src/calendar/createRangeCalendarState.ts +293 -79
  99. package/src/calendar/createTimeFieldState.d.ts +81 -79
  100. package/src/calendar/createTimeFieldState.ts +187 -108
  101. package/src/calendar/index.d.ts +80 -7
  102. package/src/calendar/index.ts +21 -12
  103. package/src/checkbox/createCheckboxGroupState.d.ts +57 -55
  104. package/src/checkbox/createCheckboxGroupState.ts +20 -29
  105. package/src/checkbox/index.d.ts +6 -2
  106. package/src/checkbox/index.ts +1 -1
  107. package/src/collections/ListCollection.d.ts +25 -22
  108. package/src/collections/ListCollection.ts +6 -6
  109. package/src/collections/createListState.d.ts +73 -58
  110. package/src/collections/createListState.ts +19 -18
  111. package/src/collections/createMenuState.d.ts +36 -31
  112. package/src/collections/createMenuState.ts +18 -12
  113. package/src/collections/createSelectionState.d.ts +78 -65
  114. package/src/collections/createSelectionState.ts +36 -32
  115. package/src/collections/index.d.ts +38 -6
  116. package/src/collections/index.ts +5 -10
  117. package/src/collections/types.d.ts +101 -101
  118. package/src/collections/types.ts +12 -12
  119. package/src/color/Color.d.ts +20 -5
  120. package/src/color/Color.ts +357 -254
  121. package/src/color/createColorAreaState.d.ts +67 -65
  122. package/src/color/createColorAreaState.ts +21 -13
  123. package/src/color/createColorFieldState.d.ts +46 -44
  124. package/src/color/createColorFieldState.ts +285 -149
  125. package/src/color/createColorSliderState.d.ts +58 -56
  126. package/src/color/createColorSliderState.ts +45 -14
  127. package/src/color/createColorWheelState.d.ts +42 -40
  128. package/src/color/createColorWheelState.ts +101 -64
  129. package/src/color/getColorChannels.ts +7 -5
  130. package/src/color/index.d.ts +36 -7
  131. package/src/color/index.ts +6 -9
  132. package/src/color/types.d.ts +68 -63
  133. package/src/color/types.ts +11 -19
  134. package/src/combobox/createComboBoxState.d.ts +107 -105
  135. package/src/combobox/createComboBoxState.ts +176 -89
  136. package/src/combobox/index.d.ts +10 -2
  137. package/src/combobox/index.ts +1 -1
  138. package/src/data/createAsyncList.ts +89 -63
  139. package/src/data/createListData.ts +67 -67
  140. package/src/data/createTreeData.ts +304 -91
  141. package/src/data/index.ts +3 -3
  142. package/src/datepicker/index.ts +6 -10
  143. package/src/disclosure/createDisclosureState.d.ts +47 -43
  144. package/src/disclosure/createDisclosureState.ts +7 -21
  145. package/src/disclosure/index.d.ts +10 -2
  146. package/src/disclosure/index.ts +1 -1
  147. package/src/dnd/createDragState.d.ts +51 -42
  148. package/src/dnd/createDragState.ts +12 -14
  149. package/src/dnd/createDraggableCollectionState.d.ts +52 -41
  150. package/src/dnd/createDraggableCollectionState.ts +14 -21
  151. package/src/dnd/createDropState.d.ts +64 -45
  152. package/src/dnd/createDropState.ts +22 -32
  153. package/src/dnd/createDroppableCollectionState.d.ts +88 -59
  154. package/src/dnd/createDroppableCollectionState.ts +40 -43
  155. package/src/dnd/index.d.ts +54 -6
  156. package/src/dnd/index.ts +5 -18
  157. package/src/dnd/types.d.ts +167 -156
  158. package/src/dnd/types.ts +20 -21
  159. package/src/form/createFormValidationState.d.ts +47 -45
  160. package/src/form/createFormValidationState.ts +30 -63
  161. package/src/form/index.d.ts +16 -2
  162. package/src/form/index.ts +1 -1
  163. package/src/grid/createGridState.d.ts +7 -4
  164. package/src/grid/createGridState.ts +44 -51
  165. package/src/grid/index.d.ts +3 -3
  166. package/src/grid/index.ts +2 -8
  167. package/src/grid/types.d.ts +136 -129
  168. package/src/grid/types.ts +20 -18
  169. package/src/index.d.ts +310 -26
  170. package/src/index.ts +48 -89
  171. package/src/menu/index.ts +2 -4
  172. package/src/numberfield/createNumberFieldState.d.ts +56 -54
  173. package/src/numberfield/createNumberFieldState.ts +26 -31
  174. package/src/numberfield/index.d.ts +6 -2
  175. package/src/numberfield/index.ts +1 -1
  176. package/src/overlays/createOverlayTriggerState.d.ts +22 -20
  177. package/src/overlays/createOverlayTriggerState.ts +3 -4
  178. package/src/overlays/index.d.ts +6 -2
  179. package/src/overlays/index.ts +1 -1
  180. package/src/radio/createRadioGroupState.d.ts +54 -52
  181. package/src/radio/createRadioGroupState.ts +27 -29
  182. package/src/radio/index.d.ts +7 -2
  183. package/src/radio/index.ts +1 -1
  184. package/src/searchfield/createSearchFieldState.d.ts +16 -14
  185. package/src/searchfield/createSearchFieldState.ts +5 -11
  186. package/src/searchfield/index.d.ts +3 -3
  187. package/src/searchfield/index.ts +2 -5
  188. package/src/select/createSelectState.d.ts +63 -61
  189. package/src/select/createSelectState.ts +27 -27
  190. package/src/select/index.d.ts +2 -2
  191. package/src/select/index.ts +1 -5
  192. package/src/selection/index.ts +6 -7
  193. package/src/slider/createSliderState.d.ts +60 -60
  194. package/src/slider/createSliderState.ts +55 -33
  195. package/src/slider/index.d.ts +3 -3
  196. package/src/slider/index.ts +2 -6
  197. package/src/ssr/index.d.ts +1 -1
  198. package/src/ssr/index.ts +2 -2
  199. package/src/steplist/createStepListState.ts +191 -0
  200. package/src/steplist/index.ts +5 -0
  201. package/src/table/TableCollection.d.ts +42 -40
  202. package/src/table/TableCollection.ts +61 -64
  203. package/src/table/createTableColumnResizeState.ts +242 -0
  204. package/src/table/createTableState.d.ts +7 -4
  205. package/src/table/createTableState.ts +10 -7
  206. package/src/table/index.d.ts +14 -4
  207. package/src/table/index.ts +10 -3
  208. package/src/table/types.d.ts +98 -92
  209. package/src/table/types.ts +17 -13
  210. package/src/tabs/createTabListState.d.ts +59 -57
  211. package/src/tabs/createTabListState.ts +24 -31
  212. package/src/tabs/index.d.ts +8 -2
  213. package/src/tabs/index.ts +1 -1
  214. package/src/textfield/createTextFieldState.d.ts +16 -14
  215. package/src/textfield/createTextFieldState.ts +7 -18
  216. package/src/textfield/index.d.ts +6 -2
  217. package/src/textfield/index.ts +1 -1
  218. package/src/toast/createToastState.d.ts +92 -86
  219. package/src/toast/createToastState.ts +48 -43
  220. package/src/toast/index.d.ts +12 -2
  221. package/src/toast/index.ts +1 -1
  222. package/src/toggle/createToggleGroupState.ts +11 -21
  223. package/src/toggle/createToggleState.d.ts +19 -19
  224. package/src/toggle/createToggleState.ts +2 -16
  225. package/src/toggle/index.d.ts +2 -2
  226. package/src/toggle/index.ts +2 -6
  227. package/src/tooltip/createTooltipTriggerState.d.ts +17 -17
  228. package/src/tooltip/createTooltipTriggerState.ts +12 -11
  229. package/src/tooltip/index.d.ts +7 -2
  230. package/src/tooltip/index.ts +1 -1
  231. package/src/tree/TreeCollection.d.ts +26 -23
  232. package/src/tree/TreeCollection.ts +29 -22
  233. package/src/tree/createTreeState.d.ts +7 -4
  234. package/src/tree/createTreeState.ts +39 -41
  235. package/src/tree/index.d.ts +10 -4
  236. package/src/tree/index.ts +3 -3
  237. package/src/tree/types.d.ts +131 -126
  238. package/src/tree/types.ts +22 -13
  239. package/src/utils/index.d.ts +2 -2
  240. package/src/utils/index.ts +1 -1
  241. package/src/utils/reactivity.d.ts +2 -2
  242. package/src/utils/reactivity.ts +3 -3
@@ -1,144 +1,254 @@
1
1
  /**
2
2
  * ColorField state management.
3
- * Based on @react-stately/color useColorFieldState.
3
+ * Based on @react-stately/color useColorFieldState and useColorChannelFieldState.
4
4
  */
5
5
 
6
- import { createSignal, createMemo, type Accessor } from 'solid-js';
7
- import type { Color, ColorChannel, ColorFormat } from './types';
8
- import { normalizeColor, parseColor } from './Color';
6
+ import { createEffect, createMemo, createSignal, type Accessor } from "solid-js";
7
+ import type { Color, ColorChannel, ColorChannelRange, ColorFormat, ColorSpace } from "./types";
8
+ import { createRGBColor, normalizeColor, parseColor } from "./Color";
9
+
10
+ const HEX_COLOR_MAX = 0xffffff;
11
+ const PARTIAL_HEX_PATTERN = /^#?[0-9a-f]{0,6}$/i;
9
12
 
10
13
  export interface ColorFieldStateOptions {
11
14
  /** The current color value (controlled). */
12
15
  value?: Color | string | null;
13
16
  /** The default color value (uncontrolled). */
14
- defaultValue?: Color | string;
17
+ defaultValue?: Color | string | null;
15
18
  /** Handler called when the color changes. */
16
19
  onChange?: (color: Color | null) => void;
17
20
  /** The color channel to edit (for single channel mode). */
18
21
  channel?: ColorChannel;
19
- /** The color format for parsing/displaying. */
22
+ /** The color space to use for channel mode. */
23
+ colorSpace?: ColorSpace;
24
+ /** The color format for parsing/displaying in hex mode. */
20
25
  colorFormat?: ColorFormat;
26
+ /** Locale used to format channel values. */
27
+ locale?: string;
21
28
  /** Whether the field is disabled. */
22
29
  isDisabled?: boolean;
23
30
  /** Whether the field is read-only. */
24
31
  isReadOnly?: boolean;
32
+ /** Whether the field is invalid. */
33
+ isInvalid?: boolean;
34
+ /** Whether the field is required. */
35
+ isRequired?: boolean;
25
36
  }
26
37
 
27
38
  export interface ColorFieldState {
28
- /** The current color value (null if invalid). */
39
+ /** The current color value (null if empty). */
29
40
  readonly value: Color | null;
41
+ /** The color used for channel calculations. */
42
+ readonly colorValue: Color;
30
43
  /** The current input text. */
31
44
  readonly inputValue: string;
45
+ /** The numeric value exposed by channel fields. */
46
+ readonly numberValue: number;
47
+ /** The channel minimum value after formatting normalization. */
48
+ readonly minValue: number;
49
+ /** The channel maximum value after formatting normalization. */
50
+ readonly maxValue: number;
51
+ /** The channel keyboard step after formatting normalization. */
52
+ readonly step: number;
53
+ /** The channel page step after formatting normalization. */
54
+ readonly pageSize: number;
55
+ /** Number formatter options for channel fields. */
56
+ readonly formatOptions: Intl.NumberFormatOptions;
32
57
  /** Whether the input is invalid. */
33
58
  readonly isInvalid: boolean;
34
59
  /** Whether the field is disabled. */
35
60
  readonly isDisabled: boolean;
36
61
  /** Whether the field is read-only. */
37
62
  readonly isReadOnly: boolean;
63
+ /** Whether the field is required. */
64
+ readonly isRequired: boolean;
38
65
  /** The color channel being edited (if single channel mode). */
39
66
  readonly channel: ColorChannel | undefined;
40
67
 
41
68
  /** Set the input text value. */
42
69
  setInputValue(value: string): void;
70
+ /** Set the color value. */
71
+ setColorValue(value: Color | null): void;
43
72
  /** Commit the current input value. */
44
73
  commit(): void;
45
- /** Increment the color channel value. */
74
+ /** Increment the color value. */
46
75
  increment(): void;
47
- /** Decrement the color channel value. */
76
+ /** Decrement the color value. */
48
77
  decrement(): void;
49
- /** Increment by page size. */
78
+ /** Increment by page size or to the maximum. */
50
79
  incrementToMax(): void;
51
- /** Decrement to minimum. */
80
+ /** Decrement by page size or to the minimum. */
52
81
  decrementToMin(): void;
53
- /** Validate the current input. */
54
- validate(): boolean;
82
+ /** Validate the current input or a candidate input. */
83
+ validate(value?: string): boolean;
55
84
  }
56
85
 
57
- /**
58
- * Creates state for a color field (text input for color values).
59
- */
60
- export function createColorFieldState(
61
- options: Accessor<ColorFieldStateOptions>
62
- ): ColorFieldState {
63
- const getOptions = () => options();
86
+ function normalizeNullableColor(value: Color | string | null | undefined): Color | null {
87
+ if (value == null) {
88
+ return null;
89
+ }
90
+ return normalizeColor(value);
91
+ }
64
92
 
65
- // Internal value state
66
- const [internalValue, setInternalValue] = createSignal<Color | null>(null);
67
- const [inputValue, setInputValueInternal] = createSignal('');
68
- const [isInvalid, setIsInvalid] = createSignal(false);
93
+ function getDefaultValue(options: ColorFieldStateOptions): Color | null {
94
+ if (options.defaultValue !== undefined) {
95
+ return normalizeNullableColor(options.defaultValue);
96
+ }
69
97
 
70
- // Initialize internal value
71
- const initValue = () => {
72
- const opts = getOptions();
73
- if (opts.defaultValue) {
74
- const color = normalizeColor(opts.defaultValue);
75
- return color;
76
- }
77
- return null;
98
+ if (options.value !== undefined) {
99
+ return normalizeNullableColor(options.value);
100
+ }
101
+
102
+ return null;
103
+ }
104
+
105
+ function convertColor(color: Color, colorSpace: ColorSpace | undefined): Color {
106
+ return colorSpace ? color.toFormat(colorSpace) : color;
107
+ }
108
+
109
+ function getDisplayColor(color: Color | null, colorSpace: ColorSpace | undefined): Color {
110
+ return convertColor(color ?? createRGBColor(0, 0, 0), colorSpace);
111
+ }
112
+
113
+ function getChannelMultiplier(color: Color, channel: ColorChannel): number {
114
+ const range = color.getChannelRange(channel);
115
+ const formatOptions = color.getChannelFormatOptions(channel);
116
+ return formatOptions.style === "percent" && range.maxValue === 100 ? 100 : 1;
117
+ }
118
+
119
+ function getNormalizedRange(color: Color, channel: ColorChannel): ColorChannelRange {
120
+ const multiplier = getChannelMultiplier(color, channel);
121
+ const range = color.getChannelRange(channel);
122
+
123
+ return {
124
+ minValue: range.minValue / multiplier,
125
+ maxValue: range.maxValue / multiplier,
126
+ step: range.step / multiplier,
127
+ pageSize: range.pageSize / multiplier,
78
128
  };
129
+ }
79
130
 
80
- // Set initial value
81
- if (internalValue() === null) {
82
- const init = initValue();
83
- if (init) {
84
- setInternalValue(init);
85
- setInputValueInternal(formatColorValue(init, getOptions().channel, getOptions().colorFormat));
86
- }
131
+ function formatColorValue(
132
+ color: Color | null,
133
+ channel: ColorChannel | undefined,
134
+ colorSpace: ColorSpace | undefined,
135
+ format: ColorFormat | undefined,
136
+ locale: string,
137
+ ): string {
138
+ if (!color) {
139
+ return "";
87
140
  }
88
141
 
89
- // Controlled vs uncontrolled value
142
+ const displayColor = getDisplayColor(color, colorSpace);
143
+
144
+ if (channel) {
145
+ return displayColor.formatChannelValue(channel, locale);
146
+ }
147
+
148
+ return displayColor.toString(format ?? "hex");
149
+ }
150
+
151
+ function normalizeHexInput(value: string): string {
152
+ return value.startsWith("#") ? value : `#${value}`;
153
+ }
154
+
155
+ function parseHexValue(value: string, format: ColorFormat | undefined): Color {
156
+ const color = parseColor(normalizeHexInput(value));
157
+ return format ? color.toFormat(format) : color;
158
+ }
159
+
160
+ function parseChannelValue(text: string, color: Color, channel: ColorChannel): number | null {
161
+ const trimmed = text.trim();
162
+ const hasPercent = trimmed.endsWith("%");
163
+ const parsed = Number.parseFloat(hasPercent ? trimmed.slice(0, -1) : trimmed);
164
+
165
+ if (Number.isNaN(parsed)) {
166
+ return null;
167
+ }
168
+
169
+ const range = color.getChannelRange(channel);
170
+ const formatOptions = color.getChannelFormatOptions(channel);
171
+ const multiplier = getChannelMultiplier(color, channel);
172
+ const rawValue = hasPercent && formatOptions.style === "percent" ? parsed : parsed * multiplier;
173
+
174
+ return Math.min(range.maxValue, Math.max(range.minValue, rawValue));
175
+ }
176
+
177
+ function hexIntToColor(value: number): Color {
178
+ const clamped = Math.min(HEX_COLOR_MAX, Math.max(0, Math.round(value)));
179
+ const red = (clamped >> 16) & 0xff;
180
+ const green = (clamped >> 8) & 0xff;
181
+ const blue = clamped & 0xff;
182
+ return createRGBColor(red, green, blue);
183
+ }
184
+
185
+ /**
186
+ * Creates state for a color field (text input for color values).
187
+ */
188
+ export function createColorFieldState(options: Accessor<ColorFieldStateOptions>): ColorFieldState {
189
+ const getOptions = () => options();
190
+ const initialOptions = getOptions();
191
+ const initialValue = getDefaultValue(initialOptions);
192
+
193
+ const [internalValue, setInternalValue] = createSignal<Color | null>(initialValue);
194
+ const [inputValue, setInputValueInternal] = createSignal(
195
+ formatColorValue(
196
+ initialValue,
197
+ initialOptions.channel,
198
+ initialOptions.colorSpace,
199
+ initialOptions.colorFormat,
200
+ initialOptions.locale ?? "en-US",
201
+ ),
202
+ );
203
+ const [invalidInput, setInvalidInput] = createSignal(false);
204
+
205
+ const channel = createMemo(() => getOptions().channel);
206
+ const colorSpace = createMemo(() => getOptions().colorSpace);
207
+ const locale = createMemo(() => getOptions().locale ?? "en-US");
208
+
90
209
  const value = createMemo(() => {
91
210
  const opts = getOptions();
92
211
  if (opts.value !== undefined) {
93
- if (opts.value === null) return null;
94
- return normalizeColor(opts.value);
212
+ return normalizeNullableColor(opts.value);
95
213
  }
96
214
  return internalValue();
97
215
  });
98
216
 
99
- const channel = createMemo(() => getOptions().channel);
217
+ const colorValue = createMemo(() => getDisplayColor(value(), colorSpace()));
100
218
  const isDisabled = createMemo(() => getOptions().isDisabled ?? false);
101
219
  const isReadOnly = createMemo(() => getOptions().isReadOnly ?? false);
220
+ const isRequired = createMemo(() => getOptions().isRequired ?? false);
221
+ const isInvalid = createMemo(() => (getOptions().isInvalid ?? false) || invalidInput());
102
222
 
103
- // Format color value for display
104
- function formatColorValue(
105
- color: Color | null,
106
- chan?: ColorChannel,
107
- format?: ColorFormat
108
- ): string {
109
- if (!color) return '';
223
+ const formatCurrentValue = (color: Color | null = value()) =>
224
+ formatColorValue(color, channel(), colorSpace(), getOptions().colorFormat, locale());
110
225
 
111
- // Single channel mode
112
- if (chan) {
113
- return String(color.getChannelValue(chan));
114
- }
115
-
116
- // Full color mode
117
- return color.toString(format ?? 'hex');
118
- }
226
+ createEffect(() => {
227
+ setInputValueInternal(formatCurrentValue());
228
+ setInvalidInput(false);
229
+ });
119
230
 
120
- // Update value
121
231
  const updateValue = (newColor: Color | null) => {
122
232
  const opts = getOptions();
123
233
 
124
- // Controlled mode
125
- if (opts.value !== undefined) {
126
- opts.onChange?.(newColor);
127
- return;
234
+ if (opts.value === undefined) {
235
+ setInternalValue(newColor);
128
236
  }
129
237
 
130
- // Uncontrolled mode
131
- setInternalValue(newColor);
132
238
  opts.onChange?.(newColor);
239
+ setInputValueInternal(formatCurrentValue(newColor));
240
+ setInvalidInput(false);
133
241
  };
134
242
 
135
- // Set input value
136
243
  const setInputValue = (text: string) => {
137
244
  setInputValueInternal(text);
138
- setIsInvalid(false);
245
+ setInvalidInput(false);
246
+ };
247
+
248
+ const setColorValue = (newColor: Color | null) => {
249
+ updateValue(newColor);
139
250
  };
140
251
 
141
- // Commit the input value
142
252
  const commit = () => {
143
253
  const text = inputValue().trim();
144
254
  const opts = getOptions();
@@ -146,129 +256,151 @@ export function createColorFieldState(
146
256
 
147
257
  if (!text) {
148
258
  updateValue(null);
149
- setIsInvalid(false);
150
259
  return;
151
260
  }
152
261
 
153
- try {
154
- let newColor: Color;
155
-
156
- if (chan) {
157
- // Single channel mode - parse as number and update channel
158
- const numValue = parseFloat(text);
159
- if (isNaN(numValue)) {
160
- setIsInvalid(true);
161
- return;
162
- }
163
-
164
- const currentColor = value();
165
- if (!currentColor) {
166
- setIsInvalid(true);
167
- return;
168
- }
169
-
170
- const range = currentColor.getChannelRange(chan);
171
- const clamped = Math.max(range.minValue, Math.min(range.maxValue, numValue));
172
- newColor = currentColor.withChannelValue(chan, clamped);
173
- } else {
174
- // Full color mode - parse the color string
175
- newColor = parseColor(text);
176
- if (opts.colorFormat) {
177
- newColor = newColor.toFormat(opts.colorFormat);
178
- }
262
+ if (chan) {
263
+ const displayColor = colorValue();
264
+ const rawValue = parseChannelValue(text, displayColor, chan);
265
+ if (rawValue == null) {
266
+ setInputValueInternal(formatCurrentValue());
267
+ setInvalidInput(false);
268
+ return;
179
269
  }
180
270
 
181
- updateValue(newColor);
182
- setInputValueInternal(formatColorValue(newColor, chan, opts.colorFormat));
183
- setIsInvalid(false);
271
+ updateValue(displayColor.withChannelValue(chan, rawValue));
272
+ return;
273
+ }
274
+
275
+ try {
276
+ updateValue(parseHexValue(text, opts.colorFormat));
184
277
  } catch {
185
- setIsInvalid(true);
278
+ setInputValueInternal(formatCurrentValue());
279
+ setInvalidInput(false);
186
280
  }
187
281
  };
188
282
 
189
- // Increment channel value
190
- const increment = () => {
191
- const chan = channel();
283
+ const incrementHex = (amount: number) => {
192
284
  const currentColor = value();
193
- if (!currentColor || !chan) return;
285
+ if (!currentColor) {
286
+ return;
287
+ }
194
288
 
195
- const currentVal = currentColor.getChannelValue(chan);
196
- const range = currentColor.getChannelRange(chan);
197
- const newVal = Math.min(range.maxValue, currentVal + range.step);
198
- const newColor = currentColor.withChannelValue(chan, newVal);
289
+ const newColor = hexIntToColor(currentColor.toFormat("rgb").toHexInt() + amount);
290
+ updateValue(getOptions().colorFormat ? newColor.toFormat(getOptions().colorFormat!) : newColor);
291
+ };
199
292
 
200
- updateValue(newColor);
201
- setInputValueInternal(formatColorValue(newColor, chan, getOptions().colorFormat));
293
+ const updateChannel = (amount: number | "min" | "max") => {
294
+ const chan = channel();
295
+ if (!chan) {
296
+ return;
297
+ }
298
+
299
+ const displayColor = colorValue();
300
+ const range = displayColor.getChannelRange(chan);
301
+ const currentValue = displayColor.getChannelValue(chan);
302
+ const nextValue =
303
+ amount === "min"
304
+ ? range.minValue
305
+ : amount === "max"
306
+ ? range.maxValue
307
+ : Math.min(range.maxValue, Math.max(range.minValue, currentValue + amount));
308
+
309
+ updateValue(displayColor.withChannelValue(chan, nextValue));
202
310
  };
203
311
 
204
- // Decrement channel value
205
- const decrement = () => {
312
+ const increment = () => {
206
313
  const chan = channel();
207
- const currentColor = value();
208
- if (!currentColor || !chan) return;
314
+ if (!chan) {
315
+ incrementHex(1);
316
+ return;
317
+ }
209
318
 
210
- const currentVal = currentColor.getChannelValue(chan);
211
- const range = currentColor.getChannelRange(chan);
212
- const newVal = Math.max(range.minValue, currentVal - range.step);
213
- const newColor = currentColor.withChannelValue(chan, newVal);
319
+ updateChannel(colorValue().getChannelRange(chan).step);
320
+ };
214
321
 
215
- updateValue(newColor);
216
- setInputValueInternal(formatColorValue(newColor, chan, getOptions().colorFormat));
322
+ const decrement = () => {
323
+ const chan = channel();
324
+ if (!chan) {
325
+ incrementHex(-1);
326
+ return;
327
+ }
328
+
329
+ updateChannel(-colorValue().getChannelRange(chan).step);
217
330
  };
218
331
 
219
- // Increment to max
220
332
  const incrementToMax = () => {
221
333
  const chan = channel();
222
- const currentColor = value();
223
- if (!currentColor || !chan) return;
224
-
225
- const range = currentColor.getChannelRange(chan);
226
- const newColor = currentColor.withChannelValue(chan, range.maxValue);
334
+ if (!chan) {
335
+ incrementHex(HEX_COLOR_MAX);
336
+ return;
337
+ }
227
338
 
228
- updateValue(newColor);
229
- setInputValueInternal(formatColorValue(newColor, chan, getOptions().colorFormat));
339
+ updateChannel("max");
230
340
  };
231
341
 
232
- // Decrement to min
233
342
  const decrementToMin = () => {
234
343
  const chan = channel();
235
- const currentColor = value();
236
- if (!currentColor || !chan) return;
237
-
238
- const range = currentColor.getChannelRange(chan);
239
- const newColor = currentColor.withChannelValue(chan, range.minValue);
344
+ if (!chan) {
345
+ incrementHex(-HEX_COLOR_MAX);
346
+ return;
347
+ }
240
348
 
241
- updateValue(newColor);
242
- setInputValueInternal(formatColorValue(newColor, chan, getOptions().colorFormat));
349
+ updateChannel("min");
243
350
  };
244
351
 
245
- // Validate input
246
- const validate = () => {
247
- const text = inputValue().trim();
352
+ const validate = (candidate?: string) => {
353
+ const text = (candidate ?? inputValue()).trim();
248
354
  const chan = channel();
249
355
 
250
- if (!text) return true;
356
+ if (!text) {
357
+ return true;
358
+ }
251
359
 
252
- try {
253
- if (chan) {
254
- const numValue = parseFloat(text);
255
- return !isNaN(numValue);
256
- } else {
257
- parseColor(text);
258
- return true;
259
- }
260
- } catch {
261
- return false;
360
+ if (chan) {
361
+ return parseChannelValue(text, colorValue(), chan) != null;
262
362
  }
363
+
364
+ return PARTIAL_HEX_PATTERN.test(text);
263
365
  };
264
366
 
265
367
  return {
266
368
  get value() {
267
369
  return value();
268
370
  },
371
+ get colorValue() {
372
+ return colorValue();
373
+ },
269
374
  get inputValue() {
270
375
  return inputValue();
271
376
  },
377
+ get numberValue() {
378
+ const chan = channel();
379
+ if (!chan || value() === null) {
380
+ return Number.NaN;
381
+ }
382
+ return colorValue().getChannelValue(chan) / getChannelMultiplier(colorValue(), chan);
383
+ },
384
+ get minValue() {
385
+ const chan = channel();
386
+ return chan ? getNormalizedRange(colorValue(), chan).minValue : 0;
387
+ },
388
+ get maxValue() {
389
+ const chan = channel();
390
+ return chan ? getNormalizedRange(colorValue(), chan).maxValue : HEX_COLOR_MAX;
391
+ },
392
+ get step() {
393
+ const chan = channel();
394
+ return chan ? getNormalizedRange(colorValue(), chan).step : 1;
395
+ },
396
+ get pageSize() {
397
+ const chan = channel();
398
+ return chan ? getNormalizedRange(colorValue(), chan).pageSize : 0x100000;
399
+ },
400
+ get formatOptions() {
401
+ const chan = channel();
402
+ return chan ? colorValue().getChannelFormatOptions(chan) : {};
403
+ },
272
404
  get isInvalid() {
273
405
  return isInvalid();
274
406
  },
@@ -278,10 +410,14 @@ export function createColorFieldState(
278
410
  get isReadOnly() {
279
411
  return isReadOnly();
280
412
  },
413
+ get isRequired() {
414
+ return isRequired();
415
+ },
281
416
  get channel() {
282
417
  return channel();
283
418
  },
284
419
  setInputValue,
420
+ setColorValue,
285
421
  commit,
286
422
  increment,
287
423
  decrement,