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 @@
1
+ export {};
@@ -0,0 +1,499 @@
1
+ import tinycolor from 'tinycolor2';
2
+ import { Color } from '../index';
3
+ function tinycolorRGBToObj(value) {
4
+ const alpha = Number.isFinite(value.a) ? value.a : undefined;
5
+ if (alpha === undefined || alpha === 1) {
6
+ return {
7
+ r: value.r,
8
+ g: value.g,
9
+ b: value.b,
10
+ };
11
+ }
12
+ return {
13
+ r: value.r,
14
+ g: value.g,
15
+ b: value.b,
16
+ a: alpha,
17
+ };
18
+ }
19
+ function expectSimilarRGBAValues(omniValues, tiny, tolerance = 1) {
20
+ const { r, g, b, a } = tiny.toRgb();
21
+ expect(Math.abs(omniValues.r - r)).toBeLessThanOrEqual(tolerance);
22
+ expect(Math.abs(omniValues.g - g)).toBeLessThanOrEqual(tolerance);
23
+ expect(Math.abs(omniValues.b - b)).toBeLessThanOrEqual(tolerance);
24
+ if ('a' in omniValues && omniValues.a !== undefined) {
25
+ expect(omniValues.a).toBeCloseTo(a, 2);
26
+ }
27
+ }
28
+ const formatDecimal = (value) => Number(value.toFixed(3));
29
+ function expectRoundedMatch(omniValue, tinyValue, tolerance = 1) {
30
+ expect(Math.abs(omniValue - tinyValue)).toBeLessThanOrEqual(tolerance);
31
+ }
32
+ function getNumericValuesFromString(value) {
33
+ return value.match(/-?\\d*\\.?\\d+/g)?.map(Number) ?? [];
34
+ }
35
+ function expectComponentArraysClose(omniValues, tinyValues, tolerance = 1, alphaTolerance = 0.02) {
36
+ const omniHasAlpha = omniValues.length > 3;
37
+ const tinyHasAlpha = tinyValues.length > 3;
38
+ const alignedTinyValues = (() => {
39
+ if (omniHasAlpha && !tinyHasAlpha) {
40
+ return [...tinyValues, 1];
41
+ }
42
+ if (!omniHasAlpha && tinyHasAlpha) {
43
+ return tinyValues.slice(0, omniValues.length);
44
+ }
45
+ return tinyValues;
46
+ })();
47
+ expect(omniValues.length).toBe(alignedTinyValues.length);
48
+ omniValues.forEach((component, index) => {
49
+ const componentTolerance = omniHasAlpha && index === omniValues.length - 1 ? alphaTolerance : tolerance;
50
+ expect(Math.abs(component - alignedTinyValues[index])).toBeLessThanOrEqual(componentTolerance);
51
+ });
52
+ }
53
+ function getHexesFromColors(colors) {
54
+ return colors.map((color) => color.toHex());
55
+ }
56
+ function getHexesFromTinyColors(colors) {
57
+ return colors.map((color) => color.toHexString().toLowerCase());
58
+ }
59
+ function sortHexesByHue(hexes) {
60
+ return hexes
61
+ .map((hex) => ({ hex: hex.toLowerCase(), hue: tinycolor(hex).toHsl().h ?? 0 }))
62
+ .sort((a, b) => a.hue - b.hue)
63
+ .map(({ hex }) => hex);
64
+ }
65
+ function expectHexArraysClose(omniHexes, tinyHexes, tolerance = 1) {
66
+ const omniSorted = sortHexesByHue(omniHexes);
67
+ const tinySorted = sortHexesByHue(tinyHexes);
68
+ expect(omniSorted).toHaveLength(tinySorted.length);
69
+ omniSorted.forEach((hex, index) => {
70
+ const omniRgb = tinycolor(hex).toRgb();
71
+ const tinyRgb = tinycolor(tinySorted[index]).toRgb();
72
+ expect(Math.abs(omniRgb.r - tinyRgb.r)).toBeLessThanOrEqual(tolerance);
73
+ expect(Math.abs(omniRgb.g - tinyRgb.g)).toBeLessThanOrEqual(tolerance);
74
+ expect(Math.abs(omniRgb.b - tinyRgb.b)).toBeLessThanOrEqual(tolerance);
75
+ });
76
+ }
77
+ function getHueSteps(hexes) {
78
+ const hues = sortHexesByHue(hexes).map((hex) => tinycolor(hex).toHsl().h ?? 0);
79
+ const steps = [];
80
+ for (let index = 1; index < hues.length; index += 1) {
81
+ steps.push(Math.round(hues[index] - hues[index - 1]));
82
+ }
83
+ return steps;
84
+ }
85
+ function getLightnesses(hexes) {
86
+ return hexes.map((hex) => tinycolor(hex).toHsl().l ?? 0);
87
+ }
88
+ describe('Color interoperability with tinycolor2', () => {
89
+ describe('parses and normalizes common inputs', () => {
90
+ it('matches tinycolor outputs for hex, named colors, and hsl strings', () => {
91
+ const hex = new Color('#6495ed');
92
+ const hexTiny = tinycolor('#6495ed');
93
+ expect(hex.toHex()).toBe(hexTiny.toHexString().toLowerCase());
94
+ expect(hex.toRGB()).toEqual(tinycolorRGBToObj(hexTiny.toRgb()));
95
+ const named = new Color('indigo');
96
+ const namedTiny = tinycolor('indigo');
97
+ expect(named.toHex()).toBe(namedTiny.toHexString().toLowerCase());
98
+ expect(named.toRGB()).toEqual(tinycolorRGBToObj(namedTiny.toRgb()));
99
+ const hslString = new Color('hsl(200, 50%, 40%)');
100
+ const hslTiny = tinycolor('hsl(200, 50%, 40%)');
101
+ expect(hslString.toHex()).toBe(hslTiny.toHexString().toLowerCase());
102
+ expect(hslString.toRGB()).toEqual(tinycolorRGBToObj(hslTiny.toRgb()));
103
+ });
104
+ it('matches tinycolor outputs for shorthand, uppercase, percent, and clamped inputs', () => {
105
+ const shortHex = new Color('#abc');
106
+ const shortHexTiny = tinycolor('#abc');
107
+ expect(shortHex.toHex()).toBe(shortHexTiny.toHexString().toLowerCase());
108
+ expect(shortHex.toRGBA()).toMatchObject(tinycolorRGBToObj(shortHexTiny.toRgb()));
109
+ const uppercaseHex = new Color('#ABCDEF');
110
+ const uppercaseHexTiny = tinycolor('#ABCDEF');
111
+ expect(uppercaseHex.toHex()).toBe(uppercaseHexTiny.toHexString().toLowerCase());
112
+ expect(uppercaseHex.toRGBA()).toMatchObject(tinycolorRGBToObj(uppercaseHexTiny.toRgb()));
113
+ // CSS percentages clamp to 0–100%, while tinycolor wraps them.
114
+ const rgbPercent = new Color('rgb(120%, 50%, -10%)');
115
+ const rgbPercentTiny = tinycolor('rgb(120%, 50%, -10%)');
116
+ expect(rgbPercent.toHex()).toBe('#ff8000');
117
+ expect(rgbPercentTiny.toHexString().toLowerCase()).toBe('#338000');
118
+ expect(rgbPercent.toRGBA()).toEqual({ r: 255, g: 128, b: 0, a: 1 });
119
+ expect(rgbPercent.toRGBA()).not.toEqual(tinycolorRGBToObj(rgbPercentTiny.toRgb()));
120
+ const rgbaPercent = new Color('rgba(110%, 10%, 50%, 120%)');
121
+ const rgbaPercentTiny = tinycolor('rgba(110%, 10%, 50%, 120%)');
122
+ expect(rgbaPercent.toHex8()).toBe('#ff1a80ff');
123
+ expect(rgbaPercentTiny.toHex8String().toLowerCase()).toBe('#1a1a80ff');
124
+ expect(rgbaPercent.toRGBA()).toEqual({ r: 255, g: 26, b: 128, a: 1 });
125
+ expect(rgbaPercent.toRGBA()).not.toEqual(tinycolorRGBToObj(rgbaPercentTiny.toRgb()));
126
+ const hslClamped = new Color('hsl(370, 110%, -5%)');
127
+ const hslClampedTiny = tinycolor('hsl(370, 110%, -5%)');
128
+ expect(hslClamped.toHex()).toBe(hslClampedTiny.toHexString().toLowerCase());
129
+ expect(hslClamped.toRGBA()).toMatchObject(tinycolorRGBToObj(hslClampedTiny.toRgb()));
130
+ // Hue is normalized and saturation/alpha are clamped to CSS limits.
131
+ const hslaClamped = new Color('hsla(-15, 105%, 55%, 1.2)');
132
+ const hslaClampedTiny = tinycolor('hsla(-15, 105%, 55%, 1.2)');
133
+ expect(hslaClamped.toHex8()).toBe('#ff1a53ff');
134
+ expect(hslaClampedTiny.toHex8String().toLowerCase()).toBe('#ff1a1aff');
135
+ const clampedRgba = hslaClamped.toRGBA();
136
+ expect(clampedRgba.r).toBeCloseTo(255, 5);
137
+ expect(clampedRgba.g).toBeCloseTo(25.5, 3);
138
+ expect(clampedRgba.b).toBeCloseTo(82.875, 3);
139
+ expect(clampedRgba.a).toBe(1);
140
+ expect(hslaClamped.toRGBA()).not.toEqual(tinycolorRGBToObj(hslaClampedTiny.toRgb()));
141
+ });
142
+ });
143
+ describe('handles alpha channels consistently', () => {
144
+ it('preserves hex8 and rgba transparency', () => {
145
+ const hex8 = new Color('#1e90ff80');
146
+ const hex8Tiny = tinycolor('#1e90ff80');
147
+ expect(hex8.toHex8()).toBe(hex8Tiny.toHex8String().toLowerCase());
148
+ expect(hex8.toRGBA()).toEqual(tinycolorRGBToObj(hex8Tiny.toRgb()));
149
+ const rgba = new Color('rgba(12, 200, 180, 0.35)');
150
+ const rgbaTiny = tinycolor('rgba(12, 200, 180, 0.35)');
151
+ expect(rgba.toHex8()).toBe(rgbaTiny.toHex8String().toLowerCase());
152
+ expect(rgba.toRGBA()).toEqual(tinycolorRGBToObj(rgbaTiny.toRgb()));
153
+ });
154
+ it('updates alpha for opaque inputs while clamping more accurately than tinycolor', () => {
155
+ const hex = '#336699';
156
+ const inRange = new Color(hex).setAlpha(0.4);
157
+ const inRangeTiny = tinycolor(hex).setAlpha(0.4);
158
+ expect(inRange.getAlpha()).toBeCloseTo(inRangeTiny.getAlpha(), 3);
159
+ expect(inRange.toHex8()).toBe(inRangeTiny.toHex8String().toLowerCase());
160
+ expect(inRange.toRGBA()).toEqual(tinycolorRGBToObj(inRangeTiny.toRgb()));
161
+ const aboveRange = new Color(hex).setAlpha(1.25);
162
+ const aboveRangeTiny = tinycolor(hex).setAlpha(1.25);
163
+ expect(aboveRange.getAlpha()).toBeCloseTo(aboveRangeTiny.getAlpha(), 3);
164
+ expect(aboveRange.toHex8()).toBe(aboveRangeTiny.toHex8String().toLowerCase());
165
+ expect(aboveRange.toRGBA()).toMatchObject(tinycolorRGBToObj(aboveRangeTiny.toRgb()));
166
+ const belowRange = new Color(hex).setAlpha(-0.2);
167
+ const belowRangeTiny = tinycolor(hex).setAlpha(-0.2);
168
+ expect(belowRange.getAlpha()).toBe(0);
169
+ expect(belowRange.toHex8()).toBe('#33669900');
170
+ expect(belowRange.toRGBA()).toEqual({ r: 51, g: 102, b: 153, a: 0 });
171
+ expect(belowRangeTiny.getAlpha()).toBe(1);
172
+ expect(belowRangeTiny.toHex8String().toLowerCase()).toBe('#336699ff');
173
+ expect(belowRange.toHex8()).not.toBe(belowRangeTiny.toHex8String().toLowerCase());
174
+ const nonFinite = new Color(hex).setAlpha(Number.NaN);
175
+ const nonFiniteTiny = tinycolor(hex).setAlpha(Number.NaN);
176
+ expect(nonFinite.getAlpha()).toBe(1);
177
+ expect(nonFinite.toHex8()).toBe(nonFiniteTiny.toHex8String().toLowerCase());
178
+ expect(nonFinite.toRGBA()).toMatchObject(tinycolorRGBToObj(nonFiniteTiny.toRgb()));
179
+ const infinite = new Color(hex).setAlpha(Number.POSITIVE_INFINITY);
180
+ const infiniteTiny = tinycolor(hex).setAlpha(Number.POSITIVE_INFINITY);
181
+ expect(infinite.getAlpha()).toBe(1);
182
+ expect(infinite.toHex8()).toBe(infiniteTiny.toHex8String().toLowerCase());
183
+ expect(infinite.toRGBA()).toMatchObject(tinycolorRGBToObj(infiniteTiny.toRgb()));
184
+ });
185
+ it('adjusts alpha on transparent colors with clamping that mirrors valid tinycolor outputs', () => {
186
+ const base = 'rgba(120, 80, 200, 0.35)';
187
+ const inRange = new Color(base).setAlpha(0.6);
188
+ const inRangeTiny = tinycolor(base).setAlpha(0.6);
189
+ expect(inRange.getAlpha()).toBeCloseTo(inRangeTiny.getAlpha(), 3);
190
+ expect(inRange.toHex8()).toBe(inRangeTiny.toHex8String().toLowerCase());
191
+ expect(inRange.toRGBA()).toEqual(tinycolorRGBToObj(inRangeTiny.toRgb()));
192
+ const aboveRange = new Color(base).setAlpha(2.4);
193
+ const aboveRangeTiny = tinycolor(base).setAlpha(2.4);
194
+ expect(aboveRange.getAlpha()).toBeCloseTo(aboveRangeTiny.getAlpha(), 3);
195
+ expect(aboveRange.toHex8()).toBe(aboveRangeTiny.toHex8String().toLowerCase());
196
+ expect(aboveRange.toRGBA()).toMatchObject(tinycolorRGBToObj(aboveRangeTiny.toRgb()));
197
+ const nonFinite = new Color(base).setAlpha(Number.NEGATIVE_INFINITY);
198
+ const nonFiniteTiny = tinycolor(base).setAlpha(Number.NEGATIVE_INFINITY);
199
+ expect(nonFinite.getAlpha()).toBe(1);
200
+ expect(nonFinite.toHex8()).toBe(nonFiniteTiny.toHex8String().toLowerCase());
201
+ expect(nonFinite.toRGBA()).toMatchObject(tinycolorRGBToObj(nonFiniteTiny.toRgb()));
202
+ });
203
+ });
204
+ describe('keeps YIQ darkness checks aligned with tinycolor when requested', () => {
205
+ it('matches tinycolor darkness and lightness classifications for YIQ inputs', () => {
206
+ const pureBlack = '#000';
207
+ expect(new Color(pureBlack).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(pureBlack).isDark());
208
+ expect(new Color(pureBlack).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(pureBlack).isLight());
209
+ const pureWhite = '#fff';
210
+ expect(new Color(pureWhite).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(pureWhite).isDark());
211
+ expect(new Color(pureWhite).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(pureWhite).isLight());
212
+ const middleGray = '#7f7f7f';
213
+ expect(new Color(middleGray).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(middleGray).isDark());
214
+ expect(new Color(middleGray).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(middleGray).isLight());
215
+ const darkerGray = '#808080';
216
+ expect(new Color(darkerGray).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(darkerGray).isDark());
217
+ expect(new Color(darkerGray).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(darkerGray).isLight());
218
+ const saturatedRed = '#ff0000';
219
+ expect(new Color(saturatedRed).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(saturatedRed).isDark());
220
+ expect(new Color(saturatedRed).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(saturatedRed).isLight());
221
+ const saturatedBlue = '#0000ff';
222
+ expect(new Color(saturatedBlue).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(saturatedBlue).isDark());
223
+ expect(new Color(saturatedBlue).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(saturatedBlue).isLight());
224
+ const saturatedGreen = '#00ff00';
225
+ expect(new Color(saturatedGreen).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(saturatedGreen).isDark());
226
+ expect(new Color(saturatedGreen).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(saturatedGreen).isLight());
227
+ const pastelBlue = '#a0c8ff';
228
+ expect(new Color(pastelBlue).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(pastelBlue).isDark());
229
+ expect(new Color(pastelBlue).isDark({ colorDarknessMode: 'YIQ' })).toBe(!tinycolor(pastelBlue).isLight());
230
+ });
231
+ it('documents WCAG default divergence from tinycolor on borderline gray', () => {
232
+ const borderlineGray = '#7f7f7f';
233
+ expect(new Color(borderlineGray).isDark()).toBe(false);
234
+ expect(tinycolor(borderlineGray).isDark()).toBe(true);
235
+ expect(new Color(borderlineGray).isDark({ colorDarknessMode: 'YIQ' })).toBe(tinycolor(borderlineGray).isDark());
236
+ });
237
+ });
238
+ describe('aligns readability helpers where the algorithms overlap', () => {
239
+ it('matches tinycolor contrast ratios for common opaque pairs', () => {
240
+ const pairs = [
241
+ ['#000000', '#ffffff'],
242
+ ['#444444', '#bbbbbb'],
243
+ ['#777777', '#cccccc'],
244
+ ];
245
+ pairs.forEach(([foreground, background]) => {
246
+ const omniContrast = new Color(foreground).getWCAGContrastRatio(background);
247
+ const tinyContrast = tinycolor.readability(foreground, background);
248
+ expect(omniContrast).toBeCloseTo(tinyContrast, 2);
249
+ });
250
+ });
251
+ it('keeps readability reports aligned with tinycolor while documenting alpha handling differences', () => {
252
+ const opaqueForeground = '#1a1a1a';
253
+ const opaqueBackground = '#fafafa';
254
+ const opaqueReport = new Color(opaqueForeground).getWCAGReadabilityReport(opaqueBackground);
255
+ const tinyOpaqueReadable = tinycolor.isReadable(opaqueForeground, opaqueBackground, {
256
+ level: 'AA',
257
+ size: 'small',
258
+ });
259
+ expect(opaqueReport.contrastRatio).toBeCloseTo(tinycolor.readability(opaqueForeground, opaqueBackground), 2);
260
+ expect(opaqueReport.isReadable).toBe(tinyOpaqueReadable);
261
+ const semiTransparentForeground = 'rgba(0, 0, 0, 0.5)';
262
+ const semiTransparentBackground = '#ffffff';
263
+ const semiTransparentReport = new Color(semiTransparentForeground).getWCAGReadabilityReport(semiTransparentBackground);
264
+ const tinySemiReadable = tinycolor.isReadable(semiTransparentForeground, semiTransparentBackground, {
265
+ level: 'AA',
266
+ size: 'small',
267
+ });
268
+ expect(tinySemiReadable).toBe(true);
269
+ expect(semiTransparentReport.isReadable).toBe(false);
270
+ expect(semiTransparentReport.contrastRatio).toBeLessThan(tinycolor.readability(semiTransparentForeground, semiTransparentBackground));
271
+ });
272
+ it('picks the most readable candidate in agreement with tinycolor for light and dark backgrounds', () => {
273
+ const candidates = ['#000000', '#3366ff', '#ffcc00'];
274
+ const lightBackground = '#ffffff';
275
+ const omniLightChoice = new Color(lightBackground).getMostReadableTextColor(candidates);
276
+ const tinyLightChoice = tinycolor.mostReadable(lightBackground, candidates);
277
+ expect(omniLightChoice.toHex()).toBe(tinyLightChoice.toHexString().toLowerCase());
278
+ const darkBackground = '#111111';
279
+ const omniDarkChoice = new Color(darkBackground).getMostReadableTextColor(candidates);
280
+ const tinyDarkChoice = tinycolor.mostReadable(darkBackground, candidates);
281
+ expect(omniDarkChoice.toHex()).toBe(tinyDarkChoice.toHexString().toLowerCase());
282
+ });
283
+ });
284
+ describe('matches tinycolor mixing defaults in RGB space', () => {
285
+ it('aligns 50/50 mixes with tinycolor defaults', () => {
286
+ const base = new Color('#ff0000');
287
+ const other = '#0000ff';
288
+ const mixed = base.mix([other], { space: 'RGB', weights: [0.5, 0.5] });
289
+ const tinyMixed = tinycolor.mix(base.toHex(), other);
290
+ expect(mixed.toHex()).toBe(tinyMixed.toHexString().toLowerCase());
291
+ expectSimilarRGBAValues(mixed.toRGBA(), tinyMixed);
292
+ });
293
+ it('respects tinycolor weight ordering for asymmetric amounts', () => {
294
+ const base = '#ff0000';
295
+ const other = '#0000ff';
296
+ const mixed25 = new Color(base).mix([other], { space: 'RGB', weights: [0.75, 0.25] });
297
+ const tinyMixed25 = tinycolor.mix(base, other, 25);
298
+ expect(mixed25.toHex()).toBe(tinyMixed25.toHexString().toLowerCase());
299
+ expectSimilarRGBAValues(mixed25.toRGBA(), tinyMixed25);
300
+ const mixed75 = new Color(base).mix([other], { space: 'RGB', weights: [0.25, 0.75] });
301
+ const tinyMixed75 = tinycolor.mix(base, other, 75);
302
+ expect(mixed75.toHex()).toBe(tinyMixed75.toHexString().toLowerCase());
303
+ expectSimilarRGBAValues(mixed75.toRGBA(), tinyMixed75);
304
+ });
305
+ it('mixes rgba inputs consistently with tinycolor defaults', () => {
306
+ const base = 'rgba(255, 0, 0, 0.6)';
307
+ const other = 'rgba(0, 0, 255, 0.2)';
308
+ const mixed = new Color(base).mix([other], { space: 'RGB', weights: [0.5, 0.5] });
309
+ const tinyMixed = tinycolor.mix(base, other);
310
+ expect(mixed.toHex8()).toBe(tinyMixed.toHex8String().toLowerCase());
311
+ expectSimilarRGBAValues(mixed.toRGBA(), tinyMixed);
312
+ const skewed = new Color(base).mix([other], { space: 'RGB', weights: [0.75, 0.25] });
313
+ const tinySkewed = tinycolor.mix(base, other, 25);
314
+ expect(skewed.toHex8()).toBe(tinySkewed.toHex8String().toLowerCase());
315
+ expectSimilarRGBAValues(skewed.toRGBA(), tinySkewed);
316
+ });
317
+ });
318
+ describe('aligns basic HSL manipulations', () => {
319
+ it('lightens and darkens in step with tinycolor defaults', () => {
320
+ const base = new Color('#556b2f');
321
+ const lightened = base.brighten();
322
+ const darkened = base.darken();
323
+ const tinyLightened = tinycolor('#556b2f').lighten();
324
+ const tinyDarkened = tinycolor('#556b2f').darken();
325
+ expectSimilarRGBAValues(lightened.toRGB(), tinyLightened);
326
+ expectSimilarRGBAValues(darkened.toRGB(), tinyDarkened);
327
+ });
328
+ it('keeps saturation adjustments aligned for straightforward cases', () => {
329
+ const base = new Color('hsl(210, 30%, 50%)');
330
+ const saturated = base.saturate();
331
+ const desaturated = base.desaturate();
332
+ const tinySaturated = tinycolor('hsl(210, 30%, 50%)').saturate();
333
+ const tinyDesaturated = tinycolor('hsl(210, 30%, 50%)').desaturate();
334
+ expectSimilarRGBAValues(saturated.toRGB(), tinySaturated);
335
+ expectSimilarRGBAValues(desaturated.toRGB(), tinyDesaturated);
336
+ });
337
+ it('spins hue similarly when moving around the wheel', () => {
338
+ const base = new Color('#ff4500');
339
+ const spun = base.spin(120);
340
+ const spunTiny = tinycolor('#ff4500').spin(120);
341
+ expectSimilarRGBAValues(spun.toRGB(), spunTiny);
342
+ expect(spun.getAlpha()).toBeCloseTo(spunTiny.getAlpha(), 5);
343
+ });
344
+ it('tracks tinycolor manipulation amounts while maintaining alpha', () => {
345
+ const opaqueBase = new Color('#4682b4');
346
+ const translucentBase = new Color('rgba(70, 130, 180, 0.42)');
347
+ const lightened = opaqueBase.brighten({ amount: 18 });
348
+ const darkened = opaqueBase.darken({ amount: 22 });
349
+ const saturated = opaqueBase.saturate({ amount: 35 });
350
+ const desaturated = opaqueBase.desaturate({ amount: 48 });
351
+ const grayscaled = opaqueBase.grayscale();
352
+ const spun = opaqueBase.spin(-75);
353
+ const tinyLightened = tinycolor('#4682b4').lighten(18);
354
+ const tinyDarkened = tinycolor('#4682b4').darken(22);
355
+ const tinySaturated = tinycolor('#4682b4').saturate(35);
356
+ const tinyDesaturated = tinycolor('#4682b4').desaturate(48);
357
+ const tinyGrayscaled = tinycolor('#4682b4').greyscale();
358
+ const tinySpun = tinycolor('#4682b4').spin(-75);
359
+ expectSimilarRGBAValues(lightened.toRGB(), tinyLightened);
360
+ expectSimilarRGBAValues(darkened.toRGB(), tinyDarkened);
361
+ expectSimilarRGBAValues(saturated.toRGB(), tinySaturated);
362
+ expectSimilarRGBAValues(desaturated.toRGB(), tinyDesaturated);
363
+ expectSimilarRGBAValues(grayscaled.toRGB(), tinyGrayscaled);
364
+ expectSimilarRGBAValues(spun.toRGB(), tinySpun);
365
+ const translucentLightened = translucentBase.brighten({ amount: 12 });
366
+ const translucentDarkened = translucentBase.darken({ amount: 14 });
367
+ const translucentSaturated = translucentBase.saturate({ amount: 20 });
368
+ const translucentDesaturated = translucentBase.desaturate({ amount: 26 });
369
+ const translucentGrayscaled = translucentBase.grayscale();
370
+ const translucentSpun = translucentBase.spin(40);
371
+ const tinyTranslucentLightened = tinycolor('rgba(70, 130, 180, 0.42)').lighten(12);
372
+ const tinyTranslucentDarkened = tinycolor('rgba(70, 130, 180, 0.42)').darken(14);
373
+ const tinyTranslucentSaturated = tinycolor('rgba(70, 130, 180, 0.42)').saturate(20);
374
+ const tinyTranslucentDesaturated = tinycolor('rgba(70, 130, 180, 0.42)').desaturate(26);
375
+ const tinyTranslucentGrayscaled = tinycolor('rgba(70, 130, 180, 0.42)').greyscale();
376
+ const tinyTranslucentSpun = tinycolor('rgba(70, 130, 180, 0.42)').spin(40);
377
+ expectSimilarRGBAValues(translucentLightened.toRGBA(), tinyTranslucentLightened);
378
+ expectSimilarRGBAValues(translucentDarkened.toRGBA(), tinyTranslucentDarkened);
379
+ expectSimilarRGBAValues(translucentSaturated.toRGBA(), tinyTranslucentSaturated);
380
+ expectSimilarRGBAValues(translucentDesaturated.toRGBA(), tinyTranslucentDesaturated);
381
+ expectSimilarRGBAValues(translucentGrayscaled.toRGBA(), tinyTranslucentGrayscaled);
382
+ expectSimilarRGBAValues(translucentSpun.toRGBA(), tinyTranslucentSpun);
383
+ expect(translucentLightened.getAlpha()).toBeCloseTo(translucentBase.getAlpha(), 5);
384
+ expect(translucentDarkened.getAlpha()).toBeCloseTo(translucentBase.getAlpha(), 5);
385
+ expect(translucentSaturated.getAlpha()).toBeCloseTo(translucentBase.getAlpha(), 5);
386
+ expect(translucentDesaturated.getAlpha()).toBeCloseTo(translucentBase.getAlpha(), 5);
387
+ expect(translucentGrayscaled.getAlpha()).toBeCloseTo(translucentBase.getAlpha(), 5);
388
+ expect(translucentSpun.getAlpha()).toBeCloseTo(translucentBase.getAlpha(), 5);
389
+ });
390
+ });
391
+ describe('keeps HSL/HSV conversions closely aligned with tinycolor', () => {
392
+ it('matches tinycolor HSL/HSV outputs for opaque colors', () => {
393
+ const color = new Color('#3498db');
394
+ const tiny = tinycolor('#3498db');
395
+ const hsl = color.toHSL();
396
+ const hsla = color.toHSLA();
397
+ const tinyHsl = tiny.toHsl();
398
+ expectRoundedMatch(hsl.h, tinyHsl.h);
399
+ expectRoundedMatch(hsl.s, tinyHsl.s * 100);
400
+ expectRoundedMatch(hsl.l, tinyHsl.l * 100);
401
+ expectRoundedMatch(hsla.h, tinyHsl.h);
402
+ expectRoundedMatch(hsla.s, tinyHsl.s * 100);
403
+ expectRoundedMatch(hsla.l, tinyHsl.l * 100);
404
+ expect(hsla.a).toBeCloseTo(tinyHsl.a ?? 1, 2);
405
+ const hsv = color.toHSV();
406
+ const hsva = color.toHSVA();
407
+ const tinyHsv = tiny.toHsv();
408
+ expectRoundedMatch(hsv.h, tinyHsv.h);
409
+ expectRoundedMatch(hsv.s, tinyHsv.s * 100);
410
+ expectRoundedMatch(hsv.v, tinyHsv.v * 100);
411
+ expectRoundedMatch(hsva.h, tinyHsv.h);
412
+ expectRoundedMatch(hsva.s, tinyHsv.s * 100);
413
+ expectRoundedMatch(hsva.v, tinyHsv.v * 100);
414
+ expect(hsva.a).toBeCloseTo(tinyHsv.a ?? 1, 2);
415
+ const tinyHslStringValues = getNumericValuesFromString(tiny.toHslString());
416
+ const tinyHsvStringValues = getNumericValuesFromString(tiny.toHsvString());
417
+ expectComponentArraysClose(getNumericValuesFromString(color.toHSLString()), tinyHslStringValues);
418
+ expectComponentArraysClose(getNumericValuesFromString(color.toHSLAString()), tinyHslStringValues);
419
+ expectComponentArraysClose(getNumericValuesFromString(`hsv(${formatDecimal(hsva.h)} ${formatDecimal(hsva.s)}% ${formatDecimal(hsva.v)}%)`), tinyHsvStringValues);
420
+ expectComponentArraysClose(getNumericValuesFromString(color.toRGBAString()), getNumericValuesFromString(tiny.toRgbString()));
421
+ });
422
+ it('matches tinycolor HSL/HSV outputs for colors with alpha', () => {
423
+ const color = new Color('rgba(50, 100, 150, 0.42)');
424
+ const tiny = tinycolor('rgba(50, 100, 150, 0.42)');
425
+ const hsl = color.toHSL();
426
+ const hsla = color.toHSLA();
427
+ const tinyHsl = tiny.toHsl();
428
+ expectRoundedMatch(hsl.h, tinyHsl.h);
429
+ expectRoundedMatch(hsl.s, tinyHsl.s * 100);
430
+ expectRoundedMatch(hsl.l, tinyHsl.l * 100);
431
+ expectRoundedMatch(hsla.h, tinyHsl.h);
432
+ expectRoundedMatch(hsla.s, tinyHsl.s * 100);
433
+ expectRoundedMatch(hsla.l, tinyHsl.l * 100);
434
+ expect(hsla.a).toBeCloseTo(tinyHsl.a ?? 1, 2);
435
+ const hsv = color.toHSV();
436
+ const hsva = color.toHSVA();
437
+ const tinyHsv = tiny.toHsv();
438
+ expectRoundedMatch(hsv.h, tinyHsv.h);
439
+ expectRoundedMatch(hsv.s, tinyHsv.s * 100);
440
+ expectRoundedMatch(hsv.v, tinyHsv.v * 100);
441
+ expectRoundedMatch(hsva.h, tinyHsv.h);
442
+ expectRoundedMatch(hsva.s, tinyHsv.s * 100);
443
+ expectRoundedMatch(hsva.v, tinyHsv.v * 100);
444
+ expect(hsva.a).toBeCloseTo(tinyHsv.a ?? 1, 2);
445
+ const tinyHslStringValues = getNumericValuesFromString(tiny.toHslString());
446
+ const tinyHsvStringValues = getNumericValuesFromString(tiny.toHsvString());
447
+ expectComponentArraysClose(getNumericValuesFromString(color.toHSLAString()), tinyHslStringValues);
448
+ expectComponentArraysClose(getNumericValuesFromString(`hsv(${formatDecimal(hsva.h)} ${formatDecimal(hsva.s)}% ${formatDecimal(hsva.v)}% / ${formatDecimal(hsva.a)})`), tinyHsvStringValues);
449
+ expectComponentArraysClose(getNumericValuesFromString(color.toRGBAString()), getNumericValuesFromString(tiny.toRgbString()));
450
+ });
451
+ });
452
+ describe('keeps harmony helpers aligned with tinycolor where intended', () => {
453
+ const baseHex = '#3498db';
454
+ const baseColor = new Color(baseHex);
455
+ const tinyBase = tinycolor(baseHex);
456
+ it('tracks complement and triad variants within rounding tolerance', () => {
457
+ expectHexArraysClose(getHexesFromColors(baseColor.getComplementaryColors()), [
458
+ baseHex,
459
+ tinyBase.complement().toHexString(),
460
+ ]);
461
+ expectHexArraysClose(getHexesFromColors(baseColor.getTriadicHarmonyColors()), getHexesFromTinyColors(tinyBase.triad()));
462
+ });
463
+ it('keeps analogous spreads evenly spaced instead of tinycolor’s tighter slices', () => {
464
+ const analogousHexes = getHexesFromColors(baseColor.getAnalogousHarmonyColors());
465
+ const tinyAnalogousHexes = getHexesFromTinyColors(tinyBase.analogous(5));
466
+ getHueSteps(analogousHexes).forEach((step) => {
467
+ expect(Math.abs(step - 30)).toBeLessThanOrEqual(1);
468
+ });
469
+ getHueSteps(tinyAnalogousHexes).forEach((step) => {
470
+ expect(step).toBeLessThanOrEqual(15);
471
+ });
472
+ expect(sortHexesByHue(analogousHexes)).not.toEqual(sortHexesByHue(tinyAnalogousHexes));
473
+ });
474
+ it('avoids tinycolor monochromatic wrap-around that drops to near-black', () => {
475
+ const monochromaticHexes = getHexesFromColors(baseColor.getMonochromaticHarmonyColors());
476
+ const tinyMonochromaticHexes = getHexesFromTinyColors(tinyBase.monochromatic(5));
477
+ const baseLightness = tinyBase.toHsl().l ?? 0;
478
+ const omniLightnesses = getLightnesses(monochromaticHexes);
479
+ const tinyLightnesses = getLightnesses(tinyMonochromaticHexes);
480
+ expect(Math.min(...omniLightnesses)).toBeGreaterThan(baseLightness - 0.25);
481
+ expect(Math.min(...tinyLightnesses)).toBeLessThan(Math.min(...omniLightnesses));
482
+ // Tinycolor cycles lightness upward after wrapping through black, while omni-color
483
+ // intentionally keeps the series centered around the base lightness without wrap-around.
484
+ expect(Math.max(...tinyLightnesses)).toBeCloseTo(baseLightness, 3);
485
+ expect(sortHexesByHue(monochromaticHexes)).not.toEqual(sortHexesByHue(tinyMonochromaticHexes));
486
+ });
487
+ it('retains rectangular tetrad spacing instead of tinycolor’s square polyad', () => {
488
+ const tetradHexes = getHexesFromColors(baseColor.getTetradicHarmonyColors());
489
+ const tinyTetradHexes = getHexesFromTinyColors(tinyBase.tetrad());
490
+ const tetradSteps = getHueSteps(tetradHexes);
491
+ expect(tetradSteps[0]).toBeCloseTo(60, 0);
492
+ expect(tetradSteps[1]).toBeCloseTo(120, 0);
493
+ expect(tetradSteps[2]).toBeCloseTo(60, 0);
494
+ getHueSteps(tinyTetradHexes).forEach((step) => {
495
+ expect(step).toBeCloseTo(90, 0);
496
+ });
497
+ });
498
+ });
499
+ });
@@ -0,0 +1 @@
1
+ export {};