chroma-panel 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 (202) hide show
  1. package/LICENSE +44 -0
  2. package/README.md +119 -0
  3. package/dist/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/a11y/contrast.cjs +107 -0
  5. package/dist/a11y/contrast.d.cts +34 -0
  6. package/dist/a11y/contrast.d.ts +34 -0
  7. package/dist/a11y/contrast.js +101 -0
  8. package/dist/color/convert.cjs +162 -0
  9. package/dist/color/convert.d.cts +16 -0
  10. package/dist/color/convert.d.ts +16 -0
  11. package/dist/color/convert.js +153 -0
  12. package/dist/color/parse.cjs +153 -0
  13. package/dist/color/parse.d.cts +7 -0
  14. package/dist/color/parse.d.ts +7 -0
  15. package/dist/color/parse.js +151 -0
  16. package/dist/color/serialize.cjs +79 -0
  17. package/dist/color/serialize.d.cts +11 -0
  18. package/dist/color/serialize.d.ts +11 -0
  19. package/dist/color/serialize.js +73 -0
  20. package/dist/color/sticky.cjs +20 -0
  21. package/dist/color/sticky.d.cts +6 -0
  22. package/dist/color/sticky.d.ts +6 -0
  23. package/dist/color/sticky.js +19 -0
  24. package/dist/color/types.d.cts +43 -0
  25. package/dist/color/types.d.ts +43 -0
  26. package/dist/components/ChromaPanel.cjs +254 -0
  27. package/dist/components/ChromaPanel.d.cts +47 -0
  28. package/dist/components/ChromaPanel.d.ts +47 -0
  29. package/dist/components/ChromaPanel.js +251 -0
  30. package/dist/components/ColorInput.cjs +128 -0
  31. package/dist/components/ColorInput.d.cts +19 -0
  32. package/dist/components/ColorInput.d.ts +19 -0
  33. package/dist/components/ColorInput.js +127 -0
  34. package/dist/components/ModeToolbar.cjs +33 -0
  35. package/dist/components/ModeToolbar.d.cts +10 -0
  36. package/dist/components/ModeToolbar.d.ts +10 -0
  37. package/dist/components/ModeToolbar.js +32 -0
  38. package/dist/components/PanelFooter.cjs +82 -0
  39. package/dist/components/PanelFooter.d.cts +5 -0
  40. package/dist/components/PanelFooter.d.ts +5 -0
  41. package/dist/components/PanelFooter.js +80 -0
  42. package/dist/components/Popover.cjs +154 -0
  43. package/dist/components/Popover.d.cts +13 -0
  44. package/dist/components/Popover.d.ts +13 -0
  45. package/dist/components/Popover.js +153 -0
  46. package/dist/contrast.cjs +10 -0
  47. package/dist/contrast.d.cts +2 -0
  48. package/dist/contrast.d.ts +2 -0
  49. package/dist/contrast.js +2 -0
  50. package/dist/core/context.cjs +30 -0
  51. package/dist/core/context.d.cts +43 -0
  52. package/dist/core/context.d.ts +43 -0
  53. package/dist/core/context.js +26 -0
  54. package/dist/core/dev.cjs +11 -0
  55. package/dist/core/dev.js +11 -0
  56. package/dist/core/store.cjs +43 -0
  57. package/dist/core/store.d.cts +17 -0
  58. package/dist/core/store.d.ts +17 -0
  59. package/dist/core/store.js +44 -0
  60. package/dist/core/styleInjector.cjs +38 -0
  61. package/dist/core/styleInjector.d.cts +4 -0
  62. package/dist/core/styleInjector.d.ts +4 -0
  63. package/dist/core/styleInjector.js +38 -0
  64. package/dist/core/useColorStore.cjs +43 -0
  65. package/dist/core/useColorStore.d.cts +6 -0
  66. package/dist/core/useColorStore.d.ts +6 -0
  67. package/dist/core/useColorStore.js +41 -0
  68. package/dist/core/useFormControl.cjs +87 -0
  69. package/dist/core/useFormControl.js +82 -0
  70. package/dist/core/useMediaQuery.cjs +48 -0
  71. package/dist/core/useMediaQuery.js +46 -0
  72. package/dist/core/usePointerDrag.cjs +177 -0
  73. package/dist/core/usePointerDrag.d.cts +35 -0
  74. package/dist/core/usePointerDrag.d.ts +35 -0
  75. package/dist/core/usePointerDrag.js +175 -0
  76. package/dist/core/useScrollFade.cjs +44 -0
  77. package/dist/core/useScrollFade.js +43 -0
  78. package/dist/core.cjs +33 -0
  79. package/dist/core.d.cts +7 -0
  80. package/dist/core.d.ts +7 -0
  81. package/dist/core.js +6 -0
  82. package/dist/data/defaults.cjs +123 -0
  83. package/dist/data/defaults.d.cts +6 -0
  84. package/dist/data/defaults.d.ts +6 -0
  85. package/dist/data/defaults.js +122 -0
  86. package/dist/data/namedColors.cjs +11 -0
  87. package/dist/data/namedColors.d.cts +3 -0
  88. package/dist/data/namedColors.d.ts +3 -0
  89. package/dist/data/namedColors.js +12 -0
  90. package/dist/image/extract.cjs +98 -0
  91. package/dist/image/extract.d.cts +16 -0
  92. package/dist/image/extract.d.ts +16 -0
  93. package/dist/image/extract.js +99 -0
  94. package/dist/image/mmcq.cjs +182 -0
  95. package/dist/image/mmcq.d.cts +8 -0
  96. package/dist/image/mmcq.d.ts +8 -0
  97. package/dist/image/mmcq.js +183 -0
  98. package/dist/image-worker.js +202 -0
  99. package/dist/image.cjs +12 -0
  100. package/dist/image.d.cts +4 -0
  101. package/dist/image.d.ts +4 -0
  102. package/dist/image.js +9 -0
  103. package/dist/index.cjs +119 -0
  104. package/dist/index.d.cts +36 -0
  105. package/dist/index.d.ts +36 -0
  106. package/dist/index.js +44 -0
  107. package/dist/modes/gradients.cjs +48 -0
  108. package/dist/modes/gradients.js +43 -0
  109. package/dist/modes/image/index.cjs +193 -0
  110. package/dist/modes/image/index.d.cts +10 -0
  111. package/dist/modes/image/index.d.ts +10 -0
  112. package/dist/modes/image/index.js +191 -0
  113. package/dist/modes/palettes/index.cjs +76 -0
  114. package/dist/modes/palettes/index.d.cts +6 -0
  115. package/dist/modes/palettes/index.d.ts +6 -0
  116. package/dist/modes/palettes/index.js +74 -0
  117. package/dist/modes/pencils/index.cjs +36 -0
  118. package/dist/modes/pencils/index.d.cts +6 -0
  119. package/dist/modes/pencils/index.d.ts +6 -0
  120. package/dist/modes/pencils/index.js +34 -0
  121. package/dist/modes/registry.cjs +26 -0
  122. package/dist/modes/registry.d.cts +13 -0
  123. package/dist/modes/registry.d.ts +13 -0
  124. package/dist/modes/registry.js +25 -0
  125. package/dist/modes/sliders/index.cjs +300 -0
  126. package/dist/modes/sliders/index.d.cts +6 -0
  127. package/dist/modes/sliders/index.d.ts +6 -0
  128. package/dist/modes/sliders/index.js +298 -0
  129. package/dist/modes/wheel/index.cjs +62 -0
  130. package/dist/modes/wheel/index.d.cts +6 -0
  131. package/dist/modes/wheel/index.d.ts +6 -0
  132. package/dist/modes/wheel/index.js +60 -0
  133. package/dist/named-colors.cjs +3 -0
  134. package/dist/named-colors.d.cts +2 -0
  135. package/dist/named-colors.d.ts +2 -0
  136. package/dist/named-colors.js +2 -0
  137. package/dist/palettes.cjs +8 -0
  138. package/dist/palettes.d.cts +2 -0
  139. package/dist/palettes.d.ts +2 -0
  140. package/dist/palettes.js +7 -0
  141. package/dist/panel.cjs +80 -0
  142. package/dist/panel.d.cts +27 -0
  143. package/dist/panel.d.ts +27 -0
  144. package/dist/panel.js +27 -0
  145. package/dist/pencils.cjs +8 -0
  146. package/dist/pencils.d.cts +2 -0
  147. package/dist/pencils.d.ts +2 -0
  148. package/dist/pencils.js +7 -0
  149. package/dist/primitives/AxisInput.cjs +26 -0
  150. package/dist/primitives/AxisInput.js +25 -0
  151. package/dist/primitives/ChannelSlider.cjs +94 -0
  152. package/dist/primitives/ChannelSlider.d.cts +18 -0
  153. package/dist/primitives/ChannelSlider.d.ts +18 -0
  154. package/dist/primitives/ChannelSlider.js +93 -0
  155. package/dist/primitives/ColorArea.cjs +85 -0
  156. package/dist/primitives/ColorArea.d.cts +8 -0
  157. package/dist/primitives/ColorArea.d.ts +8 -0
  158. package/dist/primitives/ColorArea.js +84 -0
  159. package/dist/primitives/ColorDisc.cjs +95 -0
  160. package/dist/primitives/ColorDisc.d.cts +8 -0
  161. package/dist/primitives/ColorDisc.d.ts +8 -0
  162. package/dist/primitives/ColorDisc.js +94 -0
  163. package/dist/primitives/ColorField.cjs +68 -0
  164. package/dist/primitives/ColorField.d.cts +9 -0
  165. package/dist/primitives/ColorField.d.ts +9 -0
  166. package/dist/primitives/ColorField.js +67 -0
  167. package/dist/primitives/NumberField.cjs +65 -0
  168. package/dist/primitives/NumberField.d.cts +14 -0
  169. package/dist/primitives/NumberField.d.ts +14 -0
  170. package/dist/primitives/NumberField.js +64 -0
  171. package/dist/primitives/SegmentedControl.cjs +66 -0
  172. package/dist/primitives/SegmentedControl.d.cts +21 -0
  173. package/dist/primitives/SegmentedControl.d.ts +21 -0
  174. package/dist/primitives/SegmentedControl.js +65 -0
  175. package/dist/primitives/Swatch.cjs +22 -0
  176. package/dist/primitives/Swatch.d.cts +13 -0
  177. package/dist/primitives/Swatch.d.ts +13 -0
  178. package/dist/primitives/Swatch.js +21 -0
  179. package/dist/primitives/SwatchGrid.cjs +63 -0
  180. package/dist/primitives/SwatchGrid.d.cts +17 -0
  181. package/dist/primitives/SwatchGrid.d.ts +17 -0
  182. package/dist/primitives/SwatchGrid.js +62 -0
  183. package/dist/primitives/icons.cjs +149 -0
  184. package/dist/primitives/icons.d.cts +9 -0
  185. package/dist/primitives/icons.d.ts +9 -0
  186. package/dist/primitives/icons.js +141 -0
  187. package/dist/primitives/useEyedropper.cjs +30 -0
  188. package/dist/primitives/useEyedropper.d.cts +7 -0
  189. package/dist/primitives/useEyedropper.d.ts +7 -0
  190. package/dist/primitives/useEyedropper.js +29 -0
  191. package/dist/sliders.cjs +8 -0
  192. package/dist/sliders.d.cts +2 -0
  193. package/dist/sliders.d.ts +2 -0
  194. package/dist/sliders.js +7 -0
  195. package/dist/style.css +2 -0
  196. package/dist/styles/css.cjs +4 -0
  197. package/dist/styles/css.js +5 -0
  198. package/dist/wheel.cjs +8 -0
  199. package/dist/wheel.d.cts +2 -0
  200. package/dist/wheel.d.ts +2 -0
  201. package/dist/wheel.js +7 -0
  202. package/package.json +222 -0
package/LICENSE ADDED
@@ -0,0 +1,44 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Sohail Khan (resohail.me)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ --------------------------------------------------------------------------------
24
+
25
+ This package inlines SVG path data from Lucide (https://lucide.dev), which is
26
+ distributed under the ISC License and is reproduced below as that license
27
+ requires. Lucide is not a dependency of this package; only the path data for a
28
+ handful of icons is included.
29
+
30
+ ISC License
31
+
32
+ Copyright (c) 2026 Lucide Icons and Contributors
33
+
34
+ Permission to use, copy, modify, and/or distribute this software for any
35
+ purpose with or without fee is hereby granted, provided that the above
36
+ copyright notice and this permission notice appear in all copies.
37
+
38
+ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
39
+ REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
40
+ AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
41
+ INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
42
+ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
43
+ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
44
+ PERFORMANCE OF THIS SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,119 @@
1
+ ![chroma-panel, a React color picker](https://raw.githubusercontent.com/re-sohail/chroma-panel/main/assets/hero.png)
2
+
3
+ [![npm](https://img.shields.io/npm/v/chroma-panel?color=2d7ff9)](https://www.npmjs.com/package/chroma-panel)
4
+ [![gzipped](https://img.shields.io/bundlephobia/minzip/chroma-panel?color=2d7ff9&label=gzipped)](https://bundlephobia.com/package/chroma-panel)
5
+ [![downloads](https://img.shields.io/npm/dw/chroma-panel?color=2d7ff9)](https://www.npmjs.com/package/chroma-panel)
6
+ [![licence](https://img.shields.io/npm/l/chroma-panel?color=2d7ff9)](./LICENSE)
7
+
8
+ # chroma-panel
9
+
10
+ A React color picker with the look and feel of the macOS color panel.
11
+
12
+ - Five ways to pick: wheel, sliders, palettes, a 120-color pencil grid, and sampling from an image
13
+ - An eyedropper for grabbing a color from anywhere on screen, where the browser supports it
14
+ - Drops into a form like an `<input>`, with `name`, `required` and `form.reset()`
15
+ - No runtime dependencies, TypeScript types included, ESM and CommonJS
16
+
17
+ **[API reference](docs/api.md)** · **[Guide](docs/guides.md)**
18
+
19
+ ## Install
20
+
21
+ ```bash
22
+ npm install chroma-panel
23
+ ```
24
+
25
+ ## Use it
26
+
27
+ ```tsx
28
+ import { useState } from 'react';
29
+ import { ColorInput } from 'chroma-panel';
30
+
31
+ export function Example() {
32
+ const [color, setColor] = useState('#3366cc');
33
+
34
+ return (
35
+ <ColorInput
36
+ value={color}
37
+ onChange={(c) => setColor(c.hex)}
38
+ onChangeComplete={(c) => save(c.hex)}
39
+ />
40
+ );
41
+ }
42
+ ```
43
+
44
+ No CSS import and no provider. `ColorInput` renders a swatch button that opens the panel in a popover.
45
+
46
+ `onChange` fires continuously while you drag, `onChangeComplete` once when you let go. [Which to use](docs/api.md#onchange-vs-onchangecomplete).
47
+
48
+ ## Inline panel
49
+
50
+ `ChromaPanel` is the same panel without the popover, for when you want it on the page:
51
+
52
+ ```tsx
53
+ import { ChromaPanel } from 'chroma-panel';
54
+
55
+ <ChromaPanel defaultValue="#3366cc" modes={['wheel']} showTitleBar={false} />
56
+ ```
57
+
58
+ ## Smaller bundle
59
+
60
+ Importing `chroma-panel` registers all five modes. If you only need one or two, import the shell and add them yourself:
61
+
62
+ ```tsx
63
+ import { ChromaPanel } from 'chroma-panel/panel';
64
+ import 'chroma-panel/wheel';
65
+
66
+ <ChromaPanel modes={['wheel']} />
67
+ ```
68
+
69
+ That is 14.1 kB instead of 22.2 kB. Every mode has its own entry point — see [entry points](docs/api.md#entry-points).
70
+
71
+ ## Theming
72
+
73
+ ![The chroma-panel color picker in light and dark themes](https://raw.githubusercontent.com/re-sohail/chroma-panel/main/assets/themes.png)
74
+
75
+ The panel follows the system color scheme. To change how it looks, override the CSS variables or pass your own class per part:
76
+
77
+ ```tsx
78
+ <ColorInput classNames={{ root: 'shadow-2xl', trigger: 'h-8 w-12' }} />
79
+ ```
80
+
81
+ Full details in the [theming guide](docs/guides.md#theming).
82
+
83
+ ## Why the color survives a round trip
84
+
85
+ HSV has two places where information disappears. At zero saturation there is no hue to speak of, and at zero brightness there is neither. Pickers that keep their state as RGB or hex hit this constantly: drag brightness to black and back up, and the hue you picked comes back as red.
86
+
87
+ This one keeps the full HSVA value and merges changes into it instead of replacing it, so dragging to an extreme and back returns the color you started with.
88
+
89
+ ## Size
90
+
91
+ Measured as the increase in a real Vite production build, gzipped, with React external.
92
+
93
+ | What you import | Added to your app |
94
+ | --- | --- |
95
+ | all five modes | 22.2 kB |
96
+ | shell plus one mode | 14.1 kB |
97
+
98
+ `dependencies` is empty. `react` and `react-dom` are peer dependencies, so the copy already in your app is the one that gets used.
99
+
100
+ ## Compatibility
101
+
102
+ | | |
103
+ | --- | --- |
104
+ | React | 16.14 and newer, including 19 |
105
+ | React DOM | Required. The popover renders through `createPortal` |
106
+ | Browsers | Chrome 123, Firefox 120, Safari 17.5 |
107
+ | TypeScript | Types included, no `@types` package needed |
108
+ | Modules | ESM and CommonJS |
109
+ | Server rendering | Safe. Browser-dependent files are marked `'use client'` |
110
+
111
+ The browser versions come from `light-dark()`, a CSS function the stylesheet relies on. [What happens on older browsers](docs/guides.md#browser-support).
112
+
113
+ ## Help
114
+
115
+ Questions and bug reports: [github.com/re-sohail/chroma-panel/issues](https://github.com/re-sohail/chroma-panel/issues)
116
+
117
+ ## Licence
118
+
119
+ MIT © [Sohail Khan](https://resohail.me)
@@ -0,0 +1,23 @@
1
+ //#region \0rolldown/runtime.js
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __copyProps = (to, from, except, desc) => {
9
+ if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
10
+ key = keys[i];
11
+ if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
12
+ get: ((k) => from[k]).bind(null, key),
13
+ enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
14
+ });
15
+ }
16
+ return to;
17
+ };
18
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule || !__hasOwnProp.call(mod, "default") ? __defProp(target, "default", {
19
+ value: mod,
20
+ enumerable: true
21
+ }) : target, mod));
22
+ //#endregion
23
+ exports.__toESM = __toESM;
@@ -0,0 +1,107 @@
1
+ const require_dev = require("../core/dev.cjs");
2
+ const require_convert = require("../color/convert.cjs");
3
+ const require_parse = require("../color/parse.cjs");
4
+ //#region src/a11y/contrast.ts
5
+ function toRgb(input) {
6
+ if (typeof input !== "string") return require_convert.hsvaToRgba(input);
7
+ const parsed = require_parse.parse(input);
8
+ return parsed === null ? null : require_convert.hsvaToRgba(parsed);
9
+ }
10
+ function linearise(channel) {
11
+ const c = channel / 255;
12
+ return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
13
+ }
14
+ function relativeLuminance(color) {
15
+ const rgb = toRgb(color);
16
+ if (rgb === null) return 0;
17
+ return .2126 * linearise(rgb.r) + .7152 * linearise(rgb.g) + .0722 * linearise(rgb.b);
18
+ }
19
+ function contrastRatio(a, b) {
20
+ const la = relativeLuminance(a);
21
+ const lb = relativeLuminance(b);
22
+ const light = Math.max(la, lb);
23
+ const dark = Math.min(la, lb);
24
+ return (light + .05) / (dark + .05);
25
+ }
26
+ function meetsContrast(a, b, options) {
27
+ const { level, size = "normal" } = options;
28
+ const ratio = contrastRatio(a, b);
29
+ if (level === "AAA") return ratio >= (size === "large" ? 4.5 : 7);
30
+ return ratio >= (size === "large" ? 3 : 4.5);
31
+ }
32
+ function meetsNonTextContrast(a, b) {
33
+ return contrastRatio(a, b) >= 3;
34
+ }
35
+ function contrastReport(a, b) {
36
+ const ratio = contrastRatio(a, b);
37
+ return {
38
+ ratio,
39
+ text: {
40
+ normal: {
41
+ aa: ratio >= 4.5,
42
+ aaa: ratio >= 7
43
+ },
44
+ large: {
45
+ aa: ratio >= 3,
46
+ aaa: ratio >= 4.5
47
+ }
48
+ },
49
+ nonText: ratio >= 3
50
+ };
51
+ }
52
+ function wcagLevel(a, b) {
53
+ require_dev.warnOnce("wcagLevel() is deprecated and cannot be accurate — a ratio alone does not determine a level. Use meetsContrast(a, b, { level, size }) instead.");
54
+ const ratio = contrastRatio(a, b);
55
+ if (ratio >= 7) return "AAA";
56
+ if (ratio >= 4.5) return "AA";
57
+ if (ratio >= 3) return "AA Large";
58
+ return "Fail";
59
+ }
60
+ const S_RCO = .2126729;
61
+ const S_GCO = .7151522;
62
+ const S_BCO = .072175;
63
+ const MAIN_TRC = 2.4;
64
+ const NORM_BG = .56;
65
+ const NORM_TXT = .57;
66
+ const REV_TXT = .62;
67
+ const REV_BG = .65;
68
+ const SCALE_BOW = 1.14;
69
+ const SCALE_WOB = 1.14;
70
+ const LO_BOW_OFFSET = .027;
71
+ const LO_WOB_OFFSET = .027;
72
+ const BLK_THRS = .022;
73
+ const BLK_CLMP = 1.414;
74
+ const DELTA_Y_MIN = 5e-4;
75
+ const LO_CLIP = .1;
76
+ function apcaY(rgb) {
77
+ const y = S_RCO * (rgb.r / 255) ** MAIN_TRC + S_GCO * (rgb.g / 255) ** MAIN_TRC + S_BCO * (rgb.b / 255) ** MAIN_TRC;
78
+ return y > BLK_THRS ? y : y + (BLK_THRS - y) ** BLK_CLMP;
79
+ }
80
+ function apcaContrast(text, background) {
81
+ const t = toRgb(text);
82
+ const b = toRgb(background);
83
+ if (t === null || b === null) return 0;
84
+ const yText = apcaY(t);
85
+ const yBg = apcaY(b);
86
+ if (Math.abs(yBg - yText) < DELTA_Y_MIN) return 0;
87
+ if (yBg > yText) {
88
+ const s = (yBg ** NORM_BG - yText ** NORM_TXT) * SCALE_BOW;
89
+ return s < LO_CLIP ? 0 : (s - LO_BOW_OFFSET) * 100;
90
+ }
91
+ const s = (yBg ** REV_BG - yText ** REV_TXT) * SCALE_WOB;
92
+ return s > -.1 ? 0 : (s + LO_WOB_OFFSET) * 100;
93
+ }
94
+ function readableTextColor(background, options = {}) {
95
+ const dark = options.dark ?? "#000000";
96
+ const light = options.light ?? "#ffffff";
97
+ return Math.abs(apcaContrast(dark, background)) >= Math.abs(apcaContrast(light, background)) ? dark : light;
98
+ }
99
+ //#endregion
100
+ exports.apcaContrast = apcaContrast;
101
+ exports.contrastRatio = contrastRatio;
102
+ exports.contrastReport = contrastReport;
103
+ exports.meetsContrast = meetsContrast;
104
+ exports.meetsNonTextContrast = meetsNonTextContrast;
105
+ exports.readableTextColor = readableTextColor;
106
+ exports.relativeLuminance = relativeLuminance;
107
+ exports.wcagLevel = wcagLevel;
@@ -0,0 +1,34 @@
1
+ import { Hsva } from "../color/types.cjs";
2
+ //#region src/a11y/contrast.d.ts
3
+ export declare function relativeLuminance(color: string | Hsva): number;
4
+ export declare function contrastRatio(a: string | Hsva, b: string | Hsva): number;
5
+ export type TextSize = "normal" | "large";
6
+ export interface ContrastOptions {
7
+ level: "AA" | "AAA";
8
+ size?: TextSize;
9
+ }
10
+ export declare function meetsContrast(a: string | Hsva, b: string | Hsva, options: ContrastOptions): boolean;
11
+ export declare function meetsNonTextContrast(a: string | Hsva, b: string | Hsva): boolean;
12
+ export interface ContrastReport {
13
+ ratio: number;
14
+ text: {
15
+ normal: {
16
+ aa: boolean;
17
+ aaa: boolean;
18
+ };
19
+ large: {
20
+ aa: boolean;
21
+ aaa: boolean;
22
+ };
23
+ };
24
+ nonText: boolean;
25
+ }
26
+ export declare function contrastReport(a: string | Hsva, b: string | Hsva): ContrastReport;
27
+ export type WcagLevel = "AAA" | "AA" | "AA Large" | "Fail";
28
+ export declare function wcagLevel(a: string | Hsva, b: string | Hsva): WcagLevel;
29
+ export declare function apcaContrast(text: string | Hsva, background: string | Hsva): number;
30
+ export declare function readableTextColor(background: string | Hsva, options?: {
31
+ dark?: string;
32
+ light?: string;
33
+ }): string;
34
+ //#endregion
@@ -0,0 +1,34 @@
1
+ import { Hsva } from "../color/types.js";
2
+ //#region src/a11y/contrast.d.ts
3
+ export declare function relativeLuminance(color: string | Hsva): number;
4
+ export declare function contrastRatio(a: string | Hsva, b: string | Hsva): number;
5
+ export type TextSize = "normal" | "large";
6
+ export interface ContrastOptions {
7
+ level: "AA" | "AAA";
8
+ size?: TextSize;
9
+ }
10
+ export declare function meetsContrast(a: string | Hsva, b: string | Hsva, options: ContrastOptions): boolean;
11
+ export declare function meetsNonTextContrast(a: string | Hsva, b: string | Hsva): boolean;
12
+ export interface ContrastReport {
13
+ ratio: number;
14
+ text: {
15
+ normal: {
16
+ aa: boolean;
17
+ aaa: boolean;
18
+ };
19
+ large: {
20
+ aa: boolean;
21
+ aaa: boolean;
22
+ };
23
+ };
24
+ nonText: boolean;
25
+ }
26
+ export declare function contrastReport(a: string | Hsva, b: string | Hsva): ContrastReport;
27
+ export type WcagLevel = "AAA" | "AA" | "AA Large" | "Fail";
28
+ export declare function wcagLevel(a: string | Hsva, b: string | Hsva): WcagLevel;
29
+ export declare function apcaContrast(text: string | Hsva, background: string | Hsva): number;
30
+ export declare function readableTextColor(background: string | Hsva, options?: {
31
+ dark?: string;
32
+ light?: string;
33
+ }): string;
34
+ //#endregion
@@ -0,0 +1,101 @@
1
+ import { warnOnce } from "../core/dev.js";
2
+ import { hsvaToRgba } from "../color/convert.js";
3
+ import { parse } from "../color/parse.js";
4
+ //#region src/a11y/contrast.ts
5
+ function toRgb(input) {
6
+ if (typeof input !== "string") return hsvaToRgba(input);
7
+ const parsed = parse(input);
8
+ return parsed === null ? null : hsvaToRgba(parsed);
9
+ }
10
+ function linearise(channel) {
11
+ const c = channel / 255;
12
+ return c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
13
+ }
14
+ function relativeLuminance(color) {
15
+ const rgb = toRgb(color);
16
+ if (rgb === null) return 0;
17
+ return .2126 * linearise(rgb.r) + .7152 * linearise(rgb.g) + .0722 * linearise(rgb.b);
18
+ }
19
+ function contrastRatio(a, b) {
20
+ const la = relativeLuminance(a);
21
+ const lb = relativeLuminance(b);
22
+ const light = Math.max(la, lb);
23
+ const dark = Math.min(la, lb);
24
+ return (light + .05) / (dark + .05);
25
+ }
26
+ function meetsContrast(a, b, options) {
27
+ const { level, size = "normal" } = options;
28
+ const ratio = contrastRatio(a, b);
29
+ if (level === "AAA") return ratio >= (size === "large" ? 4.5 : 7);
30
+ return ratio >= (size === "large" ? 3 : 4.5);
31
+ }
32
+ function meetsNonTextContrast(a, b) {
33
+ return contrastRatio(a, b) >= 3;
34
+ }
35
+ function contrastReport(a, b) {
36
+ const ratio = contrastRatio(a, b);
37
+ return {
38
+ ratio,
39
+ text: {
40
+ normal: {
41
+ aa: ratio >= 4.5,
42
+ aaa: ratio >= 7
43
+ },
44
+ large: {
45
+ aa: ratio >= 3,
46
+ aaa: ratio >= 4.5
47
+ }
48
+ },
49
+ nonText: ratio >= 3
50
+ };
51
+ }
52
+ function wcagLevel(a, b) {
53
+ warnOnce("wcagLevel() is deprecated and cannot be accurate — a ratio alone does not determine a level. Use meetsContrast(a, b, { level, size }) instead.");
54
+ const ratio = contrastRatio(a, b);
55
+ if (ratio >= 7) return "AAA";
56
+ if (ratio >= 4.5) return "AA";
57
+ if (ratio >= 3) return "AA Large";
58
+ return "Fail";
59
+ }
60
+ const S_RCO = .2126729;
61
+ const S_GCO = .7151522;
62
+ const S_BCO = .072175;
63
+ const MAIN_TRC = 2.4;
64
+ const NORM_BG = .56;
65
+ const NORM_TXT = .57;
66
+ const REV_TXT = .62;
67
+ const REV_BG = .65;
68
+ const SCALE_BOW = 1.14;
69
+ const SCALE_WOB = 1.14;
70
+ const LO_BOW_OFFSET = .027;
71
+ const LO_WOB_OFFSET = .027;
72
+ const BLK_THRS = .022;
73
+ const BLK_CLMP = 1.414;
74
+ const DELTA_Y_MIN = 5e-4;
75
+ const LO_CLIP = .1;
76
+ function apcaY(rgb) {
77
+ const y = S_RCO * (rgb.r / 255) ** MAIN_TRC + S_GCO * (rgb.g / 255) ** MAIN_TRC + S_BCO * (rgb.b / 255) ** MAIN_TRC;
78
+ return y > BLK_THRS ? y : y + (BLK_THRS - y) ** BLK_CLMP;
79
+ }
80
+ function apcaContrast(text, background) {
81
+ const t = toRgb(text);
82
+ const b = toRgb(background);
83
+ if (t === null || b === null) return 0;
84
+ const yText = apcaY(t);
85
+ const yBg = apcaY(b);
86
+ if (Math.abs(yBg - yText) < DELTA_Y_MIN) return 0;
87
+ if (yBg > yText) {
88
+ const s = (yBg ** NORM_BG - yText ** NORM_TXT) * SCALE_BOW;
89
+ return s < LO_CLIP ? 0 : (s - LO_BOW_OFFSET) * 100;
90
+ }
91
+ const s = (yBg ** REV_BG - yText ** REV_TXT) * SCALE_WOB;
92
+ return s > -.1 ? 0 : (s + LO_WOB_OFFSET) * 100;
93
+ }
94
+ function readableTextColor(background, options = {}) {
95
+ const dark = options.dark ?? "#000000";
96
+ const light = options.light ?? "#ffffff";
97
+ return Math.abs(apcaContrast(dark, background)) >= Math.abs(apcaContrast(light, background)) ? dark : light;
98
+ }
99
+ //#endregion
100
+ export { apcaContrast, contrastRatio, contrastReport, meetsContrast, meetsNonTextContrast, readableTextColor, relativeLuminance, wcagLevel };
101
+
@@ -0,0 +1,162 @@
1
+ //#region src/color/convert.ts
2
+ function clamp(value, min, max) {
3
+ return value < min ? min : value > max ? max : value;
4
+ }
5
+ function normalizeHue(h) {
6
+ const r = h % 360;
7
+ return r < 0 ? r + 360 : r;
8
+ }
9
+ function rgbaToHsva(rgba) {
10
+ const { r, g, b, a } = rgba;
11
+ const max = Math.max(r, g, b);
12
+ const d = max - Math.min(r, g, b);
13
+ let hh;
14
+ if (d === 0) hh = 0;
15
+ else if (max === r) hh = (g - b) / d;
16
+ else if (max === g) hh = 2 + (b - r) / d;
17
+ else hh = 4 + (r - g) / d;
18
+ return {
19
+ h: 60 * (hh < 0 ? hh + 6 : hh),
20
+ s: max === 0 ? 0 : d / max * 100,
21
+ v: max / 255 * 100,
22
+ a
23
+ };
24
+ }
25
+ function hsvaToRgba(hsva) {
26
+ const hh = normalizeHue(hsva.h) / 360 * 6;
27
+ const i = Math.floor(hh);
28
+ const f = hh - i;
29
+ const s = hsva.s;
30
+ const v = hsva.v;
31
+ const p = v * (100 - s) / 100;
32
+ const q = v * (100 - f * s) / 100;
33
+ const t = v * (100 - (1 - f) * s) / 100;
34
+ let r;
35
+ let g;
36
+ let b;
37
+ switch (i % 6) {
38
+ case 0:
39
+ r = v;
40
+ g = t;
41
+ b = p;
42
+ break;
43
+ case 1:
44
+ r = q;
45
+ g = v;
46
+ b = p;
47
+ break;
48
+ case 2:
49
+ r = p;
50
+ g = v;
51
+ b = t;
52
+ break;
53
+ case 3:
54
+ r = p;
55
+ g = q;
56
+ b = v;
57
+ break;
58
+ case 4:
59
+ r = t;
60
+ g = p;
61
+ b = v;
62
+ break;
63
+ default:
64
+ r = v;
65
+ g = p;
66
+ b = q;
67
+ }
68
+ return {
69
+ r: r * 255 / 100,
70
+ g: g * 255 / 100,
71
+ b: b * 255 / 100,
72
+ a: hsva.a
73
+ };
74
+ }
75
+ function hsvaToHsla(hsva) {
76
+ const { h, s, v, a } = hsva;
77
+ const hh = (200 - s) * v / 100;
78
+ return {
79
+ h,
80
+ s: hh > 0 && hh < 200 ? s * v / 100 / (hh <= 100 ? hh : 200 - hh) * 100 : 0,
81
+ l: hh / 2,
82
+ a
83
+ };
84
+ }
85
+ function hslaToHsva(hsla) {
86
+ const { h, s, l, a } = hsla;
87
+ const ss = s * (l < 50 ? l : 100 - l) / 100;
88
+ return {
89
+ h,
90
+ s: ss > 0 ? 2 * ss / (l + ss) * 100 : 0,
91
+ v: l + ss,
92
+ a
93
+ };
94
+ }
95
+ function hwbaToHsva(hwba) {
96
+ const { h, a } = hwba;
97
+ let { w, b } = hwba;
98
+ const sum = w + b;
99
+ if (sum >= 100) {
100
+ const grey = w / sum * 100;
101
+ w = grey;
102
+ b = 100 - grey;
103
+ return {
104
+ h,
105
+ s: 0,
106
+ v: grey,
107
+ a
108
+ };
109
+ }
110
+ const v = 100 - b;
111
+ return {
112
+ h,
113
+ s: v === 0 ? 0 : (1 - w / v) * 100,
114
+ v,
115
+ a
116
+ };
117
+ }
118
+ function hsvaToHwba(hsva) {
119
+ const { h, s, v, a } = hsva;
120
+ return {
121
+ h,
122
+ w: (100 - s) * v / 100,
123
+ b: 100 - v,
124
+ a
125
+ };
126
+ }
127
+ function hsvaToRgb(hsva) {
128
+ const { r, g, b } = hsvaToRgba(hsva);
129
+ return {
130
+ r,
131
+ g,
132
+ b
133
+ };
134
+ }
135
+ function hsvaToHsl(hsva) {
136
+ const { h, s, l } = hsvaToHsla(hsva);
137
+ return {
138
+ h,
139
+ s,
140
+ l
141
+ };
142
+ }
143
+ function roundRgba(rgba) {
144
+ return {
145
+ r: Math.round(clamp(rgba.r, 0, 255)),
146
+ g: Math.round(clamp(rgba.g, 0, 255)),
147
+ b: Math.round(clamp(rgba.b, 0, 255)),
148
+ a: clamp(rgba.a, 0, 1)
149
+ };
150
+ }
151
+ //#endregion
152
+ exports.clamp = clamp;
153
+ exports.hslaToHsva = hslaToHsva;
154
+ exports.hsvaToHsl = hsvaToHsl;
155
+ exports.hsvaToHsla = hsvaToHsla;
156
+ exports.hsvaToHwba = hsvaToHwba;
157
+ exports.hsvaToRgb = hsvaToRgb;
158
+ exports.hsvaToRgba = hsvaToRgba;
159
+ exports.hwbaToHsva = hwbaToHsva;
160
+ exports.normalizeHue = normalizeHue;
161
+ exports.rgbaToHsva = rgbaToHsva;
162
+ exports.roundRgba = roundRgba;
@@ -0,0 +1,16 @@
1
+ import { Hsl, Hsla, Hsva, Hwba, Rgb, Rgba } from "./types.cjs";
2
+ //#region src/color/convert.d.ts
3
+ export declare function clamp(value: number, min: number, max: number): number;
4
+ export declare function normalizeHue(h: number): number;
5
+ export declare function rgbaToHsva(rgba: Rgba): Hsva;
6
+ export declare function hsvaToRgba(hsva: Hsva): Rgba;
7
+ export declare function hsvaToHsla(hsva: Hsva): Hsla;
8
+ export declare function hslaToHsva(hsla: Hsla & {
9
+ a: number;
10
+ }): Hsva;
11
+ export declare function hwbaToHsva(hwba: Hwba): Hsva;
12
+ export declare function hsvaToHwba(hsva: Hsva): Hwba;
13
+ export declare function hsvaToRgb(hsva: Hsva): Rgb;
14
+ export declare function hsvaToHsl(hsva: Hsva): Hsl;
15
+ export declare function roundRgba(rgba: Rgba): Rgba;
16
+ //#endregion
@@ -0,0 +1,16 @@
1
+ import { Hsl, Hsla, Hsva, Hwba, Rgb, Rgba } from "./types.js";
2
+ //#region src/color/convert.d.ts
3
+ export declare function clamp(value: number, min: number, max: number): number;
4
+ export declare function normalizeHue(h: number): number;
5
+ export declare function rgbaToHsva(rgba: Rgba): Hsva;
6
+ export declare function hsvaToRgba(hsva: Hsva): Rgba;
7
+ export declare function hsvaToHsla(hsva: Hsva): Hsla;
8
+ export declare function hslaToHsva(hsla: Hsla & {
9
+ a: number;
10
+ }): Hsva;
11
+ export declare function hwbaToHsva(hwba: Hwba): Hsva;
12
+ export declare function hsvaToHwba(hsva: Hsva): Hwba;
13
+ export declare function hsvaToRgb(hsva: Hsva): Rgb;
14
+ export declare function hsvaToHsl(hsva: Hsva): Hsl;
15
+ export declare function roundRgba(rgba: Rgba): Rgba;
16
+ //#endregion