omni-color 0.1.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 (293) hide show
  1. package/LICENSE +7 -0
  2. package/README.md +1042 -0
  3. package/dist/__test__/color-interop.test.d.ts +1 -0
  4. package/dist/__test__/color-interop.test.js +82 -0
  5. package/dist/__test__/comparison_culori.test.d.ts +1 -0
  6. package/dist/__test__/comparison_culori.test.js +169 -0
  7. package/dist/__test__/interop-chroma.test.d.ts +1 -0
  8. package/dist/__test__/interop-chroma.test.js +676 -0
  9. package/dist/__test__/interop-tinycolor.test.d.ts +1 -0
  10. package/dist/__test__/interop-tinycolor.test.js +499 -0
  11. package/dist/color/__test__/baseColor.test.d.ts +2 -0
  12. package/dist/color/__test__/baseColor.test.d.ts.map +1 -0
  13. package/dist/color/__test__/baseColor.test.js +34 -0
  14. package/dist/color/__test__/baseColor.test.js.map +1 -0
  15. package/dist/color/__test__/blank.d.ts +1 -0
  16. package/dist/color/__test__/blank.js +3 -0
  17. package/dist/color/__test__/color.test.d.ts +1 -0
  18. package/dist/color/__test__/color.test.js +1073 -0
  19. package/dist/color/__test__/colorSpaces.test.d.ts +1 -0
  20. package/dist/color/__test__/colorSpaces.test.js +69 -0
  21. package/dist/color/__test__/combinations.test.d.ts +1 -0
  22. package/dist/color/__test__/combinations.test.js +665 -0
  23. package/dist/color/__test__/conversions.test.d.ts +1 -0
  24. package/dist/color/__test__/conversions.test.js +719 -0
  25. package/dist/color/__test__/deltaE.test.d.ts +1 -0
  26. package/dist/color/__test__/deltaE.test.js +120 -0
  27. package/dist/color/__test__/formats.test.d.ts +1 -0
  28. package/dist/color/__test__/formats.test.js +514 -0
  29. package/dist/color/__test__/gradients.test.d.ts +1 -0
  30. package/dist/color/__test__/gradients.test.js +414 -0
  31. package/dist/color/__test__/harmonies.test.d.ts +1 -0
  32. package/dist/color/__test__/harmonies.test.js +676 -0
  33. package/dist/color/__test__/manipulations.test.d.ts +1 -0
  34. package/dist/color/__test__/manipulations.test.js +264 -0
  35. package/dist/color/__test__/names.test.d.ts +1 -0
  36. package/dist/color/__test__/names.test.js +67 -0
  37. package/dist/color/__test__/parse.test.d.ts +1 -0
  38. package/dist/color/__test__/parse.test.js +256 -0
  39. package/dist/color/__test__/random.test.d.ts +1 -0
  40. package/dist/color/__test__/random.test.js +262 -0
  41. package/dist/color/__test__/readability.test.d.ts +1 -0
  42. package/dist/color/__test__/readability.test.js +2596 -0
  43. package/dist/color/__test__/srgb.test.d.ts +1 -0
  44. package/dist/color/__test__/srgb.test.js +43 -0
  45. package/dist/color/__test__/swatch.test.d.ts +1 -0
  46. package/dist/color/__test__/swatch.test.js +267 -0
  47. package/dist/color/__test__/temperature.test.d.ts +1 -0
  48. package/dist/color/__test__/temperature.test.js +223 -0
  49. package/dist/color/__test__/utils.test.d.ts +1 -0
  50. package/dist/color/__test__/utils.test.js +409 -0
  51. package/dist/color/__test__/validations.test.d.ts +1 -0
  52. package/dist/color/__test__/validations.test.js +239 -0
  53. package/dist/color/color.constants.d.ts +4 -0
  54. package/dist/color/color.constants.d.ts.map +1 -0
  55. package/dist/color/color.constants.js +152 -0
  56. package/dist/color/color.constants.js.map +1 -0
  57. package/dist/color/color.consts.d.ts +4 -0
  58. package/dist/color/color.consts.js +152 -0
  59. package/dist/color/color.d.ts +788 -0
  60. package/dist/color/color.d.ts.map +1 -0
  61. package/dist/color/color.helpers.d.ts +6 -0
  62. package/dist/color/color.helpers.js +4 -0
  63. package/dist/color/color.js +919 -0
  64. package/dist/color/color.js.map +1 -0
  65. package/dist/color/colorSpaces.d.ts +15 -0
  66. package/dist/color/colorSpaces.js +127 -0
  67. package/dist/color/combinations.d.ts +45 -0
  68. package/dist/color/combinations.js +562 -0
  69. package/dist/color/conversions.d.ts +16 -0
  70. package/dist/color/conversions.d.ts.map +1 -0
  71. package/dist/color/conversions.js +705 -0
  72. package/dist/color/conversions.js.map +1 -0
  73. package/dist/color/deltaE.d.ts +59 -0
  74. package/dist/color/deltaE.js +110 -0
  75. package/dist/color/formats.d.ts +128 -0
  76. package/dist/color/formats.d.ts.map +1 -0
  77. package/dist/color/formats.js +153 -0
  78. package/dist/color/formats.js.map +1 -0
  79. package/dist/color/gradients.d.ts +49 -0
  80. package/dist/color/gradients.js +672 -0
  81. package/dist/color/harmonies.d.ts +18 -0
  82. package/dist/color/harmonies.js +128 -0
  83. package/dist/color/manipulations.d.ts +29 -0
  84. package/dist/color/manipulations.js +106 -0
  85. package/dist/color/names.d.ts +38 -0
  86. package/dist/color/names.js +108 -0
  87. package/dist/color/parse.d.ts +2 -0
  88. package/dist/color/parse.js +437 -0
  89. package/dist/color/random.d.ts +30 -0
  90. package/dist/color/random.js +65 -0
  91. package/dist/color/readability.d.ts +53 -0
  92. package/dist/color/readability.js +284 -0
  93. package/dist/color/srgb.d.ts +8 -0
  94. package/dist/color/srgb.js +33 -0
  95. package/dist/color/swatch.d.ts +49 -0
  96. package/dist/color/swatch.js +118 -0
  97. package/dist/color/temperature.d.ts +26 -0
  98. package/dist/color/temperature.js +141 -0
  99. package/dist/color/utils.d.ts +49 -0
  100. package/dist/color/utils.d.ts.map +1 -0
  101. package/dist/color/utils.js +104 -0
  102. package/dist/color/utils.js.map +1 -0
  103. package/dist/color/validations.d.ts +2 -0
  104. package/dist/color/validations.d.ts.map +1 -0
  105. package/dist/color/validations.js +178 -0
  106. package/dist/color/validations.js.map +1 -0
  107. package/dist/demo/src/AppFooter.d.ts +1 -0
  108. package/dist/demo/src/AppFooter.js +4 -0
  109. package/dist/demo/src/AppHeader.d.ts +5 -0
  110. package/dist/demo/src/AppHeader.js +6 -0
  111. package/dist/demo/src/components/Card.d.ts +9 -0
  112. package/dist/demo/src/components/Card.js +9 -0
  113. package/dist/demo/src/components/Chip.d.ts +21 -0
  114. package/dist/demo/src/components/Chip.js +23 -0
  115. package/dist/demo/src/components/ColorBox.d.ts +16 -0
  116. package/dist/demo/src/components/ColorBox.js +45 -0
  117. package/dist/demo/src/components/ColorInfoCard.d.ts +7 -0
  118. package/dist/demo/src/components/ColorInfoCard.js +50 -0
  119. package/dist/demo/src/components/ExpandableCodeSnippet.d.ts +5 -0
  120. package/dist/demo/src/components/ExpandableCodeSnippet.js +17 -0
  121. package/dist/demo/src/components/Icon.d.ts +11 -0
  122. package/dist/demo/src/components/Icon.js +50 -0
  123. package/dist/demo/src/components/Icon.types.d.ts +14 -0
  124. package/dist/demo/src/components/Icon.types.js +15 -0
  125. package/dist/demo/src/components/SectionContainer.d.ts +10 -0
  126. package/dist/demo/src/components/SectionContainer.js +12 -0
  127. package/dist/demo/src/components/VSpace.d.ts +5 -0
  128. package/dist/demo/src/components/VSpace.js +4 -0
  129. package/dist/demo/src/components/inputs/Checkbox.d.ts +7 -0
  130. package/dist/demo/src/components/inputs/Checkbox.js +4 -0
  131. package/dist/demo/src/components/inputs/InputGroup.d.ts +6 -0
  132. package/dist/demo/src/components/inputs/InputGroup.js +4 -0
  133. package/dist/demo/src/components/inputs/NumberInput.d.ts +10 -0
  134. package/dist/demo/src/components/inputs/NumberInput.js +8 -0
  135. package/dist/demo/src/components/inputs/Select.d.ts +12 -0
  136. package/dist/demo/src/components/inputs/Select.js +6 -0
  137. package/dist/demo/src/components/inputs/Slider.d.ts +10 -0
  138. package/dist/demo/src/components/inputs/Slider.js +4 -0
  139. package/dist/demo/src/components/utils.d.ts +17 -0
  140. package/dist/demo/src/components/utils.js +24 -0
  141. package/dist/demo/src/demo/ColorDemo.d.ts +1 -0
  142. package/dist/demo/src/demo/ColorDemo.js +45 -0
  143. package/dist/demo/src/demo/ColorHarmonyDemo.d.ts +6 -0
  144. package/dist/demo/src/demo/ColorHarmonyDemo.js +18 -0
  145. package/dist/demo/src/demo/ColorInfo.d.ts +6 -0
  146. package/dist/demo/src/demo/ColorInfo.js +14 -0
  147. package/dist/demo/src/demo/ColorInput.d.ts +7 -0
  148. package/dist/demo/src/demo/ColorInput.js +58 -0
  149. package/dist/demo/src/demo/ColorManipulationDemo.d.ts +6 -0
  150. package/dist/demo/src/demo/ColorManipulationDemo.js +63 -0
  151. package/dist/demo/src/demo/ColorSwatch.d.ts +13 -0
  152. package/dist/demo/src/demo/ColorSwatch.js +15 -0
  153. package/dist/demo/src/demo/ReadabilityDemo.d.ts +6 -0
  154. package/dist/demo/src/demo/ReadabilityDemo.js +24 -0
  155. package/dist/demo/src/demo/combinations/AverageColorsOptionInputs.d.ts +7 -0
  156. package/dist/demo/src/demo/combinations/AverageColorsOptionInputs.js +7 -0
  157. package/dist/demo/src/demo/combinations/BlendColorsOptionInputs.d.ts +7 -0
  158. package/dist/demo/src/demo/combinations/BlendColorsOptionInputs.js +13 -0
  159. package/dist/demo/src/demo/combinations/ColorCombinationDemo.d.ts +6 -0
  160. package/dist/demo/src/demo/combinations/ColorCombinationDemo.js +78 -0
  161. package/dist/demo/src/demo/combinations/MixColorsOptionInputs.d.ts +7 -0
  162. package/dist/demo/src/demo/combinations/MixColorsOptionInputs.js +10 -0
  163. package/dist/demo/src/demo/combinations/colorCombinationDemo.consts.d.ts +4 -0
  164. package/dist/demo/src/demo/combinations/colorCombinationDemo.consts.js +12 -0
  165. package/dist/demo/src/demo/gradients/GradientOptionInputs.d.ts +10 -0
  166. package/dist/demo/src/demo/gradients/GradientOptionInputs.js +42 -0
  167. package/dist/demo/src/demo/gradients/GradientThroughCard.d.ts +6 -0
  168. package/dist/demo/src/demo/gradients/GradientThroughCard.js +50 -0
  169. package/dist/demo/src/demo/gradients/GradientToCard.d.ts +6 -0
  170. package/dist/demo/src/demo/gradients/GradientToCard.js +54 -0
  171. package/dist/demo/src/demo/gradients/GradientsDemo.d.ts +6 -0
  172. package/dist/demo/src/demo/gradients/GradientsDemo.js +6 -0
  173. package/dist/demo/src/demo/gradients/gradientOptions.consts.d.ts +3 -0
  174. package/dist/demo/src/demo/gradients/gradientOptions.consts.js +16 -0
  175. package/dist/demo/src/demo/palette/ColorPaletteDemo.d.ts +6 -0
  176. package/dist/demo/src/demo/palette/ColorPaletteDemo.js +26 -0
  177. package/dist/demo/src/demo/palette/PaletteGenerationOptions.d.ts +8 -0
  178. package/dist/demo/src/demo/palette/PaletteGenerationOptions.js +53 -0
  179. package/dist/demo/src/demo/palette/PaletteHarmonyOptions.d.ts +7 -0
  180. package/dist/demo/src/demo/palette/PaletteHarmonyOptions.js +5 -0
  181. package/dist/demo/src/main.d.ts +1 -0
  182. package/dist/demo/src/main.js +14 -0
  183. package/dist/demo/src/pages/DemoPage.d.ts +1 -0
  184. package/dist/demo/src/pages/DemoPage.js +28 -0
  185. package/dist/demo/src/pages/PlaygroundPage.d.ts +1 -0
  186. package/dist/demo/src/pages/PlaygroundPage.js +24 -0
  187. package/dist/demo/src/playground/Playground.d.ts +1 -0
  188. package/dist/demo/src/playground/Playground.js +42 -0
  189. package/dist/demo/src/playground/playgroundUtils.d.ts +16 -0
  190. package/dist/demo/src/playground/playgroundUtils.js +202 -0
  191. package/dist/demo/src/seo/PageHead.d.ts +9 -0
  192. package/dist/demo/src/seo/PageHead.js +76 -0
  193. package/dist/demo/src/seo/StructuredData.d.ts +6 -0
  194. package/dist/demo/src/seo/StructuredData.js +13 -0
  195. package/dist/demo/src/toast/ToastProvider.d.ts +2 -0
  196. package/dist/demo/src/toast/ToastProvider.js +62 -0
  197. package/dist/demo/src/toast/index.d.ts +2 -0
  198. package/dist/demo/src/toast/index.js +2 -0
  199. package/dist/demo/src/toast/toastBus.d.ts +13 -0
  200. package/dist/demo/src/toast/toastBus.js +27 -0
  201. package/dist/index.d.ts +1233 -0
  202. package/dist/index.d.ts.map +1 -0
  203. package/dist/index.js +5235 -0
  204. package/dist/index.js.map +1 -0
  205. package/dist/palette/__test__/palette.test.d.ts +1 -0
  206. package/dist/palette/__test__/palette.test.js +397 -0
  207. package/dist/palette/palette.d.ts +41 -0
  208. package/dist/palette/palette.js +126 -0
  209. package/dist/src/__test__/interop-chroma.test.d.ts +1 -0
  210. package/dist/src/__test__/interop-chroma.test.js +673 -0
  211. package/dist/src/__test__/interop-tinycolor.test.d.ts +1 -0
  212. package/dist/src/__test__/interop-tinycolor.test.js +499 -0
  213. package/dist/src/color/__test__/color.test.d.ts +1 -0
  214. package/dist/src/color/__test__/color.test.js +1071 -0
  215. package/dist/src/color/__test__/colorSpaces.test.d.ts +1 -0
  216. package/dist/src/color/__test__/colorSpaces.test.js +69 -0
  217. package/dist/src/color/__test__/combinations.test.d.ts +1 -0
  218. package/dist/src/color/__test__/combinations.test.js +665 -0
  219. package/dist/src/color/__test__/conversions.test.d.ts +1 -0
  220. package/dist/src/color/__test__/conversions.test.js +719 -0
  221. package/dist/src/color/__test__/deltaE.test.d.ts +1 -0
  222. package/dist/src/color/__test__/deltaE.test.js +120 -0
  223. package/dist/src/color/__test__/formats.test.d.ts +1 -0
  224. package/dist/src/color/__test__/formats.test.js +470 -0
  225. package/dist/src/color/__test__/gradients.test.d.ts +1 -0
  226. package/dist/src/color/__test__/gradients.test.js +414 -0
  227. package/dist/src/color/__test__/harmonies.test.d.ts +1 -0
  228. package/dist/src/color/__test__/harmonies.test.js +734 -0
  229. package/dist/src/color/__test__/manipulations.test.d.ts +1 -0
  230. package/dist/src/color/__test__/manipulations.test.js +264 -0
  231. package/dist/src/color/__test__/names.test.d.ts +1 -0
  232. package/dist/src/color/__test__/names.test.js +67 -0
  233. package/dist/src/color/__test__/parse.test.d.ts +1 -0
  234. package/dist/src/color/__test__/parse.test.js +251 -0
  235. package/dist/src/color/__test__/random.test.d.ts +1 -0
  236. package/dist/src/color/__test__/random.test.js +262 -0
  237. package/dist/src/color/__test__/readability.test.d.ts +1 -0
  238. package/dist/src/color/__test__/readability.test.js +2596 -0
  239. package/dist/src/color/__test__/swatch.test.d.ts +1 -0
  240. package/dist/src/color/__test__/swatch.test.js +267 -0
  241. package/dist/src/color/__test__/temperature.test.d.ts +1 -0
  242. package/dist/src/color/__test__/temperature.test.js +223 -0
  243. package/dist/src/color/__test__/utils.test.d.ts +1 -0
  244. package/dist/src/color/__test__/utils.test.js +409 -0
  245. package/dist/src/color/__test__/validations.test.d.ts +1 -0
  246. package/dist/src/color/__test__/validations.test.js +239 -0
  247. package/dist/src/color/color.consts.d.ts +4 -0
  248. package/dist/src/color/color.consts.js +152 -0
  249. package/dist/src/color/color.d.ts +775 -0
  250. package/dist/src/color/color.js +903 -0
  251. package/dist/src/color/colorSpaces.d.ts +15 -0
  252. package/dist/src/color/colorSpaces.js +127 -0
  253. package/dist/src/color/combinations.d.ts +45 -0
  254. package/dist/src/color/combinations.js +557 -0
  255. package/dist/src/color/conversions.d.ts +16 -0
  256. package/dist/src/color/conversions.js +705 -0
  257. package/dist/src/color/deltaE.d.ts +59 -0
  258. package/dist/src/color/deltaE.js +110 -0
  259. package/dist/src/color/formats.d.ts +126 -0
  260. package/dist/src/color/formats.js +150 -0
  261. package/dist/src/color/gradients.d.ts +49 -0
  262. package/dist/src/color/gradients.js +673 -0
  263. package/dist/src/color/harmonies.d.ts +18 -0
  264. package/dist/src/color/harmonies.js +129 -0
  265. package/dist/src/color/manipulations.d.ts +29 -0
  266. package/dist/src/color/manipulations.js +107 -0
  267. package/dist/src/color/names.d.ts +38 -0
  268. package/dist/src/color/names.js +108 -0
  269. package/dist/src/color/parse.d.ts +2 -0
  270. package/dist/src/color/parse.js +438 -0
  271. package/dist/src/color/random.d.ts +30 -0
  272. package/dist/src/color/random.js +65 -0
  273. package/dist/src/color/readability.d.ts +53 -0
  274. package/dist/src/color/readability.js +284 -0
  275. package/dist/src/color/swatch.d.ts +49 -0
  276. package/dist/src/color/swatch.js +117 -0
  277. package/dist/src/color/temperature.d.ts +26 -0
  278. package/dist/src/color/temperature.js +142 -0
  279. package/dist/src/color/utils.d.ts +57 -0
  280. package/dist/src/color/utils.js +142 -0
  281. package/dist/src/color/validations.d.ts +2 -0
  282. package/dist/src/color/validations.js +178 -0
  283. package/dist/src/index.d.ts +20 -0
  284. package/dist/src/index.js +2 -0
  285. package/dist/src/palette/__test__/palette.test.d.ts +1 -0
  286. package/dist/src/palette/__test__/palette.test.js +397 -0
  287. package/dist/src/palette/palette.d.ts +41 -0
  288. package/dist/src/palette/palette.js +127 -0
  289. package/dist/src/utils.d.ts +20 -0
  290. package/dist/src/utils.js +42 -0
  291. package/dist/utils.d.ts +20 -0
  292. package/dist/utils.js +42 -0
  293. package/package.json +96 -0
@@ -0,0 +1,414 @@
1
+ import { Color } from '../color';
2
+ import { createColorGradient } from '../gradients';
3
+ describe('createColorGradient', () => {
4
+ it('builds linear RGB gradients with evenly spaced stops', () => {
5
+ const gradient = createColorGradient([new Color('#ff0000'), new Color('#0000ff')], {
6
+ stops: 5,
7
+ space: 'RGB',
8
+ });
9
+ expect(gradient.map((color) => color.toHex())).toEqual([
10
+ '#ff0000',
11
+ '#bf0040',
12
+ '#800080',
13
+ '#4000bf',
14
+ '#0000ff',
15
+ ]);
16
+ });
17
+ it('defaults to 5 evenly spaced gradient stops', () => {
18
+ const gradient = createColorGradient([new Color('#000000'), new Color('#ffffff')]);
19
+ expect(gradient).toHaveLength(5);
20
+ expect(gradient[0].toHex()).toBe('#000000');
21
+ expect(gradient[4].toHex()).toBe('#ffffff');
22
+ });
23
+ it('rounds non-integer stop counts and enforces a minimum of two anchors', () => {
24
+ const twoStopGradient = createColorGradient([new Color('#000000'), new Color('#ffffff')], {
25
+ stops: 1.7,
26
+ });
27
+ expect(twoStopGradient).toHaveLength(2);
28
+ expect(twoStopGradient[0].toHex()).toBe('#000000');
29
+ expect(twoStopGradient[1].toHex()).toBe('#ffffff');
30
+ });
31
+ it('supports bezier interpolation with easing while preserving anchors', () => {
32
+ const anchors = [new Color('#f43f5e'), new Color('#fbbf24'), new Color('#22d3ee')];
33
+ const gradient = createColorGradient(anchors, {
34
+ stops: 4,
35
+ interpolation: 'BEZIER',
36
+ space: 'HSL',
37
+ easing: 'EASE_IN_OUT',
38
+ });
39
+ // Bezier curve in Polar space (hue unwound)
40
+ expect(gradient.map((color) => color.toHex())).toEqual([
41
+ '#f43f5e',
42
+ '#f66f34',
43
+ '#24f340',
44
+ '#22d3ee',
45
+ ]);
46
+ expect(gradient[0].toHex()).toBe('#f43f5e');
47
+ expect(gradient[gradient.length - 1].toHex()).toBe('#22d3ee');
48
+ });
49
+ it('wraps hues smoothly across the 0°/360° boundary in polar spaces', () => {
50
+ const gradient = createColorGradient([new Color({ h: 350, s: 100, l: 50 }), new Color({ h: 10, s: 100, l: 50 })], { stops: 5, space: 'HSL' });
51
+ expect(gradient.map((color) => color.toHex())).toEqual([
52
+ '#ff002b',
53
+ '#ff0016',
54
+ '#ff0000',
55
+ '#ff1500',
56
+ '#ff2a00',
57
+ ]);
58
+ const hue = gradient[2].toHSL().h;
59
+ expect(Math.min(hue, 360 - hue)).toBeCloseTo(0, 0);
60
+ });
61
+ it('interpolates alpha alongside color channels', () => {
62
+ const gradient = createColorGradient([new Color('rgba(255,0,0,0.2)'), new Color('rgba(0,0,255,0.8)')], {
63
+ stops: 3,
64
+ space: 'RGB',
65
+ });
66
+ expect(gradient[0].toRGBA()).toEqual({ r: 255, g: 0, b: 0, a: 0.2 });
67
+ expect(gradient[1].toRGBA()).toEqual({ r: 128, g: 0, b: 128, a: 0.5 });
68
+ expect(gradient[2].toRGBA()).toEqual({ r: 0, g: 0, b: 255, a: 0.8 });
69
+ });
70
+ it('clamps eased stops inside OKLCH and LCH gamuts', () => {
71
+ const oklchGradient = createColorGradient([new Color('#111111'), new Color('#eeeeee')], {
72
+ stops: 3,
73
+ space: 'OKLCH',
74
+ easing: (t) => 1.2 * t - 0.1,
75
+ });
76
+ const lchGradient = createColorGradient([new Color('#2d2c7a'), new Color('#f4f0ff')], {
77
+ stops: 4,
78
+ space: 'LCH',
79
+ easing: 'EASE_OUT',
80
+ });
81
+ expect(oklchGradient[1].toOKLCH().c).toBeGreaterThanOrEqual(0);
82
+ expect(oklchGradient[1].toOKLCH().c).toBeLessThanOrEqual(0.5);
83
+ expect(lchGradient[1].toLCH().c).toBeLessThanOrEqual(150);
84
+ expect(lchGradient[2].toLCH().l).toBeGreaterThanOrEqual(0);
85
+ expect(lchGradient[2].toLCH().l).toBeLessThanOrEqual(100);
86
+ });
87
+ it('throws when fewer than two colors are provided', () => {
88
+ expect(() => createColorGradient([new Color('#ff0000')], { stops: 2 })).toThrow('at least two colors are required to build a gradient');
89
+ });
90
+ it('accepts readonly anchor arrays', () => {
91
+ const anchors = [new Color('#14213d'), new Color('#fca311')];
92
+ const gradient = createColorGradient(anchors, { stops: 3, space: 'RGB' });
93
+ expect(gradient.map((color) => color.toHex())).toEqual(['#14213d', '#886227', '#fca311']);
94
+ });
95
+ it('throws for invalid option values', () => {
96
+ const anchors = [new Color('#ff0000'), new Color('#0000ff')];
97
+ expect(() => createColorGradient(anchors, { space: 'INVALID' })).toThrow("Invalid 'space'");
98
+ expect(() => createColorGradient(anchors, { interpolation: 'INVALID' })).toThrow("Invalid 'interpolation'");
99
+ expect(() => createColorGradient(anchors, { easing: 'INVALID' })).toThrow("Invalid 'easing'");
100
+ expect(() => createColorGradient(anchors, { hueInterpolationMode: 'INVALID' })).toThrow("Invalid 'hueInterpolationMode'");
101
+ });
102
+ });
103
+ describe('Color gradient helpers', () => {
104
+ it('maintains anchors when easing between multiple colors', () => {
105
+ const anchors = [new Color('#ff0000'), new Color('#00ff00'), new Color('#0000ff')];
106
+ const gradient = createColorGradient(anchors, {
107
+ stops: 5,
108
+ easing: 'EASE_IN_OUT',
109
+ space: 'RGB',
110
+ });
111
+ expect(gradient[0].toHex()).toBe('#ff0000');
112
+ expect(gradient[2].toHex()).toBe('#00ff00');
113
+ expect(gradient[4].toHex()).toBe('#0000ff');
114
+ });
115
+ });
116
+ describe('Polar Hue Interpolation (createColorGradient)', () => {
117
+ const red = new Color('hsl(0, 100%, 50%)');
118
+ const blue = new Color('hsl(240, 100%, 50%)');
119
+ const green = new Color('hsl(120, 100%, 50%)');
120
+ it('reproduces current Cartesian desaturation behavior when explicitly requested', () => {
121
+ const gradient = createColorGradient([red, blue], {
122
+ stops: 3,
123
+ space: 'HSL',
124
+ hueInterpolationMode: 'CARTESIAN',
125
+ });
126
+ const mid = gradient[1];
127
+ const hsl = mid.toHSL();
128
+ // Cartesian interpolation between 0 and 240 results in hue 300, but saturation ~50%
129
+ expect(hsl.h).toBeCloseTo(300, 0);
130
+ expect(hsl.s).toBeCloseTo(50, 0);
131
+ });
132
+ it('defaults to Shortest Polar interpolation (preserving saturation)', () => {
133
+ const gradient = createColorGradient([red, blue], {
134
+ stops: 3,
135
+ space: 'HSL',
136
+ // No mode specified -> defaults to Shortest
137
+ });
138
+ const mid = gradient[1];
139
+ const hsl = mid.toHSL();
140
+ // Shortest path from 0 to 240 is via -60 (300) -> 240.
141
+ // 0 -> 240 diff is 240 (>180). 240 - 360 = -120.
142
+ // 0 -> -120. Midpoint -60 (300).
143
+ // Wait, 0 to 240.
144
+ // Shortest is 0 -> -120 -> 240? No.
145
+ // 0 -> 240. Diff is 240.
146
+ // 240 > 180. Adjusted End = 240 - 360 = -120.
147
+ // Start 0. End -120.
148
+ // Midpoint: -60. WrapHue(-60) = 300.
149
+ // Magenta.
150
+ expect(hsl.h).toBeCloseTo(300, 0);
151
+ expect(hsl.s).toBeCloseTo(100, 0); // Preserved Saturation
152
+ });
153
+ it('supports Longest mode', () => {
154
+ // Red (0) -> Blue (240).
155
+ // Diff 240. Abs(240) > 180.
156
+ // Longest mode condition: if (abs(diff) < 180).
157
+ // So here it does nothing.
158
+ // 0 -> 240. Midpoint 120. Green.
159
+ const gradient = createColorGradient([red, blue], {
160
+ stops: 3,
161
+ space: 'HSL',
162
+ hueInterpolationMode: 'LONGEST',
163
+ });
164
+ const mid = gradient[1];
165
+ expect(mid.toHSL().h).toBeCloseTo(120, 0);
166
+ expect(mid.toHSL().s).toBe(100);
167
+ });
168
+ it('supports Increasing mode (0 -> 240)', () => {
169
+ // 0 -> 240. 240 > 0. Stays 240.
170
+ // Midpoint 120.
171
+ const gradient = createColorGradient([red, blue], {
172
+ stops: 3,
173
+ space: 'HSL',
174
+ hueInterpolationMode: 'INCREASING',
175
+ });
176
+ const mid = gradient[1];
177
+ expect(mid.toHSL().h).toBeCloseTo(120, 0);
178
+ });
179
+ it('supports Increasing mode with wrap (350 -> 10)', () => {
180
+ const start = new Color('hsl(350, 100%, 50%)');
181
+ const end = new Color('hsl(10, 100%, 50%)');
182
+ // 350 -> 10. 10 < 350. Add 360 -> 370.
183
+ // 350 -> 370. Midpoint 360 (0).
184
+ const gradient = createColorGradient([start, end], {
185
+ stops: 3,
186
+ space: 'HSL',
187
+ hueInterpolationMode: 'INCREASING',
188
+ });
189
+ const mid = gradient[1];
190
+ const hue = mid.toHSL().h;
191
+ expect(Math.min(hue, 360 - hue)).toBeCloseTo(0, 0);
192
+ });
193
+ it('supports Decreasing mode (0 -> 240)', () => {
194
+ // 0 -> 240. 240 > 0. Sub 360 -> -120.
195
+ // 0 -> -120. Mid -60 (300).
196
+ const gradient = createColorGradient([red, blue], {
197
+ stops: 3,
198
+ space: 'HSL',
199
+ hueInterpolationMode: 'DECREASING',
200
+ });
201
+ const mid = gradient[1];
202
+ expect(mid.toHSL().h).toBeCloseTo(300, 0);
203
+ });
204
+ it('supports Decreasing mode with wrap (10 -> 350)', () => {
205
+ const start = new Color('hsl(10, 100%, 50%)');
206
+ const end = new Color('hsl(350, 100%, 50%)');
207
+ // 10 -> 350. 350 > 10. Sub 360 -> -10.
208
+ // 10 -> -10. Mid 0.
209
+ const gradient = createColorGradient([start, end], {
210
+ stops: 3,
211
+ space: 'HSL',
212
+ hueInterpolationMode: 'DECREASING',
213
+ });
214
+ const mid = gradient[1];
215
+ const hue = mid.toHSL().h;
216
+ expect(Math.min(hue, 360 - hue)).toBeCloseTo(0, 0);
217
+ });
218
+ it('supports Raw mode (350 -> 10)', () => {
219
+ // 350 -> 10. Raw interpolation.
220
+ // Midpoint (350+10)/2 = 180.
221
+ const start = new Color('hsl(350, 100%, 50%)');
222
+ const end = new Color('hsl(10, 100%, 50%)');
223
+ const gradient = createColorGradient([start, end], {
224
+ stops: 3,
225
+ space: 'HSL',
226
+ hueInterpolationMode: 'RAW',
227
+ });
228
+ const mid = gradient[1];
229
+ expect(mid.toHSL().h).toBeCloseTo(180, 0);
230
+ });
231
+ it('handles ambiguous Shortest path (180 degree diff)', () => {
232
+ // 0 -> 180. Diff 180.
233
+ // Shortest logic: if (diff > 180) ... else if (diff < -180).
234
+ // 180 is not > 180. So it stays 180.
235
+ // 0 -> 180. Mid 90.
236
+ const cyan = new Color('hsl(180, 100%, 50%)');
237
+ const gradient = createColorGradient([red, cyan], {
238
+ stops: 3,
239
+ space: 'HSL',
240
+ hueInterpolationMode: 'SHORTEST',
241
+ });
242
+ const mid = gradient[1];
243
+ expect(mid.toHSL().h).toBeCloseTo(90, 0);
244
+ });
245
+ it('handles ambiguous Longest path (180 degree diff)', () => {
246
+ // 0 -> 180. Diff 180.
247
+ // Longest logic: if (abs(diff) < 180).
248
+ // 180 is not < 180.
249
+ // So it stays 180.
250
+ // 0 -> 180. Mid 90.
251
+ const cyan = new Color('hsl(180, 100%, 50%)');
252
+ const gradient = createColorGradient([red, cyan], {
253
+ stops: 3,
254
+ space: 'HSL',
255
+ hueInterpolationMode: 'LONGEST',
256
+ });
257
+ const mid = gradient[1];
258
+ expect(mid.toHSL().h).toBeCloseTo(90, 0);
259
+ });
260
+ it('handles exact 0 degree difference', () => {
261
+ const gradient = createColorGradient([red, red], {
262
+ stops: 3,
263
+ space: 'HSL',
264
+ hueInterpolationMode: 'SHORTEST',
265
+ });
266
+ expect(gradient[1].toHSL().h).toBeCloseTo(0, 0);
267
+ });
268
+ it('works with OKLCH space (Default)', () => {
269
+ // Red (#ff0000) -> Blue (#0000ff) in OKLCH.
270
+ // Shortest path.
271
+ const gradient = createColorGradient([new Color('#ff0000'), new Color('#0000ff')], {
272
+ stops: 3,
273
+ space: 'OKLCH',
274
+ });
275
+ const mid = gradient[1];
276
+ // Check it's not gray.
277
+ expect(mid.toOKLCH().c).toBeGreaterThan(0.1);
278
+ });
279
+ it('supports OKLAB space to avoid polar hue handling', () => {
280
+ const start = new Color('#ff0000');
281
+ const end = new Color('#0000ff');
282
+ const gradient = createColorGradient([start, end], { stops: 3, space: 'OKLAB' });
283
+ const mid = gradient[1].toOKLAB();
284
+ expect(mid.l).toBeCloseTo(0.539339, 6);
285
+ expect(mid.a).toBeCloseTo(0.095752, 6);
286
+ expect(mid.b).toBeCloseTo(-0.092635, 6);
287
+ });
288
+ it('works with LCH space', () => {
289
+ const gradient = createColorGradient([new Color('#ff0000'), new Color('#0000ff')], {
290
+ stops: 3,
291
+ space: 'LCH',
292
+ });
293
+ const mid = gradient[1];
294
+ expect(mid.toLCH().c).toBeGreaterThan(10);
295
+ });
296
+ it('works with HSV space', () => {
297
+ const gradient = createColorGradient([red, blue], {
298
+ stops: 3,
299
+ space: 'HSV',
300
+ });
301
+ const mid = gradient[1];
302
+ expect(mid.toHSV().s).toBeCloseTo(100, 0);
303
+ });
304
+ it('ignores hueInterpolationMode in RGB space', () => {
305
+ // RGB interpolation Red -> Blue is #800080 (Purple).
306
+ // Even if we say 'Increasing' or 'Longest', RGB doesn't use hue.
307
+ const gradient = createColorGradient([red, blue], {
308
+ stops: 3,
309
+ space: 'RGB',
310
+ hueInterpolationMode: 'LONGEST', // Should be ignored
311
+ });
312
+ const mid = gradient[1];
313
+ expect(mid.toHex()).toBe('#800080');
314
+ });
315
+ it('applies hue unwinding to multiple stops correctly', () => {
316
+ // 0 -> 90 -> 180.
317
+ // Shortest: 0->90 (diff 90), 90->180 (diff 90). No adjustments.
318
+ const gradient = createColorGradient([red, green, blue], {
319
+ stops: 5,
320
+ space: 'HSL',
321
+ hueInterpolationMode: 'SHORTEST',
322
+ });
323
+ // Stops: 0, 90, 180.
324
+ // 5 stops: 0, 45, 90, 135 (approx), 180 (approx).
325
+ // Wait, anchors are 0, 120 (Green), 240 (Blue).
326
+ // 0 -> 120 (diff 120). 120 -> 240 (diff 120).
327
+ // Result should be 0, 60, 120, 180, 240.
328
+ // 0 (Red), 60 (Yellow), 120 (Green), 180 (Cyan), 240 (Blue).
329
+ expect(gradient[1].toHSL().h).toBeCloseTo(60, 1);
330
+ expect(gradient[3].toHSL().h).toBeCloseTo(180, 1);
331
+ });
332
+ it('handles zig-zag hues with Shortest', () => {
333
+ // 0 -> 10 -> 350.
334
+ // 0 -> 10.
335
+ // 10 -> 350. Diff 340. Shortest: 10 -> -10.
336
+ const start = new Color('hsl(0, 100%, 50%)');
337
+ const mid = new Color('hsl(10, 100%, 50%)');
338
+ const end = new Color('hsl(350, 100%, 50%)');
339
+ const gradient = createColorGradient([start, mid, end], {
340
+ stops: 5, // 0, 5, 10, 0, -10 (350).
341
+ space: 'HSL',
342
+ hueInterpolationMode: 'SHORTEST',
343
+ });
344
+ // Segment 1: 0 -> 10. 3 stops. 0, 5, 10.
345
+ // Segment 2: 10 -> 350 (adj -10). 3 stops. 10, 0, -10.
346
+ // Total 5 stops.
347
+ // 0: 0
348
+ // 1: 5 (mid of seg 1)
349
+ // 2: 10 (anchor)
350
+ // 3: 0 (mid of seg 2)
351
+ // 4: 350 (anchor)
352
+ const hue1 = gradient[1].toHSL().h;
353
+ const hue3 = gradient[3].toHSL().h;
354
+ expect(Math.min(hue1, 360 - hue1)).toBeCloseTo(5, 0);
355
+ expect(Math.min(hue3, 360 - hue3)).toBeCloseTo(0, 0);
356
+ });
357
+ it('works with clamping disabled', () => {
358
+ // Red -> Blue via Longest (0 -> 120 -> 240).
359
+ // Mid is 120.
360
+ const gradient = createColorGradient([red, blue], {
361
+ stops: 3,
362
+ space: 'HSL',
363
+ hueInterpolationMode: 'LONGEST',
364
+ clamp: false,
365
+ });
366
+ expect(gradient[1].toHSL().h).toBeCloseTo(120, 0);
367
+ });
368
+ it('works with Bezier interpolation and Shortest mode', () => {
369
+ // 0 -> 120 -> 240.
370
+ // Shortest unwinds to: 0 -> 120 -> 240.
371
+ // Bezier curve through these points.
372
+ // t=0.5. Bezier(0, 120, 240) at 0.5.
373
+ // L1 = lerp(0, 120, 0.5) = 60.
374
+ // L2 = lerp(120, 240, 0.5) = 180.
375
+ // L3 = lerp(60, 180, 0.5) = 120.
376
+ // Should be Green.
377
+ const gradient = createColorGradient([red, green, blue], {
378
+ stops: 3,
379
+ space: 'HSL',
380
+ interpolation: 'BEZIER',
381
+ });
382
+ // Bezier with 3 anchors -> 0 -> 120.
383
+ // stops=3. 0, 0.5, 1.
384
+ expect(gradient[1].toHSL().h).toBeCloseTo(120, 1);
385
+ });
386
+ it('accepts mixed case gradient space', () => {
387
+ const c1 = new Color('red');
388
+ const c2 = new Color('blue');
389
+ const g1 = createColorGradient([c1, c2], { space: 'HSL' });
390
+ const g2 = createColorGradient([c1, c2], { space: 'hsl' });
391
+ expect(g1[1].toHex()).toBe(g2[1].toHex());
392
+ });
393
+ it('accepts mixed case interpolation', () => {
394
+ const c1 = new Color('red');
395
+ const c2 = new Color('blue');
396
+ const g1 = createColorGradient([c1, c2], { interpolation: 'BEZIER' });
397
+ const g2 = createColorGradient([c1, c2], { interpolation: 'bezier' });
398
+ expect(g1[1].toHex()).toBe(g2[1].toHex());
399
+ });
400
+ it('accepts mixed case easing', () => {
401
+ const c1 = new Color('red');
402
+ const c2 = new Color('blue');
403
+ const g1 = createColorGradient([c1, c2], { easing: 'EASE_IN' });
404
+ const g2 = createColorGradient([c1, c2], { easing: 'ease_in' });
405
+ expect(g1[1].toHex()).toBe(g2[1].toHex());
406
+ });
407
+ it('accepts mixed case hue interpolation mode', () => {
408
+ const c1 = new Color('red');
409
+ const c2 = new Color('blue');
410
+ const g1 = createColorGradient([c1, c2], { hueInterpolationMode: 'LONGEST' });
411
+ const g2 = createColorGradient([c1, c2], { hueInterpolationMode: 'longest' });
412
+ expect(g1[1].toHex()).toBe(g2[1].toHex());
413
+ });
414
+ });
@@ -0,0 +1 @@
1
+ export {};