@proyecto-viviana/solid-stately 0.2.2 → 0.2.4

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 (218) hide show
  1. package/dist/index.js +43 -43
  2. package/dist/index.js.map +1 -1
  3. package/package.json +7 -5
  4. package/src/autocomplete/createAutocompleteState.d.ts +46 -0
  5. package/src/autocomplete/createAutocompleteState.d.ts.map +1 -0
  6. package/src/autocomplete/createAutocompleteState.ts +90 -0
  7. package/src/autocomplete/index.d.ts +2 -0
  8. package/src/autocomplete/index.d.ts.map +1 -0
  9. package/src/autocomplete/index.ts +5 -0
  10. package/src/calendar/createCalendarState.d.ts +130 -0
  11. package/src/calendar/createCalendarState.d.ts.map +1 -0
  12. package/src/calendar/createCalendarState.ts +461 -0
  13. package/src/calendar/createDateFieldState.d.ts +110 -0
  14. package/src/calendar/createDateFieldState.d.ts.map +1 -0
  15. package/src/calendar/createDateFieldState.ts +562 -0
  16. package/src/calendar/createRangeCalendarState.d.ts +146 -0
  17. package/src/calendar/createRangeCalendarState.d.ts.map +1 -0
  18. package/src/calendar/createRangeCalendarState.ts +535 -0
  19. package/src/calendar/createTimeFieldState.d.ts +95 -0
  20. package/src/calendar/createTimeFieldState.d.ts.map +1 -0
  21. package/src/calendar/createTimeFieldState.ts +483 -0
  22. package/src/calendar/index.d.ts +7 -0
  23. package/src/calendar/index.d.ts.map +1 -0
  24. package/src/calendar/index.ts +81 -0
  25. package/src/checkbox/createCheckboxGroupState.d.ts +71 -0
  26. package/src/checkbox/createCheckboxGroupState.d.ts.map +1 -0
  27. package/src/checkbox/createCheckboxGroupState.ts +193 -0
  28. package/src/checkbox/index.d.ts +2 -0
  29. package/src/checkbox/index.d.ts.map +1 -0
  30. package/src/checkbox/index.ts +5 -0
  31. package/src/collections/ListCollection.d.ts +37 -0
  32. package/src/collections/ListCollection.d.ts.map +1 -0
  33. package/src/collections/ListCollection.ts +146 -0
  34. package/src/collections/createListState.d.ts +79 -0
  35. package/src/collections/createListState.d.ts.map +1 -0
  36. package/src/collections/createListState.ts +264 -0
  37. package/src/collections/createMenuState.d.ts +50 -0
  38. package/src/collections/createMenuState.d.ts.map +1 -0
  39. package/src/collections/createMenuState.ts +106 -0
  40. package/src/collections/createSelectionState.d.ts +76 -0
  41. package/src/collections/createSelectionState.d.ts.map +1 -0
  42. package/src/collections/createSelectionState.ts +336 -0
  43. package/src/collections/index.d.ts +6 -0
  44. package/src/collections/index.d.ts.map +1 -0
  45. package/src/collections/index.ts +46 -0
  46. package/src/collections/types.d.ts +147 -0
  47. package/src/collections/types.d.ts.map +1 -0
  48. package/src/collections/types.ts +169 -0
  49. package/src/color/Color.d.ts +28 -0
  50. package/src/color/Color.d.ts.map +1 -0
  51. package/src/color/Color.ts +951 -0
  52. package/src/color/createColorAreaState.d.ts +76 -0
  53. package/src/color/createColorAreaState.d.ts.map +1 -0
  54. package/src/color/createColorAreaState.ts +293 -0
  55. package/src/color/createColorFieldState.d.ts +55 -0
  56. package/src/color/createColorFieldState.d.ts.map +1 -0
  57. package/src/color/createColorFieldState.ts +292 -0
  58. package/src/color/createColorSliderState.d.ts +67 -0
  59. package/src/color/createColorSliderState.d.ts.map +1 -0
  60. package/src/color/createColorSliderState.ts +241 -0
  61. package/src/color/createColorWheelState.d.ts +51 -0
  62. package/src/color/createColorWheelState.d.ts.map +1 -0
  63. package/src/color/createColorWheelState.ts +211 -0
  64. package/src/color/index.d.ts +10 -0
  65. package/src/color/index.d.ts.map +1 -0
  66. package/src/color/index.ts +47 -0
  67. package/src/color/types.d.ts +106 -0
  68. package/src/color/types.d.ts.map +1 -0
  69. package/src/color/types.ts +127 -0
  70. package/src/combobox/createComboBoxState.d.ts +125 -0
  71. package/src/combobox/createComboBoxState.d.ts.map +1 -0
  72. package/src/combobox/createComboBoxState.ts +703 -0
  73. package/src/combobox/index.d.ts +5 -0
  74. package/src/combobox/index.d.ts.map +1 -0
  75. package/src/combobox/index.ts +13 -0
  76. package/src/disclosure/createDisclosureState.d.ts +64 -0
  77. package/src/disclosure/createDisclosureState.d.ts.map +1 -0
  78. package/src/disclosure/createDisclosureState.ts +193 -0
  79. package/src/disclosure/index.d.ts +2 -0
  80. package/src/disclosure/index.d.ts.map +1 -0
  81. package/src/disclosure/index.ts +9 -0
  82. package/src/dnd/createDragState.d.ts +59 -0
  83. package/src/dnd/createDragState.d.ts.map +1 -0
  84. package/src/dnd/createDragState.ts +153 -0
  85. package/src/dnd/createDraggableCollectionState.d.ts +57 -0
  86. package/src/dnd/createDraggableCollectionState.d.ts.map +1 -0
  87. package/src/dnd/createDraggableCollectionState.ts +165 -0
  88. package/src/dnd/createDropState.d.ts +61 -0
  89. package/src/dnd/createDropState.d.ts.map +1 -0
  90. package/src/dnd/createDropState.ts +212 -0
  91. package/src/dnd/createDroppableCollectionState.d.ts +78 -0
  92. package/src/dnd/createDroppableCollectionState.d.ts.map +1 -0
  93. package/src/dnd/createDroppableCollectionState.ts +357 -0
  94. package/src/dnd/index.d.ts +11 -0
  95. package/src/dnd/index.d.ts.map +1 -0
  96. package/src/dnd/index.ts +76 -0
  97. package/src/dnd/types.d.ts +264 -0
  98. package/src/dnd/types.d.ts.map +1 -0
  99. package/src/dnd/types.ts +317 -0
  100. package/src/form/createFormValidationState.d.ts +100 -0
  101. package/src/form/createFormValidationState.d.ts.map +1 -0
  102. package/src/form/createFormValidationState.ts +389 -0
  103. package/src/form/index.d.ts +2 -0
  104. package/src/form/index.d.ts.map +1 -0
  105. package/src/form/index.ts +15 -0
  106. package/src/grid/createGridState.d.ts +12 -0
  107. package/src/grid/createGridState.d.ts.map +1 -0
  108. package/src/grid/createGridState.ts +327 -0
  109. package/src/grid/index.d.ts +7 -0
  110. package/src/grid/index.d.ts.map +1 -0
  111. package/src/grid/index.ts +13 -0
  112. package/src/grid/types.d.ts +156 -0
  113. package/src/grid/types.d.ts.map +1 -0
  114. package/src/grid/types.ts +179 -0
  115. package/src/index.d.ts +26 -0
  116. package/src/index.d.ts.map +1 -0
  117. package/src/index.ts +383 -0
  118. package/src/numberfield/createNumberFieldState.d.ts +65 -0
  119. package/src/numberfield/createNumberFieldState.d.ts.map +1 -0
  120. package/src/numberfield/createNumberFieldState.ts +383 -0
  121. package/src/numberfield/index.d.ts +2 -0
  122. package/src/numberfield/index.d.ts.map +1 -0
  123. package/src/numberfield/index.ts +5 -0
  124. package/src/overlays/createOverlayTriggerState.d.ts +32 -0
  125. package/src/overlays/createOverlayTriggerState.d.ts.map +1 -0
  126. package/src/overlays/createOverlayTriggerState.ts +67 -0
  127. package/src/overlays/index.d.ts +2 -0
  128. package/src/overlays/index.d.ts.map +1 -0
  129. package/src/overlays/index.ts +5 -0
  130. package/src/radio/createRadioGroupState.d.ts +77 -0
  131. package/src/radio/createRadioGroupState.d.ts.map +1 -0
  132. package/src/radio/createRadioGroupState.ts +201 -0
  133. package/src/radio/index.d.ts +2 -0
  134. package/src/radio/index.d.ts.map +1 -0
  135. package/src/radio/index.ts +6 -0
  136. package/src/searchfield/createSearchFieldState.d.ts +25 -0
  137. package/src/searchfield/createSearchFieldState.d.ts.map +1 -0
  138. package/src/searchfield/createSearchFieldState.ts +62 -0
  139. package/src/searchfield/index.d.ts +3 -0
  140. package/src/searchfield/index.d.ts.map +1 -0
  141. package/src/searchfield/index.ts +5 -0
  142. package/src/select/createSelectState.d.ts +73 -0
  143. package/src/select/createSelectState.d.ts.map +1 -0
  144. package/src/select/createSelectState.ts +181 -0
  145. package/src/select/index.d.ts +2 -0
  146. package/src/select/index.d.ts.map +1 -0
  147. package/src/select/index.ts +5 -0
  148. package/src/slider/createSliderState.d.ts +72 -0
  149. package/src/slider/createSliderState.d.ts.map +1 -0
  150. package/src/slider/createSliderState.ts +211 -0
  151. package/src/slider/index.d.ts +3 -0
  152. package/src/slider/index.d.ts.map +1 -0
  153. package/src/slider/index.ts +6 -0
  154. package/src/ssr/index.d.ts +28 -0
  155. package/src/ssr/index.d.ts.map +1 -0
  156. package/src/ssr/index.ts +41 -0
  157. package/src/table/TableCollection.d.ts +52 -0
  158. package/src/table/TableCollection.d.ts.map +1 -0
  159. package/src/table/TableCollection.ts +388 -0
  160. package/src/table/createTableState.d.ts +12 -0
  161. package/src/table/createTableState.d.ts.map +1 -0
  162. package/src/table/createTableState.ts +127 -0
  163. package/src/table/index.d.ts +8 -0
  164. package/src/table/index.d.ts.map +1 -0
  165. package/src/table/index.ts +18 -0
  166. package/src/table/types.d.ts +139 -0
  167. package/src/table/types.d.ts.map +1 -0
  168. package/src/table/types.ts +150 -0
  169. package/src/tabs/createTabListState.d.ts +68 -0
  170. package/src/tabs/createTabListState.d.ts.map +1 -0
  171. package/src/tabs/createTabListState.ts +240 -0
  172. package/src/tabs/index.d.ts +2 -0
  173. package/src/tabs/index.d.ts.map +1 -0
  174. package/src/tabs/index.ts +7 -0
  175. package/src/textfield/createTextFieldState.d.ts +30 -0
  176. package/src/textfield/createTextFieldState.d.ts.map +1 -0
  177. package/src/textfield/createTextFieldState.ts +75 -0
  178. package/src/textfield/index.d.ts +2 -0
  179. package/src/textfield/index.d.ts.map +1 -0
  180. package/src/textfield/index.ts +5 -0
  181. package/src/toast/createToastState.d.ts +118 -0
  182. package/src/toast/createToastState.d.ts.map +1 -0
  183. package/src/toast/createToastState.ts +316 -0
  184. package/src/toast/index.d.ts +2 -0
  185. package/src/toast/index.d.ts.map +1 -0
  186. package/src/toast/index.ts +11 -0
  187. package/src/toggle/createToggleState.d.ts +34 -0
  188. package/src/toggle/createToggleState.d.ts.map +1 -0
  189. package/src/toggle/createToggleState.ts +94 -0
  190. package/src/toggle/index.d.ts +2 -0
  191. package/src/toggle/index.d.ts.map +1 -0
  192. package/src/toggle/index.ts +5 -0
  193. package/src/tooltip/createTooltipTriggerState.d.ts +39 -0
  194. package/src/tooltip/createTooltipTriggerState.d.ts.map +1 -0
  195. package/src/tooltip/createTooltipTriggerState.ts +183 -0
  196. package/src/tooltip/index.d.ts +2 -0
  197. package/src/tooltip/index.d.ts.map +1 -0
  198. package/src/tooltip/index.ts +6 -0
  199. package/src/tree/TreeCollection.d.ts +40 -0
  200. package/src/tree/TreeCollection.d.ts.map +1 -0
  201. package/src/tree/TreeCollection.ts +175 -0
  202. package/src/tree/createTreeState.d.ts +14 -0
  203. package/src/tree/createTreeState.d.ts.map +1 -0
  204. package/src/tree/createTreeState.ts +392 -0
  205. package/src/tree/index.d.ts +7 -0
  206. package/src/tree/index.d.ts.map +1 -0
  207. package/src/tree/index.ts +13 -0
  208. package/src/tree/types.d.ts +157 -0
  209. package/src/tree/types.d.ts.map +1 -0
  210. package/src/tree/types.ts +174 -0
  211. package/src/utils/index.d.ts +2 -0
  212. package/src/utils/index.d.ts.map +1 -0
  213. package/src/utils/index.ts +1 -0
  214. package/src/utils/reactivity.d.ts +28 -0
  215. package/src/utils/reactivity.d.ts.map +1 -0
  216. package/src/utils/reactivity.ts +36 -0
  217. package/dist/index.jsx +0 -6408
  218. package/dist/index.jsx.map +0 -7
@@ -0,0 +1,951 @@
1
+ /**
2
+ * Color class implementation.
3
+ * Based on @react-stately/color.
4
+ *
5
+ * Provides color manipulation, conversion, and formatting.
6
+ */
7
+
8
+ import type {
9
+ Color,
10
+ ColorFormat,
11
+ ColorSpace,
12
+ ColorChannel,
13
+ ColorChannelRange,
14
+ ColorAxes,
15
+ RGBColor,
16
+ HSLColor,
17
+ HSBColor,
18
+ } from './types';
19
+
20
+ // Channel ranges
21
+ const RGB_CHANNEL_RANGE: ColorChannelRange = {
22
+ minValue: 0,
23
+ maxValue: 255,
24
+ step: 1,
25
+ pageSize: 17,
26
+ };
27
+
28
+ const ALPHA_CHANNEL_RANGE: ColorChannelRange = {
29
+ minValue: 0,
30
+ maxValue: 1,
31
+ step: 0.01,
32
+ pageSize: 0.1,
33
+ };
34
+
35
+ const HUE_CHANNEL_RANGE: ColorChannelRange = {
36
+ minValue: 0,
37
+ maxValue: 360,
38
+ step: 1,
39
+ pageSize: 15,
40
+ };
41
+
42
+ const PERCENT_CHANNEL_RANGE: ColorChannelRange = {
43
+ minValue: 0,
44
+ maxValue: 100,
45
+ step: 1,
46
+ pageSize: 10,
47
+ };
48
+
49
+ // Channel names (English only for now)
50
+ const CHANNEL_NAMES: Record<ColorChannel, string> = {
51
+ hue: 'Hue',
52
+ saturation: 'Saturation',
53
+ brightness: 'Brightness',
54
+ lightness: 'Lightness',
55
+ red: 'Red',
56
+ green: 'Green',
57
+ blue: 'Blue',
58
+ alpha: 'Alpha',
59
+ };
60
+
61
+ // Hue names for color naming
62
+ const HUE_NAMES: Array<{ max: number; name: string }> = [
63
+ { max: 15, name: 'pink' },
64
+ { max: 48, name: 'red' },
65
+ { max: 94, name: 'orange' },
66
+ { max: 135, name: 'yellow' },
67
+ { max: 175, name: 'green' },
68
+ { max: 264, name: 'cyan' },
69
+ { max: 284, name: 'blue' },
70
+ { max: 320, name: 'purple' },
71
+ { max: 349, name: 'magenta' },
72
+ { max: 360, name: 'pink' },
73
+ ];
74
+
75
+ /**
76
+ * Clamp a value to a range.
77
+ */
78
+ function clamp(value: number, min: number, max: number): number {
79
+ return Math.min(Math.max(value, min), max);
80
+ }
81
+
82
+ /**
83
+ * Round to fixed decimal places.
84
+ */
85
+ function toFixed(value: number, decimals: number): number {
86
+ return Math.round(value * Math.pow(10, decimals)) / Math.pow(10, decimals);
87
+ }
88
+
89
+ /**
90
+ * Convert RGB to HSL.
91
+ */
92
+ function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {
93
+ r /= 255;
94
+ g /= 255;
95
+ b /= 255;
96
+
97
+ const max = Math.max(r, g, b);
98
+ const min = Math.min(r, g, b);
99
+ let h = 0;
100
+ let s = 0;
101
+ const l = (max + min) / 2;
102
+
103
+ if (max !== min) {
104
+ const d = max - min;
105
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
106
+
107
+ switch (max) {
108
+ case r:
109
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
110
+ break;
111
+ case g:
112
+ h = ((b - r) / d + 2) / 6;
113
+ break;
114
+ case b:
115
+ h = ((r - g) / d + 4) / 6;
116
+ break;
117
+ }
118
+ }
119
+
120
+ return {
121
+ h: Math.round(h * 360),
122
+ s: Math.round(s * 100),
123
+ l: Math.round(l * 100),
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Convert HSL to RGB.
129
+ */
130
+ function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {
131
+ h /= 360;
132
+ s /= 100;
133
+ l /= 100;
134
+
135
+ let r: number, g: number, b: number;
136
+
137
+ if (s === 0) {
138
+ r = g = b = l;
139
+ } else {
140
+ const hue2rgb = (p: number, q: number, t: number) => {
141
+ if (t < 0) t += 1;
142
+ if (t > 1) t -= 1;
143
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
144
+ if (t < 1 / 2) return q;
145
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
146
+ return p;
147
+ };
148
+
149
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
150
+ const p = 2 * l - q;
151
+ r = hue2rgb(p, q, h + 1 / 3);
152
+ g = hue2rgb(p, q, h);
153
+ b = hue2rgb(p, q, h - 1 / 3);
154
+ }
155
+
156
+ return {
157
+ r: Math.round(r * 255),
158
+ g: Math.round(g * 255),
159
+ b: Math.round(b * 255),
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Convert RGB to HSB.
165
+ */
166
+ function rgbToHsb(r: number, g: number, b: number): { h: number; s: number; b: number } {
167
+ r /= 255;
168
+ g /= 255;
169
+ b /= 255;
170
+
171
+ const max = Math.max(r, g, b);
172
+ const min = Math.min(r, g, b);
173
+ let h = 0;
174
+ const v = max;
175
+ const d = max - min;
176
+ const s = max === 0 ? 0 : d / max;
177
+
178
+ if (max !== min) {
179
+ switch (max) {
180
+ case r:
181
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
182
+ break;
183
+ case g:
184
+ h = ((b - r) / d + 2) / 6;
185
+ break;
186
+ case b:
187
+ h = ((r - g) / d + 4) / 6;
188
+ break;
189
+ }
190
+ }
191
+
192
+ return {
193
+ h: Math.round(h * 360),
194
+ s: Math.round(s * 100),
195
+ b: Math.round(v * 100),
196
+ };
197
+ }
198
+
199
+ /**
200
+ * Convert HSB to RGB.
201
+ */
202
+ function hsbToRgb(h: number, s: number, b: number): { r: number; g: number; b: number } {
203
+ h /= 360;
204
+ s /= 100;
205
+ b /= 100;
206
+
207
+ let r: number, g: number, bl: number;
208
+
209
+ const i = Math.floor(h * 6);
210
+ const f = h * 6 - i;
211
+ const p = b * (1 - s);
212
+ const q = b * (1 - f * s);
213
+ const t = b * (1 - (1 - f) * s);
214
+
215
+ switch (i % 6) {
216
+ case 0:
217
+ r = b;
218
+ g = t;
219
+ bl = p;
220
+ break;
221
+ case 1:
222
+ r = q;
223
+ g = b;
224
+ bl = p;
225
+ break;
226
+ case 2:
227
+ r = p;
228
+ g = b;
229
+ bl = t;
230
+ break;
231
+ case 3:
232
+ r = p;
233
+ g = q;
234
+ bl = b;
235
+ break;
236
+ case 4:
237
+ r = t;
238
+ g = p;
239
+ bl = b;
240
+ break;
241
+ default:
242
+ r = b;
243
+ g = p;
244
+ bl = q;
245
+ break;
246
+ }
247
+
248
+ return {
249
+ r: Math.round(r * 255),
250
+ g: Math.round(g * 255),
251
+ b: Math.round(bl * 255),
252
+ };
253
+ }
254
+
255
+ /**
256
+ * Get hue name from hue angle.
257
+ */
258
+ function getHueNameFromAngle(hue: number): string {
259
+ for (const { max, name } of HUE_NAMES) {
260
+ if (hue <= max) {
261
+ return name;
262
+ }
263
+ }
264
+ return 'pink';
265
+ }
266
+
267
+ /**
268
+ * Get a human-readable color name based on RGB values.
269
+ */
270
+ function getColorNameFromRgb(r: number, g: number, b: number, alpha: number): string {
271
+ // Convert to HSL for analysis
272
+ const { h, s, l } = rgbToHsl(r, g, b);
273
+
274
+ // Handle edge cases
275
+ if (l >= 100) {
276
+ return alpha < 1 ? `white ${Math.round(alpha * 100)}% transparent` : 'white';
277
+ }
278
+ if (l <= 0) {
279
+ return alpha < 1 ? `black ${Math.round(alpha * 100)}% transparent` : 'black';
280
+ }
281
+
282
+ // Build color name
283
+ const parts: string[] = [];
284
+
285
+ // Lightness descriptor
286
+ if (l < 30) {
287
+ parts.push('very dark');
288
+ } else if (l < 55) {
289
+ parts.push('dark');
290
+ } else if (l > 85) {
291
+ parts.push('very light');
292
+ } else if (l > 70) {
293
+ parts.push('light');
294
+ }
295
+
296
+ // Saturation/chroma descriptor
297
+ if (s < 10) {
298
+ parts.push('gray');
299
+ } else if (s < 30) {
300
+ parts.push('grayish');
301
+ } else if (s > 80) {
302
+ parts.push('vibrant');
303
+ }
304
+
305
+ // Hue name (skip if gray)
306
+ if (s >= 10) {
307
+ let hueName = getHueNameFromAngle(h);
308
+
309
+ // Special cases
310
+ if (hueName === 'orange' && l < 68) {
311
+ hueName = 'brown';
312
+ }
313
+ if (hueName === 'yellow' && l < 85 && s > 50) {
314
+ hueName = 'yellow green';
315
+ }
316
+
317
+ parts.push(hueName);
318
+ }
319
+
320
+ let name = parts.join(' ');
321
+
322
+ // Add transparency
323
+ if (alpha < 1) {
324
+ name += ` ${Math.round(alpha * 100)}% transparent`;
325
+ }
326
+
327
+ return name || 'color';
328
+ }
329
+
330
+ /**
331
+ * RGB Color implementation.
332
+ */
333
+ class RGBColorImpl implements Color {
334
+ private red: number;
335
+ private green: number;
336
+ private blue: number;
337
+ private alpha: number;
338
+
339
+ constructor(red: number, green: number, blue: number, alpha: number = 1) {
340
+ this.red = clamp(Math.round(red), 0, 255);
341
+ this.green = clamp(Math.round(green), 0, 255);
342
+ this.blue = clamp(Math.round(blue), 0, 255);
343
+ this.alpha = clamp(toFixed(alpha, 2), 0, 1);
344
+ }
345
+
346
+ toFormat(format: ColorFormat): Color {
347
+ switch (format) {
348
+ case 'hex':
349
+ case 'hexa':
350
+ case 'rgb':
351
+ case 'rgba':
352
+ return this.clone();
353
+ case 'hsl':
354
+ case 'hsla': {
355
+ const { h, s, l } = rgbToHsl(this.red, this.green, this.blue);
356
+ return new HSLColorImpl(h, s, l, this.alpha);
357
+ }
358
+ case 'hsb':
359
+ case 'hsba': {
360
+ const { h, s, b } = rgbToHsb(this.red, this.green, this.blue);
361
+ return new HSBColorImpl(h, s, b, this.alpha);
362
+ }
363
+ default:
364
+ throw new Error(`Unsupported format: ${format}`);
365
+ }
366
+ }
367
+
368
+ toString(format?: ColorFormat | 'css'): string {
369
+ const f = format ?? 'css';
370
+
371
+ switch (f) {
372
+ case 'hex':
373
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue.toString(16).padStart(2, '0')}`;
374
+ case 'hexa':
375
+ return `#${this.red.toString(16).padStart(2, '0')}${this.green.toString(16).padStart(2, '0')}${this.blue.toString(16).padStart(2, '0')}${Math.round(this.alpha * 255).toString(16).padStart(2, '0')}`;
376
+ case 'rgb':
377
+ return `rgb(${this.red}, ${this.green}, ${this.blue})`;
378
+ case 'rgba':
379
+ case 'css':
380
+ return this.alpha === 1
381
+ ? `rgb(${this.red}, ${this.green}, ${this.blue})`
382
+ : `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
383
+ default:
384
+ return this.toFormat(f as ColorFormat).toString(f);
385
+ }
386
+ }
387
+
388
+ clone(): Color {
389
+ return new RGBColorImpl(this.red, this.green, this.blue, this.alpha);
390
+ }
391
+
392
+ toHexInt(): number {
393
+ return (this.red << 16) | (this.green << 8) | this.blue;
394
+ }
395
+
396
+ getChannelValue(channel: ColorChannel): number {
397
+ switch (channel) {
398
+ case 'red':
399
+ return this.red;
400
+ case 'green':
401
+ return this.green;
402
+ case 'blue':
403
+ return this.blue;
404
+ case 'alpha':
405
+ return this.alpha;
406
+ // Cross-color-space channels - convert to HSB
407
+ case 'hue':
408
+ case 'saturation':
409
+ case 'brightness':
410
+ return this.toFormat('hsb').getChannelValue(channel);
411
+ case 'lightness':
412
+ return this.toFormat('hsl').getChannelValue(channel);
413
+ default:
414
+ throw new Error(`Invalid channel: ${channel}`);
415
+ }
416
+ }
417
+
418
+ withChannelValue(channel: ColorChannel, value: number): Color {
419
+ switch (channel) {
420
+ case 'red':
421
+ return new RGBColorImpl(value, this.green, this.blue, this.alpha);
422
+ case 'green':
423
+ return new RGBColorImpl(this.red, value, this.blue, this.alpha);
424
+ case 'blue':
425
+ return new RGBColorImpl(this.red, this.green, value, this.alpha);
426
+ case 'alpha':
427
+ return new RGBColorImpl(this.red, this.green, this.blue, value);
428
+ // Cross-color-space channels - convert, update, convert back
429
+ case 'hue':
430
+ case 'saturation':
431
+ case 'brightness':
432
+ return this.toFormat('hsb').withChannelValue(channel, value).toFormat('rgb');
433
+ case 'lightness':
434
+ return this.toFormat('hsl').withChannelValue(channel, value).toFormat('rgb');
435
+ default:
436
+ throw new Error(`Invalid channel: ${channel}`);
437
+ }
438
+ }
439
+
440
+ getChannelRange(channel: ColorChannel): ColorChannelRange {
441
+ switch (channel) {
442
+ case 'red':
443
+ case 'green':
444
+ case 'blue':
445
+ return RGB_CHANNEL_RANGE;
446
+ case 'alpha':
447
+ return ALPHA_CHANNEL_RANGE;
448
+ case 'hue':
449
+ return HUE_CHANNEL_RANGE;
450
+ case 'saturation':
451
+ case 'brightness':
452
+ case 'lightness':
453
+ return PERCENT_CHANNEL_RANGE;
454
+ default:
455
+ throw new Error(`Invalid channel: ${channel}`);
456
+ }
457
+ }
458
+
459
+ getChannelName(channel: ColorChannel, _locale: string): string {
460
+ return CHANNEL_NAMES[channel] || channel;
461
+ }
462
+
463
+ getChannelFormatOptions(channel: ColorChannel): Intl.NumberFormatOptions {
464
+ if (channel === 'alpha') {
465
+ return { style: 'percent' };
466
+ }
467
+ return { maximumFractionDigits: 0 };
468
+ }
469
+
470
+ formatChannelValue(channel: ColorChannel, locale: string): string {
471
+ const value = this.getChannelValue(channel);
472
+ const options = this.getChannelFormatOptions(channel);
473
+ return new Intl.NumberFormat(locale, options).format(value);
474
+ }
475
+
476
+ getColorSpace(): ColorSpace {
477
+ return 'rgb';
478
+ }
479
+
480
+ getColorSpaceAxes(xyChannels?: { xChannel?: ColorChannel; yChannel?: ColorChannel }): ColorAxes {
481
+ const xChannel = xyChannels?.xChannel ?? 'red';
482
+ const yChannel = xyChannels?.yChannel ?? 'green';
483
+ const channels: ColorChannel[] = ['red', 'green', 'blue'];
484
+ const zChannel = channels.find((c) => c !== xChannel && c !== yChannel) ?? 'blue';
485
+ return { xChannel, yChannel, zChannel };
486
+ }
487
+
488
+ getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
489
+ return ['red', 'green', 'blue'];
490
+ }
491
+
492
+ getColorName(locale: string): string {
493
+ return getColorNameFromRgb(this.red, this.green, this.blue, this.alpha);
494
+ }
495
+
496
+ getHueName(_locale: string): string {
497
+ const { h } = rgbToHsl(this.red, this.green, this.blue);
498
+ return getHueNameFromAngle(h);
499
+ }
500
+ }
501
+
502
+ /**
503
+ * HSL Color implementation.
504
+ */
505
+ class HSLColorImpl implements Color {
506
+ private hue: number;
507
+ private saturation: number;
508
+ private lightness: number;
509
+ private alpha: number;
510
+
511
+ constructor(hue: number, saturation: number, lightness: number, alpha: number = 1) {
512
+ this.hue = clamp(Math.round(hue) % 360, 0, 360);
513
+ this.saturation = clamp(Math.round(saturation), 0, 100);
514
+ this.lightness = clamp(Math.round(lightness), 0, 100);
515
+ this.alpha = clamp(toFixed(alpha, 2), 0, 1);
516
+ }
517
+
518
+ toFormat(format: ColorFormat): Color {
519
+ switch (format) {
520
+ case 'hsl':
521
+ case 'hsla':
522
+ return this.clone();
523
+ case 'hex':
524
+ case 'hexa':
525
+ case 'rgb':
526
+ case 'rgba': {
527
+ const { r, g, b } = hslToRgb(this.hue, this.saturation, this.lightness);
528
+ return new RGBColorImpl(r, g, b, this.alpha);
529
+ }
530
+ case 'hsb':
531
+ case 'hsba': {
532
+ const { r, g, b } = hslToRgb(this.hue, this.saturation, this.lightness);
533
+ const hsb = rgbToHsb(r, g, b);
534
+ return new HSBColorImpl(hsb.h, hsb.s, hsb.b, this.alpha);
535
+ }
536
+ default:
537
+ throw new Error(`Unsupported format: ${format}`);
538
+ }
539
+ }
540
+
541
+ toString(format?: ColorFormat | 'css'): string {
542
+ const f = format ?? 'css';
543
+
544
+ switch (f) {
545
+ case 'hsl':
546
+ return `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`;
547
+ case 'hsla':
548
+ case 'css':
549
+ return this.alpha === 1
550
+ ? `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`
551
+ : `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha})`;
552
+ default:
553
+ return this.toFormat(f as ColorFormat).toString(f);
554
+ }
555
+ }
556
+
557
+ clone(): Color {
558
+ return new HSLColorImpl(this.hue, this.saturation, this.lightness, this.alpha);
559
+ }
560
+
561
+ toHexInt(): number {
562
+ return this.toFormat('rgb').toHexInt();
563
+ }
564
+
565
+ getChannelValue(channel: ColorChannel): number {
566
+ switch (channel) {
567
+ case 'hue':
568
+ return this.hue;
569
+ case 'saturation':
570
+ return this.saturation;
571
+ case 'lightness':
572
+ return this.lightness;
573
+ case 'alpha':
574
+ return this.alpha;
575
+ // Cross-color-space channels
576
+ case 'red':
577
+ case 'green':
578
+ case 'blue':
579
+ return this.toFormat('rgb').getChannelValue(channel);
580
+ case 'brightness':
581
+ return this.toFormat('hsb').getChannelValue(channel);
582
+ default:
583
+ throw new Error(`Invalid channel: ${channel}`);
584
+ }
585
+ }
586
+
587
+ withChannelValue(channel: ColorChannel, value: number): Color {
588
+ switch (channel) {
589
+ case 'hue':
590
+ return new HSLColorImpl(value, this.saturation, this.lightness, this.alpha);
591
+ case 'saturation':
592
+ return new HSLColorImpl(this.hue, value, this.lightness, this.alpha);
593
+ case 'lightness':
594
+ return new HSLColorImpl(this.hue, this.saturation, value, this.alpha);
595
+ case 'alpha':
596
+ return new HSLColorImpl(this.hue, this.saturation, this.lightness, value);
597
+ // Cross-color-space channels
598
+ case 'red':
599
+ case 'green':
600
+ case 'blue':
601
+ return this.toFormat('rgb').withChannelValue(channel, value).toFormat('hsl');
602
+ case 'brightness':
603
+ return this.toFormat('hsb').withChannelValue(channel, value).toFormat('hsl');
604
+ default:
605
+ throw new Error(`Invalid channel: ${channel}`);
606
+ }
607
+ }
608
+
609
+ getChannelRange(channel: ColorChannel): ColorChannelRange {
610
+ switch (channel) {
611
+ case 'hue':
612
+ return HUE_CHANNEL_RANGE;
613
+ case 'saturation':
614
+ case 'lightness':
615
+ case 'brightness':
616
+ return PERCENT_CHANNEL_RANGE;
617
+ case 'alpha':
618
+ return ALPHA_CHANNEL_RANGE;
619
+ case 'red':
620
+ case 'green':
621
+ case 'blue':
622
+ return RGB_CHANNEL_RANGE;
623
+ default:
624
+ throw new Error(`Invalid channel: ${channel}`);
625
+ }
626
+ }
627
+
628
+ getChannelName(channel: ColorChannel, _locale: string): string {
629
+ return CHANNEL_NAMES[channel] || channel;
630
+ }
631
+
632
+ getChannelFormatOptions(channel: ColorChannel): Intl.NumberFormatOptions {
633
+ if (channel === 'alpha') {
634
+ return { style: 'percent' };
635
+ }
636
+ if (channel === 'hue') {
637
+ return { maximumFractionDigits: 0 };
638
+ }
639
+ return { style: 'percent', maximumFractionDigits: 0 };
640
+ }
641
+
642
+ formatChannelValue(channel: ColorChannel, locale: string): string {
643
+ const value = this.getChannelValue(channel);
644
+ const options = this.getChannelFormatOptions(channel);
645
+ if (channel === 'saturation' || channel === 'lightness') {
646
+ return new Intl.NumberFormat(locale, options).format(value / 100);
647
+ }
648
+ return new Intl.NumberFormat(locale, options).format(value);
649
+ }
650
+
651
+ getColorSpace(): ColorSpace {
652
+ return 'hsl';
653
+ }
654
+
655
+ getColorSpaceAxes(xyChannels?: { xChannel?: ColorChannel; yChannel?: ColorChannel }): ColorAxes {
656
+ const xChannel = xyChannels?.xChannel ?? 'saturation';
657
+ const yChannel = xyChannels?.yChannel ?? 'lightness';
658
+ const channels: ColorChannel[] = ['hue', 'saturation', 'lightness'];
659
+ const zChannel = channels.find((c) => c !== xChannel && c !== yChannel) ?? 'hue';
660
+ return { xChannel, yChannel, zChannel };
661
+ }
662
+
663
+ getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
664
+ return ['hue', 'saturation', 'lightness'];
665
+ }
666
+
667
+ getColorName(_locale: string): string {
668
+ const { r, g, b } = hslToRgb(this.hue, this.saturation, this.lightness);
669
+ return getColorNameFromRgb(r, g, b, this.alpha);
670
+ }
671
+
672
+ getHueName(_locale: string): string {
673
+ return getHueNameFromAngle(this.hue);
674
+ }
675
+ }
676
+
677
+ /**
678
+ * HSB Color implementation.
679
+ */
680
+ class HSBColorImpl implements Color {
681
+ private hue: number;
682
+ private saturation: number;
683
+ private brightness: number;
684
+ private alpha: number;
685
+
686
+ constructor(hue: number, saturation: number, brightness: number, alpha: number = 1) {
687
+ this.hue = clamp(Math.round(hue) % 360, 0, 360);
688
+ this.saturation = clamp(Math.round(saturation), 0, 100);
689
+ this.brightness = clamp(Math.round(brightness), 0, 100);
690
+ this.alpha = clamp(toFixed(alpha, 2), 0, 1);
691
+ }
692
+
693
+ toFormat(format: ColorFormat): Color {
694
+ switch (format) {
695
+ case 'hsb':
696
+ case 'hsba':
697
+ return this.clone();
698
+ case 'hex':
699
+ case 'hexa':
700
+ case 'rgb':
701
+ case 'rgba': {
702
+ const { r, g, b } = hsbToRgb(this.hue, this.saturation, this.brightness);
703
+ return new RGBColorImpl(r, g, b, this.alpha);
704
+ }
705
+ case 'hsl':
706
+ case 'hsla': {
707
+ const { r, g, b } = hsbToRgb(this.hue, this.saturation, this.brightness);
708
+ const hsl = rgbToHsl(r, g, b);
709
+ return new HSLColorImpl(hsl.h, hsl.s, hsl.l, this.alpha);
710
+ }
711
+ default:
712
+ throw new Error(`Unsupported format: ${format}`);
713
+ }
714
+ }
715
+
716
+ toString(format?: ColorFormat | 'css'): string {
717
+ const f = format ?? 'css';
718
+
719
+ switch (f) {
720
+ case 'hsb':
721
+ return `hsb(${this.hue}, ${this.saturation}%, ${this.brightness}%)`;
722
+ case 'hsba':
723
+ case 'css':
724
+ // HSB is not a standard CSS format, convert to RGB
725
+ return this.toFormat('rgba').toString('css');
726
+ default:
727
+ return this.toFormat(f as ColorFormat).toString(f);
728
+ }
729
+ }
730
+
731
+ clone(): Color {
732
+ return new HSBColorImpl(this.hue, this.saturation, this.brightness, this.alpha);
733
+ }
734
+
735
+ toHexInt(): number {
736
+ return this.toFormat('rgb').toHexInt();
737
+ }
738
+
739
+ getChannelValue(channel: ColorChannel): number {
740
+ switch (channel) {
741
+ case 'hue':
742
+ return this.hue;
743
+ case 'saturation':
744
+ return this.saturation;
745
+ case 'brightness':
746
+ return this.brightness;
747
+ case 'alpha':
748
+ return this.alpha;
749
+ // Cross-color-space channels
750
+ case 'red':
751
+ case 'green':
752
+ case 'blue':
753
+ return this.toFormat('rgb').getChannelValue(channel);
754
+ case 'lightness':
755
+ return this.toFormat('hsl').getChannelValue(channel);
756
+ default:
757
+ throw new Error(`Invalid channel: ${channel}`);
758
+ }
759
+ }
760
+
761
+ withChannelValue(channel: ColorChannel, value: number): Color {
762
+ switch (channel) {
763
+ case 'hue':
764
+ return new HSBColorImpl(value, this.saturation, this.brightness, this.alpha);
765
+ case 'saturation':
766
+ return new HSBColorImpl(this.hue, value, this.brightness, this.alpha);
767
+ case 'brightness':
768
+ return new HSBColorImpl(this.hue, this.saturation, value, this.alpha);
769
+ case 'alpha':
770
+ return new HSBColorImpl(this.hue, this.saturation, this.brightness, value);
771
+ // Cross-color-space channels
772
+ case 'red':
773
+ case 'green':
774
+ case 'blue':
775
+ return this.toFormat('rgb').withChannelValue(channel, value).toFormat('hsb');
776
+ case 'lightness':
777
+ return this.toFormat('hsl').withChannelValue(channel, value).toFormat('hsb');
778
+ default:
779
+ throw new Error(`Invalid channel: ${channel}`);
780
+ }
781
+ }
782
+
783
+ getChannelRange(channel: ColorChannel): ColorChannelRange {
784
+ switch (channel) {
785
+ case 'hue':
786
+ return HUE_CHANNEL_RANGE;
787
+ case 'saturation':
788
+ case 'brightness':
789
+ case 'lightness':
790
+ return PERCENT_CHANNEL_RANGE;
791
+ case 'alpha':
792
+ return ALPHA_CHANNEL_RANGE;
793
+ case 'red':
794
+ case 'green':
795
+ case 'blue':
796
+ return RGB_CHANNEL_RANGE;
797
+ default:
798
+ throw new Error(`Invalid channel: ${channel}`);
799
+ }
800
+ }
801
+
802
+ getChannelName(channel: ColorChannel, _locale: string): string {
803
+ return CHANNEL_NAMES[channel] || channel;
804
+ }
805
+
806
+ getChannelFormatOptions(channel: ColorChannel): Intl.NumberFormatOptions {
807
+ if (channel === 'alpha') {
808
+ return { style: 'percent' };
809
+ }
810
+ if (channel === 'hue') {
811
+ return { maximumFractionDigits: 0 };
812
+ }
813
+ return { style: 'percent', maximumFractionDigits: 0 };
814
+ }
815
+
816
+ formatChannelValue(channel: ColorChannel, locale: string): string {
817
+ const value = this.getChannelValue(channel);
818
+ const options = this.getChannelFormatOptions(channel);
819
+ if (channel === 'saturation' || channel === 'brightness') {
820
+ return new Intl.NumberFormat(locale, options).format(value / 100);
821
+ }
822
+ return new Intl.NumberFormat(locale, options).format(value);
823
+ }
824
+
825
+ getColorSpace(): ColorSpace {
826
+ return 'hsb';
827
+ }
828
+
829
+ getColorSpaceAxes(xyChannels?: { xChannel?: ColorChannel; yChannel?: ColorChannel }): ColorAxes {
830
+ const xChannel = xyChannels?.xChannel ?? 'saturation';
831
+ const yChannel = xyChannels?.yChannel ?? 'brightness';
832
+ const channels: ColorChannel[] = ['hue', 'saturation', 'brightness'];
833
+ const zChannel = channels.find((c) => c !== xChannel && c !== yChannel) ?? 'hue';
834
+ return { xChannel, yChannel, zChannel };
835
+ }
836
+
837
+ getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
838
+ return ['hue', 'saturation', 'brightness'];
839
+ }
840
+
841
+ getColorName(_locale: string): string {
842
+ const { r, g, b } = hsbToRgb(this.hue, this.saturation, this.brightness);
843
+ return getColorNameFromRgb(r, g, b, this.alpha);
844
+ }
845
+
846
+ getHueName(_locale: string): string {
847
+ return getHueNameFromAngle(this.hue);
848
+ }
849
+ }
850
+
851
+ /**
852
+ * Parse a color string into a Color object.
853
+ */
854
+ export function parseColor(value: string): Color {
855
+ const trimmed = value.trim().toLowerCase();
856
+
857
+ // Hex format
858
+ if (trimmed.startsWith('#')) {
859
+ const hex = trimmed.slice(1);
860
+ if (hex.length === 3) {
861
+ const r = parseInt(hex[0] + hex[0], 16);
862
+ const g = parseInt(hex[1] + hex[1], 16);
863
+ const b = parseInt(hex[2] + hex[2], 16);
864
+ return new RGBColorImpl(r, g, b);
865
+ }
866
+ if (hex.length === 4) {
867
+ const r = parseInt(hex[0] + hex[0], 16);
868
+ const g = parseInt(hex[1] + hex[1], 16);
869
+ const b = parseInt(hex[2] + hex[2], 16);
870
+ const a = parseInt(hex[3] + hex[3], 16) / 255;
871
+ return new RGBColorImpl(r, g, b, a);
872
+ }
873
+ if (hex.length === 6) {
874
+ const r = parseInt(hex.slice(0, 2), 16);
875
+ const g = parseInt(hex.slice(2, 4), 16);
876
+ const b = parseInt(hex.slice(4, 6), 16);
877
+ return new RGBColorImpl(r, g, b);
878
+ }
879
+ if (hex.length === 8) {
880
+ const r = parseInt(hex.slice(0, 2), 16);
881
+ const g = parseInt(hex.slice(2, 4), 16);
882
+ const b = parseInt(hex.slice(4, 6), 16);
883
+ const a = parseInt(hex.slice(6, 8), 16) / 255;
884
+ return new RGBColorImpl(r, g, b, a);
885
+ }
886
+ throw new Error(`Invalid hex color: ${value}`);
887
+ }
888
+
889
+ // RGB/RGBA format
890
+ const rgbMatch = trimmed.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/);
891
+ if (rgbMatch) {
892
+ const r = parseInt(rgbMatch[1], 10);
893
+ const g = parseInt(rgbMatch[2], 10);
894
+ const b = parseInt(rgbMatch[3], 10);
895
+ const a = rgbMatch[4] !== undefined ? parseFloat(rgbMatch[4]) : 1;
896
+ return new RGBColorImpl(r, g, b, a);
897
+ }
898
+
899
+ // HSL/HSLA format
900
+ const hslMatch = trimmed.match(/^hsla?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)$/);
901
+ if (hslMatch) {
902
+ const h = parseInt(hslMatch[1], 10);
903
+ const s = parseInt(hslMatch[2], 10);
904
+ const l = parseInt(hslMatch[3], 10);
905
+ const a = hslMatch[4] !== undefined ? parseFloat(hslMatch[4]) : 1;
906
+ return new HSLColorImpl(h, s, l, a);
907
+ }
908
+
909
+ // HSB/HSBA format
910
+ const hsbMatch = trimmed.match(/^hsba?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)$/);
911
+ if (hsbMatch) {
912
+ const h = parseInt(hsbMatch[1], 10);
913
+ const s = parseInt(hsbMatch[2], 10);
914
+ const b = parseInt(hsbMatch[3], 10);
915
+ const a = hsbMatch[4] !== undefined ? parseFloat(hsbMatch[4]) : 1;
916
+ return new HSBColorImpl(h, s, b, a);
917
+ }
918
+
919
+ throw new Error(`Invalid color format: ${value}`);
920
+ }
921
+
922
+ /**
923
+ * Create an RGB color.
924
+ */
925
+ export function createRGBColor(red: number, green: number, blue: number, alpha: number = 1): Color {
926
+ return new RGBColorImpl(red, green, blue, alpha);
927
+ }
928
+
929
+ /**
930
+ * Create an HSL color.
931
+ */
932
+ export function createHSLColor(hue: number, saturation: number, lightness: number, alpha: number = 1): Color {
933
+ return new HSLColorImpl(hue, saturation, lightness, alpha);
934
+ }
935
+
936
+ /**
937
+ * Create an HSB color.
938
+ */
939
+ export function createHSBColor(hue: number, saturation: number, brightness: number, alpha: number = 1): Color {
940
+ return new HSBColorImpl(hue, saturation, brightness, alpha);
941
+ }
942
+
943
+ /**
944
+ * Normalize a color value (string or Color) to a Color object.
945
+ */
946
+ export function normalizeColor(value: string | Color): Color {
947
+ if (typeof value === 'string') {
948
+ return parseColor(value);
949
+ }
950
+ return value;
951
+ }