@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
@@ -15,7 +15,7 @@ import type {
15
15
  RGBColor,
16
16
  HSLColor,
17
17
  HSBColor,
18
- } from './types';
18
+ } from "./types";
19
19
 
20
20
  // Channel ranges
21
21
  const RGB_CHANNEL_RANGE: ColorChannelRange = {
@@ -48,28 +48,33 @@ const PERCENT_CHANNEL_RANGE: ColorChannelRange = {
48
48
 
49
49
  // Channel names (English only for now)
50
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',
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
59
  };
60
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' },
61
+ // React Stately uses OKLCH for color names so that lightness is perceptually
62
+ // consistent across hues.
63
+ const ORANGE_LIGHTNESS_THRESHOLD = 0.68;
64
+ const YELLOW_GREEN_LIGHTNESS_THRESHOLD = 0.85;
65
+ const MAX_DARK_LIGHTNESS = 0.55;
66
+ const GRAY_THRESHOLD = 0.001;
67
+ const OKLCH_HUES: Array<[number, string]> = [
68
+ [0, "pink"],
69
+ [15, "red"],
70
+ [48, "orange"],
71
+ [94, "yellow"],
72
+ [135, "green"],
73
+ [175, "cyan"],
74
+ [264, "blue"],
75
+ [284, "purple"],
76
+ [320, "magenta"],
77
+ [349, "pink"],
73
78
  ];
74
79
 
75
80
  /**
@@ -252,79 +257,157 @@ function hsbToRgb(h: number, s: number, b: number): { r: number; g: number; b: n
252
257
  };
253
258
  }
254
259
 
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;
260
+ function getOklchHueName(l: number, c: number, h: number): [string, number] {
261
+ if (c < GRAY_THRESHOLD) {
262
+ return ["gray", l];
263
+ }
264
+
265
+ for (let i = 0; i < OKLCH_HUES.length; i += 1) {
266
+ let [hue, hueName] = OKLCH_HUES[i];
267
+ const [nextHue, nextHueName] = OKLCH_HUES[i + 1] || [360, "pink"];
268
+ if (h >= hue && h < nextHue) {
269
+ if (hueName === "orange") {
270
+ if (l < ORANGE_LIGHTNESS_THRESHOLD) {
271
+ hueName = "brown";
272
+ } else {
273
+ l = l - ORANGE_LIGHTNESS_THRESHOLD + MAX_DARK_LIGHTNESS;
274
+ }
275
+ }
276
+
277
+ if (h > hue + (nextHue - hue) / 2 && hueName !== nextHueName) {
278
+ hueName = `${hueName} ${nextHueName}`;
279
+ } else if (hueName === "yellow" && l < YELLOW_GREEN_LIGHTNESS_THRESHOLD) {
280
+ hueName = "yellow green";
281
+ }
282
+
283
+ return [hueName.toLocaleLowerCase(), l];
262
284
  }
263
285
  }
264
- return 'pink';
286
+
287
+ return ["pink", l];
265
288
  }
266
289
 
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);
290
+ function getColorNameFromColor(color: Color): string {
291
+ let [l, c, h] = toOKLCH(color);
292
+ const alpha = color.getChannelValue("alpha");
273
293
 
274
- // Handle edge cases
275
- if (l >= 100) {
276
- return alpha < 1 ? `white ${Math.round(alpha * 100)}% transparent` : 'white';
294
+ if (l > 0.999) {
295
+ return alpha < 1 ? `white ${Math.round((1 - alpha) * 100)}% transparent` : "white";
277
296
  }
278
- if (l <= 0) {
279
- return alpha < 1 ? `black ${Math.round(alpha * 100)}% transparent` : 'black';
297
+
298
+ if (l < 0.001) {
299
+ return alpha < 1 ? `black ${Math.round((1 - alpha) * 100)}% transparent` : "black";
280
300
  }
281
301
 
282
- // Build color name
283
- const parts: string[] = [];
302
+ let hue: string;
303
+ [hue, l] = getOklchHueName(l, c, h);
284
304
 
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');
305
+ let chroma = "";
306
+ if (c <= 0.1 && c >= GRAY_THRESHOLD) {
307
+ chroma = l >= 0.7 ? "pale" : "grayish";
308
+ } else if (c >= 0.15) {
309
+ chroma = "vibrant";
294
310
  }
295
311
 
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');
312
+ let lightness = "";
313
+ if (l < 0.3) {
314
+ lightness = "very dark";
315
+ } else if (l < MAX_DARK_LIGHTNESS) {
316
+ lightness = "dark";
317
+ } else if (l < 0.7) {
318
+ lightness = "";
319
+ } else if (l < 0.85) {
320
+ lightness = "light";
321
+ } else {
322
+ lightness = "very light";
303
323
  }
304
324
 
305
- // Hue name (skip if gray)
306
- if (s >= 10) {
307
- let hueName = getHueNameFromAngle(h);
325
+ const name = [lightness, chroma, hue].filter(Boolean).join(" ").replace(/\s+/g, " ").trim();
326
+ if (alpha < 1) {
327
+ return `${Math.round((1 - alpha) * 100)}% transparent ${name}`.trim();
328
+ }
308
329
 
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
- }
330
+ return name;
331
+ }
316
332
 
317
- parts.push(hueName);
318
- }
333
+ function getHueNameFromColor(color: Color): string {
334
+ const [l, c, h] = toOKLCH(color);
335
+ const [name] = getOklchHueName(l, c, h);
336
+ return name;
337
+ }
319
338
 
320
- let name = parts.join(' ');
339
+ function toOKLCH(color: Color): [number, number, number] {
340
+ const rgb = color.toFormat("rgb");
341
+ let red = rgb.getChannelValue("red") / 255;
342
+ let green = rgb.getChannelValue("green") / 255;
343
+ let blue = rgb.getChannelValue("blue") / 255;
344
+ [red, green, blue] = linSRGB(red, green, blue);
345
+ const [x, y, z] = linSRGBToXYZ(red, green, blue);
346
+ const [l, a, b] = xyzToOKLab(x, y, z);
347
+ return okLabToOKLCH(l, a, b);
348
+ }
321
349
 
322
- // Add transparency
323
- if (alpha < 1) {
324
- name += ` ${Math.round(alpha * 100)}% transparent`;
350
+ function okLabToOKLCH(l: number, a: number, b: number): [number, number, number] {
351
+ const hue = (Math.atan2(b, a) * 180) / Math.PI;
352
+ return [l, Math.sqrt(a ** 2 + b ** 2), hue >= 0 ? hue : hue + 360];
353
+ }
354
+
355
+ function linSRGB(r: number, g: number, b: number): [number, number, number] {
356
+ return [linSRGBComponent(r), linSRGBComponent(g), linSRGBComponent(b)];
357
+ }
358
+
359
+ function linSRGBComponent(value: number) {
360
+ const sign = value < 0 ? -1 : 1;
361
+ const abs = Math.abs(value);
362
+
363
+ if (abs <= 0.04045) {
364
+ return value / 12.92;
325
365
  }
326
366
 
327
- return name || 'color';
367
+ return sign * Math.pow((abs + 0.055) / 1.055, 2.4);
368
+ }
369
+
370
+ function linSRGBToXYZ(r: number, g: number, b: number): [number, number, number] {
371
+ const matrix = [
372
+ 506752 / 1228815,
373
+ 87881 / 245763,
374
+ 12673 / 70218,
375
+ 87098 / 409605,
376
+ 175762 / 245763,
377
+ 12673 / 175545,
378
+ 7918 / 409605,
379
+ 87881 / 737289,
380
+ 1001167 / 1053270,
381
+ ];
382
+ return multiplyMatrix(matrix, r, g, b);
383
+ }
384
+
385
+ function xyzToOKLab(x: number, y: number, z: number): [number, number, number] {
386
+ const xyzToLMS = [
387
+ 0.819022437996703, 0.3619062600528904, -0.1288737815209879, 0.0329836539323885,
388
+ 0.9292868615863434, 0.0361446663506424, 0.0481771893596242, 0.2642395317527308,
389
+ 0.6335478284694309,
390
+ ];
391
+ const lmsToOKLab = [
392
+ 0.210454268309314, 0.7936177747023054, -0.0040720430116193, 1.9779985324311684,
393
+ -2.4285922420485799, 0.450593709617411, 0.0259040424655478, 0.7827717124575296,
394
+ -0.8086757549230774,
395
+ ];
396
+
397
+ const [a, b, c] = multiplyMatrix(xyzToLMS, x, y, z);
398
+ return multiplyMatrix(lmsToOKLab, Math.cbrt(a), Math.cbrt(b), Math.cbrt(c));
399
+ }
400
+
401
+ function multiplyMatrix(
402
+ matrix: number[],
403
+ x: number,
404
+ y: number,
405
+ z: number,
406
+ ): [number, number, number] {
407
+ const a = matrix[0] * x + matrix[1] * y + matrix[2] * z;
408
+ const b = matrix[3] * x + matrix[4] * y + matrix[5] * z;
409
+ const c = matrix[6] * x + matrix[7] * y + matrix[8] * z;
410
+ return [a, b, c];
328
411
  }
329
412
 
330
413
  /**
@@ -345,18 +428,18 @@ class RGBColorImpl implements Color {
345
428
 
346
429
  toFormat(format: ColorFormat): Color {
347
430
  switch (format) {
348
- case 'hex':
349
- case 'hexa':
350
- case 'rgb':
351
- case 'rgba':
431
+ case "hex":
432
+ case "hexa":
433
+ case "rgb":
434
+ case "rgba":
352
435
  return this.clone();
353
- case 'hsl':
354
- case 'hsla': {
436
+ case "hsl":
437
+ case "hsla": {
355
438
  const { h, s, l } = rgbToHsl(this.red, this.green, this.blue);
356
439
  return new HSLColorImpl(h, s, l, this.alpha);
357
440
  }
358
- case 'hsb':
359
- case 'hsba': {
441
+ case "hsb":
442
+ case "hsba": {
360
443
  const { h, s, b } = rgbToHsb(this.red, this.green, this.blue);
361
444
  return new HSBColorImpl(h, s, b, this.alpha);
362
445
  }
@@ -365,18 +448,22 @@ class RGBColorImpl implements Color {
365
448
  }
366
449
  }
367
450
 
368
- toString(format?: ColorFormat | 'css'): string {
369
- const f = format ?? 'css';
451
+ toString(format?: ColorFormat | "css"): string {
452
+ const f = format ?? "css";
370
453
 
371
454
  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':
455
+ case "hex":
456
+ return `#${this.red.toString(16).padStart(2, "0")}${this.green.toString(16).padStart(2, "0")}${this.blue.toString(16).padStart(2, "0")}`;
457
+ case "hexa":
458
+ return `#${this.red.toString(16).padStart(2, "0")}${this.green.toString(16).padStart(2, "0")}${this.blue.toString(16).padStart(2, "0")}${Math.round(
459
+ this.alpha * 255,
460
+ )
461
+ .toString(16)
462
+ .padStart(2, "0")}`;
463
+ case "rgb":
377
464
  return `rgb(${this.red}, ${this.green}, ${this.blue})`;
378
- case 'rgba':
379
- case 'css':
465
+ case "rgba":
466
+ case "css":
380
467
  return this.alpha === 1
381
468
  ? `rgb(${this.red}, ${this.green}, ${this.blue})`
382
469
  : `rgba(${this.red}, ${this.green}, ${this.blue}, ${this.alpha})`;
@@ -395,21 +482,21 @@ class RGBColorImpl implements Color {
395
482
 
396
483
  getChannelValue(channel: ColorChannel): number {
397
484
  switch (channel) {
398
- case 'red':
485
+ case "red":
399
486
  return this.red;
400
- case 'green':
487
+ case "green":
401
488
  return this.green;
402
- case 'blue':
489
+ case "blue":
403
490
  return this.blue;
404
- case 'alpha':
491
+ case "alpha":
405
492
  return this.alpha;
406
493
  // 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);
494
+ case "hue":
495
+ case "saturation":
496
+ case "brightness":
497
+ return this.toFormat("hsb").getChannelValue(channel);
498
+ case "lightness":
499
+ return this.toFormat("hsl").getChannelValue(channel);
413
500
  default:
414
501
  throw new Error(`Invalid channel: ${channel}`);
415
502
  }
@@ -417,21 +504,21 @@ class RGBColorImpl implements Color {
417
504
 
418
505
  withChannelValue(channel: ColorChannel, value: number): Color {
419
506
  switch (channel) {
420
- case 'red':
507
+ case "red":
421
508
  return new RGBColorImpl(value, this.green, this.blue, this.alpha);
422
- case 'green':
509
+ case "green":
423
510
  return new RGBColorImpl(this.red, value, this.blue, this.alpha);
424
- case 'blue':
511
+ case "blue":
425
512
  return new RGBColorImpl(this.red, this.green, value, this.alpha);
426
- case 'alpha':
513
+ case "alpha":
427
514
  return new RGBColorImpl(this.red, this.green, this.blue, value);
428
515
  // 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');
516
+ case "hue":
517
+ case "saturation":
518
+ case "brightness":
519
+ return this.toFormat("hsb").withChannelValue(channel, value).toFormat("rgb");
520
+ case "lightness":
521
+ return this.toFormat("hsl").withChannelValue(channel, value).toFormat("rgb");
435
522
  default:
436
523
  throw new Error(`Invalid channel: ${channel}`);
437
524
  }
@@ -439,17 +526,17 @@ class RGBColorImpl implements Color {
439
526
 
440
527
  getChannelRange(channel: ColorChannel): ColorChannelRange {
441
528
  switch (channel) {
442
- case 'red':
443
- case 'green':
444
- case 'blue':
529
+ case "red":
530
+ case "green":
531
+ case "blue":
445
532
  return RGB_CHANNEL_RANGE;
446
- case 'alpha':
533
+ case "alpha":
447
534
  return ALPHA_CHANNEL_RANGE;
448
- case 'hue':
535
+ case "hue":
449
536
  return HUE_CHANNEL_RANGE;
450
- case 'saturation':
451
- case 'brightness':
452
- case 'lightness':
537
+ case "saturation":
538
+ case "brightness":
539
+ case "lightness":
453
540
  return PERCENT_CHANNEL_RANGE;
454
541
  default:
455
542
  throw new Error(`Invalid channel: ${channel}`);
@@ -461,8 +548,8 @@ class RGBColorImpl implements Color {
461
548
  }
462
549
 
463
550
  getChannelFormatOptions(channel: ColorChannel): Intl.NumberFormatOptions {
464
- if (channel === 'alpha') {
465
- return { style: 'percent' };
551
+ if (channel === "alpha") {
552
+ return { style: "percent" };
466
553
  }
467
554
  return { maximumFractionDigits: 0 };
468
555
  }
@@ -474,28 +561,27 @@ class RGBColorImpl implements Color {
474
561
  }
475
562
 
476
563
  getColorSpace(): ColorSpace {
477
- return 'rgb';
564
+ return "rgb";
478
565
  }
479
566
 
480
567
  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';
568
+ const xChannel = xyChannels?.xChannel ?? "red";
569
+ const yChannel = xyChannels?.yChannel ?? "green";
570
+ const channels: ColorChannel[] = ["red", "green", "blue"];
571
+ const zChannel = channels.find((c) => c !== xChannel && c !== yChannel) ?? "blue";
485
572
  return { xChannel, yChannel, zChannel };
486
573
  }
487
574
 
488
575
  getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
489
- return ['red', 'green', 'blue'];
576
+ return ["red", "green", "blue"];
490
577
  }
491
578
 
492
- getColorName(locale: string): string {
493
- return getColorNameFromRgb(this.red, this.green, this.blue, this.alpha);
579
+ getColorName(_locale: string): string {
580
+ return getColorNameFromColor(this);
494
581
  }
495
582
 
496
583
  getHueName(_locale: string): string {
497
- const { h } = rgbToHsl(this.red, this.green, this.blue);
498
- return getHueNameFromAngle(h);
584
+ return getHueNameFromColor(this);
499
585
  }
500
586
  }
501
587
 
@@ -517,18 +603,18 @@ class HSLColorImpl implements Color {
517
603
 
518
604
  toFormat(format: ColorFormat): Color {
519
605
  switch (format) {
520
- case 'hsl':
521
- case 'hsla':
606
+ case "hsl":
607
+ case "hsla":
522
608
  return this.clone();
523
- case 'hex':
524
- case 'hexa':
525
- case 'rgb':
526
- case 'rgba': {
609
+ case "hex":
610
+ case "hexa":
611
+ case "rgb":
612
+ case "rgba": {
527
613
  const { r, g, b } = hslToRgb(this.hue, this.saturation, this.lightness);
528
614
  return new RGBColorImpl(r, g, b, this.alpha);
529
615
  }
530
- case 'hsb':
531
- case 'hsba': {
616
+ case "hsb":
617
+ case "hsba": {
532
618
  const { r, g, b } = hslToRgb(this.hue, this.saturation, this.lightness);
533
619
  const hsb = rgbToHsb(r, g, b);
534
620
  return new HSBColorImpl(hsb.h, hsb.s, hsb.b, this.alpha);
@@ -538,14 +624,14 @@ class HSLColorImpl implements Color {
538
624
  }
539
625
  }
540
626
 
541
- toString(format?: ColorFormat | 'css'): string {
542
- const f = format ?? 'css';
627
+ toString(format?: ColorFormat | "css"): string {
628
+ const f = format ?? "css";
543
629
 
544
630
  switch (f) {
545
- case 'hsl':
631
+ case "hsl":
546
632
  return `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`;
547
- case 'hsla':
548
- case 'css':
633
+ case "hsla":
634
+ case "css":
549
635
  return this.alpha === 1
550
636
  ? `hsl(${this.hue}, ${this.saturation}%, ${this.lightness}%)`
551
637
  : `hsla(${this.hue}, ${this.saturation}%, ${this.lightness}%, ${this.alpha})`;
@@ -559,26 +645,26 @@ class HSLColorImpl implements Color {
559
645
  }
560
646
 
561
647
  toHexInt(): number {
562
- return this.toFormat('rgb').toHexInt();
648
+ return this.toFormat("rgb").toHexInt();
563
649
  }
564
650
 
565
651
  getChannelValue(channel: ColorChannel): number {
566
652
  switch (channel) {
567
- case 'hue':
653
+ case "hue":
568
654
  return this.hue;
569
- case 'saturation':
655
+ case "saturation":
570
656
  return this.saturation;
571
- case 'lightness':
657
+ case "lightness":
572
658
  return this.lightness;
573
- case 'alpha':
659
+ case "alpha":
574
660
  return this.alpha;
575
661
  // 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);
662
+ case "red":
663
+ case "green":
664
+ case "blue":
665
+ return this.toFormat("rgb").getChannelValue(channel);
666
+ case "brightness":
667
+ return this.toFormat("hsb").getChannelValue(channel);
582
668
  default:
583
669
  throw new Error(`Invalid channel: ${channel}`);
584
670
  }
@@ -586,21 +672,21 @@ class HSLColorImpl implements Color {
586
672
 
587
673
  withChannelValue(channel: ColorChannel, value: number): Color {
588
674
  switch (channel) {
589
- case 'hue':
675
+ case "hue":
590
676
  return new HSLColorImpl(value, this.saturation, this.lightness, this.alpha);
591
- case 'saturation':
677
+ case "saturation":
592
678
  return new HSLColorImpl(this.hue, value, this.lightness, this.alpha);
593
- case 'lightness':
679
+ case "lightness":
594
680
  return new HSLColorImpl(this.hue, this.saturation, value, this.alpha);
595
- case 'alpha':
681
+ case "alpha":
596
682
  return new HSLColorImpl(this.hue, this.saturation, this.lightness, value);
597
683
  // 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');
684
+ case "red":
685
+ case "green":
686
+ case "blue":
687
+ return this.toFormat("rgb").withChannelValue(channel, value).toFormat("hsl");
688
+ case "brightness":
689
+ return this.toFormat("hsb").withChannelValue(channel, value).toFormat("hsl");
604
690
  default:
605
691
  throw new Error(`Invalid channel: ${channel}`);
606
692
  }
@@ -608,17 +694,17 @@ class HSLColorImpl implements Color {
608
694
 
609
695
  getChannelRange(channel: ColorChannel): ColorChannelRange {
610
696
  switch (channel) {
611
- case 'hue':
697
+ case "hue":
612
698
  return HUE_CHANNEL_RANGE;
613
- case 'saturation':
614
- case 'lightness':
615
- case 'brightness':
699
+ case "saturation":
700
+ case "lightness":
701
+ case "brightness":
616
702
  return PERCENT_CHANNEL_RANGE;
617
- case 'alpha':
703
+ case "alpha":
618
704
  return ALPHA_CHANNEL_RANGE;
619
- case 'red':
620
- case 'green':
621
- case 'blue':
705
+ case "red":
706
+ case "green":
707
+ case "blue":
622
708
  return RGB_CHANNEL_RANGE;
623
709
  default:
624
710
  throw new Error(`Invalid channel: ${channel}`);
@@ -630,47 +716,46 @@ class HSLColorImpl implements Color {
630
716
  }
631
717
 
632
718
  getChannelFormatOptions(channel: ColorChannel): Intl.NumberFormatOptions {
633
- if (channel === 'alpha') {
634
- return { style: 'percent' };
719
+ if (channel === "alpha") {
720
+ return { style: "percent" };
635
721
  }
636
- if (channel === 'hue') {
637
- return { maximumFractionDigits: 0 };
722
+ if (channel === "hue") {
723
+ return { style: "unit", unit: "degree", unitDisplay: "narrow", maximumFractionDigits: 0 };
638
724
  }
639
- return { style: 'percent', maximumFractionDigits: 0 };
725
+ return { style: "percent", maximumFractionDigits: 0 };
640
726
  }
641
727
 
642
728
  formatChannelValue(channel: ColorChannel, locale: string): string {
643
729
  const value = this.getChannelValue(channel);
644
730
  const options = this.getChannelFormatOptions(channel);
645
- if (channel === 'saturation' || channel === 'lightness') {
731
+ if (channel === "saturation" || channel === "lightness") {
646
732
  return new Intl.NumberFormat(locale, options).format(value / 100);
647
733
  }
648
734
  return new Intl.NumberFormat(locale, options).format(value);
649
735
  }
650
736
 
651
737
  getColorSpace(): ColorSpace {
652
- return 'hsl';
738
+ return "hsl";
653
739
  }
654
740
 
655
741
  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';
742
+ const xChannel = xyChannels?.xChannel ?? "saturation";
743
+ const yChannel = xyChannels?.yChannel ?? "lightness";
744
+ const channels: ColorChannel[] = ["hue", "saturation", "lightness"];
745
+ const zChannel = channels.find((c) => c !== xChannel && c !== yChannel) ?? "hue";
660
746
  return { xChannel, yChannel, zChannel };
661
747
  }
662
748
 
663
749
  getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
664
- return ['hue', 'saturation', 'lightness'];
750
+ return ["hue", "saturation", "lightness"];
665
751
  }
666
752
 
667
753
  getColorName(_locale: string): string {
668
- const { r, g, b } = hslToRgb(this.hue, this.saturation, this.lightness);
669
- return getColorNameFromRgb(r, g, b, this.alpha);
754
+ return getColorNameFromColor(this);
670
755
  }
671
756
 
672
757
  getHueName(_locale: string): string {
673
- return getHueNameFromAngle(this.hue);
758
+ return getHueNameFromColor(this);
674
759
  }
675
760
  }
676
761
 
@@ -692,18 +777,18 @@ class HSBColorImpl implements Color {
692
777
 
693
778
  toFormat(format: ColorFormat): Color {
694
779
  switch (format) {
695
- case 'hsb':
696
- case 'hsba':
780
+ case "hsb":
781
+ case "hsba":
697
782
  return this.clone();
698
- case 'hex':
699
- case 'hexa':
700
- case 'rgb':
701
- case 'rgba': {
783
+ case "hex":
784
+ case "hexa":
785
+ case "rgb":
786
+ case "rgba": {
702
787
  const { r, g, b } = hsbToRgb(this.hue, this.saturation, this.brightness);
703
788
  return new RGBColorImpl(r, g, b, this.alpha);
704
789
  }
705
- case 'hsl':
706
- case 'hsla': {
790
+ case "hsl":
791
+ case "hsla": {
707
792
  const { r, g, b } = hsbToRgb(this.hue, this.saturation, this.brightness);
708
793
  const hsl = rgbToHsl(r, g, b);
709
794
  return new HSLColorImpl(hsl.h, hsl.s, hsl.l, this.alpha);
@@ -713,16 +798,16 @@ class HSBColorImpl implements Color {
713
798
  }
714
799
  }
715
800
 
716
- toString(format?: ColorFormat | 'css'): string {
717
- const f = format ?? 'css';
801
+ toString(format?: ColorFormat | "css"): string {
802
+ const f = format ?? "css";
718
803
 
719
804
  switch (f) {
720
- case 'hsb':
805
+ case "hsb":
721
806
  return `hsb(${this.hue}, ${this.saturation}%, ${this.brightness}%)`;
722
- case 'hsba':
723
- case 'css':
807
+ case "hsba":
808
+ case "css":
724
809
  // HSB is not a standard CSS format, convert to RGB
725
- return this.toFormat('rgba').toString('css');
810
+ return this.toFormat("rgba").toString("css");
726
811
  default:
727
812
  return this.toFormat(f as ColorFormat).toString(f);
728
813
  }
@@ -733,26 +818,26 @@ class HSBColorImpl implements Color {
733
818
  }
734
819
 
735
820
  toHexInt(): number {
736
- return this.toFormat('rgb').toHexInt();
821
+ return this.toFormat("rgb").toHexInt();
737
822
  }
738
823
 
739
824
  getChannelValue(channel: ColorChannel): number {
740
825
  switch (channel) {
741
- case 'hue':
826
+ case "hue":
742
827
  return this.hue;
743
- case 'saturation':
828
+ case "saturation":
744
829
  return this.saturation;
745
- case 'brightness':
830
+ case "brightness":
746
831
  return this.brightness;
747
- case 'alpha':
832
+ case "alpha":
748
833
  return this.alpha;
749
834
  // 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);
835
+ case "red":
836
+ case "green":
837
+ case "blue":
838
+ return this.toFormat("rgb").getChannelValue(channel);
839
+ case "lightness":
840
+ return this.toFormat("hsl").getChannelValue(channel);
756
841
  default:
757
842
  throw new Error(`Invalid channel: ${channel}`);
758
843
  }
@@ -760,21 +845,21 @@ class HSBColorImpl implements Color {
760
845
 
761
846
  withChannelValue(channel: ColorChannel, value: number): Color {
762
847
  switch (channel) {
763
- case 'hue':
848
+ case "hue":
764
849
  return new HSBColorImpl(value, this.saturation, this.brightness, this.alpha);
765
- case 'saturation':
850
+ case "saturation":
766
851
  return new HSBColorImpl(this.hue, value, this.brightness, this.alpha);
767
- case 'brightness':
852
+ case "brightness":
768
853
  return new HSBColorImpl(this.hue, this.saturation, value, this.alpha);
769
- case 'alpha':
854
+ case "alpha":
770
855
  return new HSBColorImpl(this.hue, this.saturation, this.brightness, value);
771
856
  // 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');
857
+ case "red":
858
+ case "green":
859
+ case "blue":
860
+ return this.toFormat("rgb").withChannelValue(channel, value).toFormat("hsb");
861
+ case "lightness":
862
+ return this.toFormat("hsl").withChannelValue(channel, value).toFormat("hsb");
778
863
  default:
779
864
  throw new Error(`Invalid channel: ${channel}`);
780
865
  }
@@ -782,17 +867,17 @@ class HSBColorImpl implements Color {
782
867
 
783
868
  getChannelRange(channel: ColorChannel): ColorChannelRange {
784
869
  switch (channel) {
785
- case 'hue':
870
+ case "hue":
786
871
  return HUE_CHANNEL_RANGE;
787
- case 'saturation':
788
- case 'brightness':
789
- case 'lightness':
872
+ case "saturation":
873
+ case "brightness":
874
+ case "lightness":
790
875
  return PERCENT_CHANNEL_RANGE;
791
- case 'alpha':
876
+ case "alpha":
792
877
  return ALPHA_CHANNEL_RANGE;
793
- case 'red':
794
- case 'green':
795
- case 'blue':
878
+ case "red":
879
+ case "green":
880
+ case "blue":
796
881
  return RGB_CHANNEL_RANGE;
797
882
  default:
798
883
  throw new Error(`Invalid channel: ${channel}`);
@@ -804,47 +889,46 @@ class HSBColorImpl implements Color {
804
889
  }
805
890
 
806
891
  getChannelFormatOptions(channel: ColorChannel): Intl.NumberFormatOptions {
807
- if (channel === 'alpha') {
808
- return { style: 'percent' };
892
+ if (channel === "alpha") {
893
+ return { style: "percent" };
809
894
  }
810
- if (channel === 'hue') {
811
- return { maximumFractionDigits: 0 };
895
+ if (channel === "hue") {
896
+ return { style: "unit", unit: "degree", unitDisplay: "narrow", maximumFractionDigits: 0 };
812
897
  }
813
- return { style: 'percent', maximumFractionDigits: 0 };
898
+ return { style: "percent", maximumFractionDigits: 0 };
814
899
  }
815
900
 
816
901
  formatChannelValue(channel: ColorChannel, locale: string): string {
817
902
  const value = this.getChannelValue(channel);
818
903
  const options = this.getChannelFormatOptions(channel);
819
- if (channel === 'saturation' || channel === 'brightness') {
904
+ if (channel === "saturation" || channel === "brightness") {
820
905
  return new Intl.NumberFormat(locale, options).format(value / 100);
821
906
  }
822
907
  return new Intl.NumberFormat(locale, options).format(value);
823
908
  }
824
909
 
825
910
  getColorSpace(): ColorSpace {
826
- return 'hsb';
911
+ return "hsb";
827
912
  }
828
913
 
829
914
  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';
915
+ const xChannel = xyChannels?.xChannel ?? "saturation";
916
+ const yChannel = xyChannels?.yChannel ?? "brightness";
917
+ const channels: ColorChannel[] = ["hue", "saturation", "brightness"];
918
+ const zChannel = channels.find((c) => c !== xChannel && c !== yChannel) ?? "hue";
834
919
  return { xChannel, yChannel, zChannel };
835
920
  }
836
921
 
837
922
  getColorChannels(): [ColorChannel, ColorChannel, ColorChannel] {
838
- return ['hue', 'saturation', 'brightness'];
923
+ return ["hue", "saturation", "brightness"];
839
924
  }
840
925
 
841
926
  getColorName(_locale: string): string {
842
- const { r, g, b } = hsbToRgb(this.hue, this.saturation, this.brightness);
843
- return getColorNameFromRgb(r, g, b, this.alpha);
927
+ return getColorNameFromColor(this);
844
928
  }
845
929
 
846
930
  getHueName(_locale: string): string {
847
- return getHueNameFromAngle(this.hue);
931
+ return getHueNameFromColor(this);
848
932
  }
849
933
  }
850
934
 
@@ -855,8 +939,11 @@ export function parseColor(value: string): Color {
855
939
  const trimmed = value.trim().toLowerCase();
856
940
 
857
941
  // Hex format
858
- if (trimmed.startsWith('#')) {
942
+ if (trimmed.startsWith("#")) {
859
943
  const hex = trimmed.slice(1);
944
+ if (!/^[0-9a-f]+$/i.test(hex)) {
945
+ throw new Error(`Invalid hex color: ${value}`);
946
+ }
860
947
  if (hex.length === 3) {
861
948
  const r = parseInt(hex[0] + hex[0], 16);
862
949
  const g = parseInt(hex[1] + hex[1], 16);
@@ -887,7 +974,9 @@ export function parseColor(value: string): Color {
887
974
  }
888
975
 
889
976
  // RGB/RGBA format
890
- const rgbMatch = trimmed.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/);
977
+ const rgbMatch = trimmed.match(
978
+ /^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/,
979
+ );
891
980
  if (rgbMatch) {
892
981
  const r = parseInt(rgbMatch[1], 10);
893
982
  const g = parseInt(rgbMatch[2], 10);
@@ -897,7 +986,9 @@ export function parseColor(value: string): Color {
897
986
  }
898
987
 
899
988
  // HSL/HSLA format
900
- const hslMatch = trimmed.match(/^hsla?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)$/);
989
+ const hslMatch = trimmed.match(
990
+ /^hsla?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)$/,
991
+ );
901
992
  if (hslMatch) {
902
993
  const h = parseInt(hslMatch[1], 10);
903
994
  const s = parseInt(hslMatch[2], 10);
@@ -907,7 +998,9 @@ export function parseColor(value: string): Color {
907
998
  }
908
999
 
909
1000
  // HSB/HSBA format
910
- const hsbMatch = trimmed.match(/^hsba?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)$/);
1001
+ const hsbMatch = trimmed.match(
1002
+ /^hsba?\(\s*(\d+)\s*,\s*(\d+)%\s*,\s*(\d+)%\s*(?:,\s*([\d.]+))?\s*\)$/,
1003
+ );
911
1004
  if (hsbMatch) {
912
1005
  const h = parseInt(hsbMatch[1], 10);
913
1006
  const s = parseInt(hsbMatch[2], 10);
@@ -929,14 +1022,24 @@ export function createRGBColor(red: number, green: number, blue: number, alpha:
929
1022
  /**
930
1023
  * Create an HSL color.
931
1024
  */
932
- export function createHSLColor(hue: number, saturation: number, lightness: number, alpha: number = 1): Color {
1025
+ export function createHSLColor(
1026
+ hue: number,
1027
+ saturation: number,
1028
+ lightness: number,
1029
+ alpha: number = 1,
1030
+ ): Color {
933
1031
  return new HSLColorImpl(hue, saturation, lightness, alpha);
934
1032
  }
935
1033
 
936
1034
  /**
937
1035
  * Create an HSB color.
938
1036
  */
939
- export function createHSBColor(hue: number, saturation: number, brightness: number, alpha: number = 1): Color {
1037
+ export function createHSBColor(
1038
+ hue: number,
1039
+ saturation: number,
1040
+ brightness: number,
1041
+ alpha: number = 1,
1042
+ ): Color {
940
1043
  return new HSBColorImpl(hue, saturation, brightness, alpha);
941
1044
  }
942
1045
 
@@ -944,7 +1047,7 @@ export function createHSBColor(hue: number, saturation: number, brightness: numb
944
1047
  * Normalize a color value (string or Color) to a Color object.
945
1048
  */
946
1049
  export function normalizeColor(value: string | Color): Color {
947
- if (typeof value === 'string') {
1050
+ if (typeof value === "string") {
948
1051
  return parseColor(value);
949
1052
  }
950
1053
  return value;