chromonym 1.4.0 → 2.0.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 (106) hide show
  1. package/NOTICE.md +32 -4
  2. package/README.md +240 -91
  3. package/dist/conversions/hex.d.ts +1 -1
  4. package/dist/conversions/hex.d.ts.map +1 -1
  5. package/dist/conversions/hex.js +2 -2
  6. package/dist/conversions/hex.js.map +1 -1
  7. package/dist/conversions/hsl.d.ts +1 -1
  8. package/dist/conversions/hsl.d.ts.map +1 -1
  9. package/dist/conversions/hsl.js +2 -2
  10. package/dist/conversions/hsl.js.map +1 -1
  11. package/dist/conversions/hsv.d.ts +1 -1
  12. package/dist/conversions/hsv.d.ts.map +1 -1
  13. package/dist/conversions/hsv.js +2 -2
  14. package/dist/conversions/hsv.js.map +1 -1
  15. package/dist/conversions/index.d.ts +5 -5
  16. package/dist/conversions/index.d.ts.map +1 -1
  17. package/dist/conversions/index.js +5 -5
  18. package/dist/conversions/index.js.map +1 -1
  19. package/dist/conversions/pantone.d.ts +9 -7
  20. package/dist/conversions/pantone.d.ts.map +1 -1
  21. package/dist/conversions/pantone.js +11 -10
  22. package/dist/conversions/pantone.js.map +1 -1
  23. package/dist/conversions/rgb.d.ts +1 -1
  24. package/dist/conversions/rgb.d.ts.map +1 -1
  25. package/dist/conversions/rgb.js +1 -1
  26. package/dist/conversions/rgb.js.map +1 -1
  27. package/dist/convert.d.ts +45 -5
  28. package/dist/convert.d.ts.map +1 -1
  29. package/dist/convert.js +68 -17
  30. package/dist/convert.js.map +1 -1
  31. package/dist/detectFormat.d.ts +1 -1
  32. package/dist/detectFormat.d.ts.map +1 -1
  33. package/dist/detectFormat.js +2 -6
  34. package/dist/detectFormat.js.map +1 -1
  35. package/dist/identify.d.ts +13 -16
  36. package/dist/identify.d.ts.map +1 -1
  37. package/dist/identify.js +12 -12
  38. package/dist/identify.js.map +1 -1
  39. package/dist/index.d.ts +14 -14
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +13 -13
  42. package/dist/index.js.map +1 -1
  43. package/dist/indexing.d.ts +30 -24
  44. package/dist/indexing.d.ts.map +1 -1
  45. package/dist/indexing.js +49 -22
  46. package/dist/indexing.js.map +1 -1
  47. package/dist/math/clamp.d.ts.map +1 -1
  48. package/dist/math/clamp.js.map +1 -1
  49. package/dist/math/colorSpace.d.ts +1 -1
  50. package/dist/math/colorSpace.d.ts.map +1 -1
  51. package/dist/math/colorSpace.js.map +1 -1
  52. package/dist/math/deltaE.d.ts.map +1 -1
  53. package/dist/math/deltaE.js.map +1 -1
  54. package/dist/math/euclideanDistance.d.ts +1 -1
  55. package/dist/math/euclideanDistance.d.ts.map +1 -1
  56. package/dist/math/euclideanDistance.js.map +1 -1
  57. package/dist/math/hueSector.d.ts.map +1 -1
  58. package/dist/math/hueSector.js.map +1 -1
  59. package/dist/palettes/index.d.ts +4 -0
  60. package/dist/palettes/index.d.ts.map +1 -0
  61. package/dist/palettes/index.js +4 -0
  62. package/dist/palettes/index.js.map +1 -0
  63. package/dist/palettes/normalize.d.ts +17 -0
  64. package/dist/palettes/normalize.d.ts.map +1 -0
  65. package/dist/palettes/normalize.js +19 -0
  66. package/dist/palettes/normalize.js.map +1 -0
  67. package/dist/palettes/pantone.d.ts +1834 -0
  68. package/dist/palettes/pantone.d.ts.map +1 -0
  69. package/dist/palettes/pantone.js +925 -0
  70. package/dist/palettes/pantone.js.map +1 -0
  71. package/dist/palettes/web.d.ts +320 -0
  72. package/dist/palettes/web.d.ts.map +1 -0
  73. package/dist/{colorspaces → palettes}/web.js +18 -3
  74. package/dist/palettes/web.js.map +1 -0
  75. package/dist/palettes/x11.d.ts +1346 -0
  76. package/dist/palettes/x11.d.ts.map +1 -0
  77. package/dist/palettes/x11.js +686 -0
  78. package/dist/palettes/x11.js.map +1 -0
  79. package/dist/resolve.d.ts +9 -7
  80. package/dist/resolve.d.ts.map +1 -1
  81. package/dist/resolve.js +18 -17
  82. package/dist/resolve.js.map +1 -1
  83. package/dist/types.d.ts +10 -6
  84. package/dist/types.d.ts.map +1 -1
  85. package/dist/types.js +2 -1
  86. package/dist/types.js.map +1 -1
  87. package/package.json +32 -18
  88. package/dist/colorspaces/index.d.ts +0 -4
  89. package/dist/colorspaces/index.d.ts.map +0 -1
  90. package/dist/colorspaces/index.js +0 -4
  91. package/dist/colorspaces/index.js.map +0 -1
  92. package/dist/colorspaces/pantone.d.ts +0 -919
  93. package/dist/colorspaces/pantone.d.ts.map +0 -1
  94. package/dist/colorspaces/pantone.js +0 -918
  95. package/dist/colorspaces/pantone.js.map +0 -1
  96. package/dist/colorspaces/registry.d.ts +0 -26
  97. package/dist/colorspaces/registry.d.ts.map +0 -1
  98. package/dist/colorspaces/registry.js +0 -36
  99. package/dist/colorspaces/registry.js.map +0 -1
  100. package/dist/colorspaces/web.d.ts +0 -156
  101. package/dist/colorspaces/web.d.ts.map +0 -1
  102. package/dist/colorspaces/web.js.map +0 -1
  103. package/dist/colorspaces/x11.d.ts +0 -667
  104. package/dist/colorspaces/x11.d.ts.map +0 -1
  105. package/dist/colorspaces/x11.js +0 -666
  106. package/dist/colorspaces/x11.js.map +0 -1
package/NOTICE.md CHANGED
@@ -6,7 +6,7 @@
6
6
  **not affiliated with, endorsed by, licensed by, or certified by Pantone
7
7
  LLC**.
8
8
 
9
- The `pantone` colorspace in this library ships **community-derived sRGB
9
+ The `pantone` palette in this library ships **community-derived sRGB
10
10
  approximations** of the Pantone Coated (C) color set, sourced from
11
11
  [`color_library`](https://github.com/draganradu/color_library) (MIT © Radu
12
12
  Dragan). These values:
@@ -16,21 +16,49 @@ Dragan). These values:
16
16
  exactly.
17
17
  - Are intended for on-screen reference, nearest-name identification, and
18
18
  general tooling — **not for print color specification**.
19
- - Use standard color codes (e.g. `185C`) as **nominative references** to
19
+ - Use standard color codes (e.g. `185 C`) as **nominative references** to
20
20
  publicly identified Pantone colors. No endorsement or claim of accuracy
21
21
  is implied.
22
22
 
23
+ ### Accuracy envelope
24
+
25
+ The approximations are closest for mid-chroma colors under a D65 / 2°
26
+ observer. As a rough guide:
27
+
28
+ - **Mid-chroma Pantones**: ΔE*00 ≲ 5 versus Pantone's own sRGB previews.
29
+ Usable for nearest-name identification and comparative swatch UI.
30
+ - **Saturated edges**: ΔE*00 commonly 5–10, higher in the deep-blue and
31
+ blue-violet region (~2736 C through ~2746 C). `identify` will still
32
+ converge on a plausible neighbor, but the *which* can depend on metric
33
+ choice — see the "Distance metrics" section of the README.
34
+ - **Fluorescents (801 C – 814 C)** and the **metallic series (8xxx C)**
35
+ are defined outside the sRGB gamut. Their sRGB approximations are
36
+ perceptually wrong by construction — the physical inks can't be
37
+ reproduced on a standard monitor. Do not use these values as a
38
+ reference for brand-critical matching; identify carefully or skip.
39
+ - **Neons / warm reds** clipping against the sRGB gamut boundary (Pantone
40
+ Red 032 / Warm Red / Red 021 area) are approximated via hard clip.
41
+
42
+ ### What `identify` does *not* guarantee
43
+
44
+ `identify(sampledRgba, { palette: pantone })` returns the nearest
45
+ entry **in the shipped sRGB approximation set**. It does **not** imply
46
+ that the physical ink bearing that Pantone code would match your sampled
47
+ color in a print workflow, under a different light source, or on
48
+ different paper stock. Pantone is a spectral specification; any
49
+ color-library lookup that rounds it through sRGB loses information.
50
+
23
51
  If you need Pantone-certified color values, consult Pantone directly:
24
52
  <https://www.pantone.com/>.
25
53
 
26
54
  ## X11 / X.Org
27
55
 
28
- The `x11` colorspace is derived from the X.Org `rgb.txt` file, which is in
56
+ The `x11` palette is derived from the X.Org `rgb.txt` file, which is in
29
57
  the public domain.
30
58
 
31
59
  ## CSS / SVG named colors
32
60
 
33
- The `web` colorspace mirrors the CSS Color Module Level 4 named-color
61
+ The `web` palette mirrors the CSS Color Module Level 4 named-color
34
62
  set, which is a public specification.
35
63
 
36
64
  ## Upstream dependencies
package/README.md CHANGED
@@ -11,16 +11,16 @@
11
11
  # chromonym
12
12
 
13
13
  [![npm version](https://img.shields.io/npm/v/chromonym?color=cb3837&logo=npm)](https://www.npmjs.com/package/chromonym)
14
- [![publish size](https://badgen.net/packagephobia/publish/chromonym?color=green)](https://packagephobia.com/result?p=chromonym)
15
- [![install size](https://badgen.net/packagephobia/install/chromonym?color=green)](https://packagephobia.com/result?p=chromonym)
16
14
  [![Types: included](https://img.shields.io/npm/types/chromonym?color=3178c6&logo=typescript)](https://www.npmjs.com/package/chromonym)
17
15
  [![CI](https://github.com/simiancraft/chromonym/actions/workflows/ci.yml/badge.svg)](https://github.com/simiancraft/chromonym/actions/workflows/ci.yml)
18
- [![codecov](https://codecov.io/gh/simiancraft/chromonym/branch/main/graph/badge.svg)](https://codecov.io/gh/simiancraft/chromonym)
19
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
20
- [![semantic-release: conventional](https://img.shields.io/badge/semantic--release-conventional-e10079?logo=semantic-release)](https://github.com/semantic-release/semantic-release)
21
- [![Checked with Biome](https://img.shields.io/badge/Checked_with-Biome-60a5fa?style=flat&logo=biome)](https://biomejs.dev)
22
16
 
23
- **Tree-shakeable color naming for TypeScript.** Identify, resolve, and convert colors across CSS, X11, and Pantone — with 5 perceptual distance metrics. Add your own colorspaces and metrics as you need them.
17
+ <sub>[bundle size](https://packagephobia.com/result?p=chromonym) • [coverage](https://codecov.io/github/simiancraft/chromonym) • [MIT license](https://opensource.org/licenses/MIT)</sub>
18
+
19
+ <p align="center">
20
+ <code>identify</code> &nbsp;•&nbsp; <code>resolve</code> &nbsp;•&nbsp; <code>convert</code>
21
+ </p>
22
+
23
+ **The last color-naming library you'll ever need.** One API — `identify`, `resolve`, `convert` — works against any palette you throw at it. Ships CSS, X11, and Pantone out of the box; bring your own (brand colors, paint chips, game factions, chart themes) with full TypeScript inference and zero registration. Perceptual accuracy via six distance metrics. Tree-shakes to the bone — you only pay for palettes you import.
24
24
 
25
25
  <p align="center">
26
26
  <a href="https://simiancraft.github.io/chromonym/">
@@ -28,29 +28,51 @@
28
28
  </a>
29
29
  </p>
30
30
 
31
- Built as a *color-strategy machine*: the identification and resolution mechanism is the same across every colorspace, so plugging in a new set (RAL, HKS, NCS, or a custom brand palette) is one file. For color *manipulation* (mixing, scales, gamut mapping), reach for [`chroma-js`](https://gka.github.io/chroma.js/) or [`color.js`](https://colorjs.io/) — chromonym is the tool for *naming*.
32
-
33
- ```ts
34
- import { identify, resolve, convert } from 'chromonym';
35
-
36
- // What Pantone is this brand color closest to? (perceptually, via CIEDE2000)
37
- identify('#E20074', { colorspace: 'pantone' }) // '213C'
31
+ Because "it's sort of magenta-ish, maybe?" doesn't copy-paste into code — and naming a color is the same problem whether your palette is CSS, Pantone, your company's brand kit, or a homebrew set of faction colors. Chromonym treats every palette as a first-class `Palette<Name>` object and runs the same nearest-neighbor machinery over all of them.
38
32
 
39
- // Resolve any Pantone code back to RGB — prefix, spacing, case all fine
40
- resolve('Pantone 185 C', { colorspace: 'pantone' })// '#e4002b'
33
+ For color *manipulation* (mixing, scales, gamut mapping), reach for [`chroma-js`](https://gka.github.io/chroma.js/) or [`color.js`](https://colorjs.io/) — chromonym is the tool for *naming*.
41
34
 
42
- // Format conversion, format-detecting input
35
+ ```ts
36
+ import { identify, resolve, convert, pantone, type Palette } from 'chromonym';
37
+
38
+ // Your palette — defined inline, type-checked, no registration. Works the
39
+ // same as the built-in ones because that's the whole point.
40
+ const brand = {
41
+ name: 'acme-brand',
42
+ colors: { 'acme red': '#ff2a3b', 'acme ink': '#0a0f2c', 'acme mist': '#e6ecf5' },
43
+ normalize: (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, ''),
44
+ defaultMetric: 'deltaEok',
45
+ } as const satisfies Palette;
46
+
47
+ identify('#ff2b3c', { palette: brand }) // 'acme red'
48
+ resolve('Acme Ink', { palette: brand }) // '#0a0f2c'
49
+
50
+ // Built-in palettes follow the identical shape — CSS, X11, Pantone out of the box.
51
+ identify('#ff8080') // 'lightcoral' (web is the default)
52
+ identify('#663399') // 'rebeccapurple'
53
+ identify('#E20074', { palette: pantone }) // '213 C' — T-Mobile magenta
54
+ resolve('Pantone 185 C', { palette: pantone }) // '#e4002b'
55
+
56
+ // Format conversion: HEX / RGB / RGBA / HSL / HSV (palette-free, tree-shakes cleanly)
43
57
  convert('#ff0000', { format: 'HSL' }) // 'hsl(0, 100%, 50%)'
44
58
  convert({ h: 120, s: 100, l: 50 }, { format: 'HEX' }) // '#00ff00'
45
59
 
46
- // The classic: nearest CSS name
47
- identify('#ff8080') // 'lightcoral'
60
+ // Same convert API understands palette names both ways when you pass one:
61
+ // name in → any structural format out; color in → exact palette key out.
62
+ // `'NAME'` is an exact reverse lookup (throws on miss; use `identify` for fuzzy).
63
+ convert('185 C', { palette: pantone, format: 'HSL' }) // 'hsl(349, 100%, 45%)'
64
+ convert('#e4002b', { palette: pantone, format: 'NAME' }) // '185 C' (exact match)
65
+ convert('acme red', { palette: brand, format: 'RGB' }) // 'rgb(255, 42, 59)'
48
66
  ```
49
67
 
68
+ The identification machinery is palette-agnostic — RAL, HKS, NCS, Munsell, or a bespoke in-house set are the same amount of work (a small file each, or a single object literal). PRs welcome; meanwhile, BYO lets you ship today.
69
+
50
70
  ## Install
51
71
 
52
72
  ```sh
53
73
  bun add chromonym
74
+ pnpm add chromonym
75
+ yarn add chromonym
54
76
  npm install chromonym
55
77
  ```
56
78
 
@@ -60,52 +82,58 @@ Three primary functions. All accept an optional `opts` object; defaults fire whe
60
82
 
61
83
  ### `identify(input, opts?)`
62
84
 
63
- **Color → name.** Finds the nearest-match name in the chosen colorspace using the selected perceptual distance metric.
85
+ **Color → name.** Finds the nearest-match name in the chosen palette using the selected perceptual distance metric.
64
86
 
65
87
  ```ts
66
- function identify(
88
+ function identify<C extends Palette = typeof web>(
67
89
  input: ColorInput,
68
- opts?: { colorspace?: ColorspaceName; metric?: DistanceMetric },
69
- ): string | null
90
+ opts?: { palette?: C; metric?: DistanceMetric },
91
+ ): Extract<keyof C['colors'], string> | null
70
92
  ```
71
93
 
72
- - **Default colorspace**: `'web'`.
73
- - **Default metric**: picked per colorspace — `'deltaE76'` for web and x11 (well-separated palettes), `'deltaE2000'` for pantone (dense, perceptually-packed). Override freely via `metric`.
94
+ - **Default palette**: the built-in `web` palette (148 entries, small — included if you call `identify` without a palette).
95
+ - **Default metric**: read from the palette's own `defaultMetric` — `'deltaE76'` for web and x11 (well-separated palettes), `'deltaE2000'` for pantone (dense, perceptually-packed). Override freely via `metric`.
74
96
  - Returns the matched name, or `null` if input is unrecognized (`detectFormat` returns `'UNKNOWN'`).
75
- - Ties are broken by the first entry encountered in the colorspace data.
97
+ - Return type is inferred from the palette's `colors` keys — so passing `{ palette: pantone }` narrows to `PantoneColorName | null`, and a BYO palette with a literal key union narrows to that union.
98
+ - Ties go to whichever color was defined first — deterministic across runs, but not semantically meaningful.
76
99
 
77
100
  ```ts
78
- identify('#ff0000') // 'red'
79
- identify([255, 0, 0]) // 'red'
80
- identify({ r: 250, g: 20, b: 60 }) // 'crimson' (nearest)
81
- identify('#ff0000', { colorspace: 'x11' }) // 'red' (or 'red1', X11 has numbered variants)
82
- identify('#ff0000', { colorspace: 'pantone' }) // nearest Pantone C code, e.g. '185C'
83
- identify('#ff0080', { metric: 'deltaE2000' }) // force perceptual-accurate match
84
- identify('#ff0000', { metric: 'euclidean-srgb' }) // force fastest (non-perceptual) match
101
+ import { identify, pantone, x11 } from 'chromonym';
102
+
103
+ identify('#ff0000') // 'red' (web default)
104
+ identify([255, 0, 0]) // 'red'
105
+ identify({ r: 250, g: 20, b: 60 }) // 'crimson' (nearest)
106
+ identify('#ff0000', { palette: x11 }) // 'red' (x11 also has 'red 1' through 'red 4')
107
+ identify('#e4002b', { palette: pantone }) // '185 C' (exact match — Target/Coca-Cola red)
108
+ identify('#ff0000', { palette: pantone }) // '172 C' (nearest — pure red isn't a Pantone)
109
+ identify('#ff0080', { metric: 'deltaE2000' }) // force perceptual-accurate match
110
+ identify('#ff0000', { metric: 'euclidean-srgb' }) // force fastest (non-perceptual) match
85
111
  ```
86
112
 
87
113
  ### `resolve(name, opts?)`
88
114
 
89
- **Name → color.** Normalizes the input (lowercases it, strips all non-alphanumeric characters), looks up the canonical key in the chosen colorspace, and returns the value in the chosen format.
115
+ **Name → color.** Normalizes the input (lowercases it, strips all non-alphanumeric characters), looks up the canonical key in the chosen palette, and returns the value in the chosen format.
90
116
 
91
117
  ```ts
92
118
  function resolve(
93
119
  name: string,
94
- opts?: { colorspace?: ColorspaceName; format?: ColorFormat },
120
+ opts?: { palette?: Palette; format?: ColorFormat },
95
121
  ): ColorValue | null
96
122
  ```
97
123
 
98
- - **Default colorspace**: `'web'`.
124
+ - **Default palette**: the built-in `web` palette.
99
125
  - **Default format**: `'HEX'`.
100
- - Returns `null` if the normalized name isn't in the colorspace.
101
- - Normalization lets users pass `'Alice Blue'`, `'alice-blue'`, `'ALICEBLUE'`, `'Pantone 185 C'` etc. — all resolved identically.
126
+ - Returns `null` if the normalized name isn't in the palette.
127
+ - Normalization is the palette's own `normalize` function — web/x11 lowercase + strip non-alphanumeric, pantone also strips a `Pantone` / `PMS` prefix. So `'Alice Blue'`, `'alice-blue'`, `'ALICEBLUE'`, `'Pantone 185 C'` all resolve identically in their respective palettes.
102
128
 
103
129
  ```ts
130
+ import { resolve, pantone } from 'chromonym';
131
+
104
132
  resolve('crimson') // '#dc143c'
105
133
  resolve('Alice Blue') // '#f0f8ff'
106
134
  resolve('alice-blue!') // '#f0f8ff'
107
- resolve('185 C', { colorspace: 'pantone' }) // '#e4002b'
108
- resolve('Pantone 185 C', { colorspace: 'pantone' })// '#e4002b' (same, normalized)
135
+ resolve('185 C', { palette: pantone }) // '#e4002b'
136
+ resolve('Pantone 185 C', { palette: pantone }) // '#e4002b' (same, normalized)
109
137
  resolve('crimson', { format: 'RGB' }) // 'rgb(220, 20, 60)'
110
138
  resolve('crimson', { format: 'RGBA' }) // { r: 220, g: 20, b: 60, a: 1 }
111
139
  resolve('not-a-color') // null
@@ -113,77 +141,198 @@ resolve('not-a-color') // null
113
141
 
114
142
  ### `convert(input, opts?)`
115
143
 
116
- **Color → color in a different format.** Detects the input format, normalizes to the canonical internal `Rgba` representation, and returns the value in the target format. Colorspace-independent.
144
+ **Color → color in a different format.** Detects the input format, normalizes to the canonical internal `Rgba`, and returns the value in the target format.
145
+
146
+ Structural on its own (HEX ↔ RGB ↔ RGBA ↔ HSL ↔ HSV, no palette data bundled). Pass an optional `palette` and the same one API understands palette names both ways — input as a name, or `format: 'NAME'` output for exact reverse lookup.
117
147
 
118
148
  ```ts
119
- function convert(
120
- input: ColorInput,
121
- opts?: { format?: ColorFormat },
122
- ): ColorValue
149
+ // Structural (palette-free, tree-shakes to ~5 KB min)
150
+ function convert(input: ColorInput, opts?: { format?: ColorFormat }): ColorValue;
151
+
152
+ // With a palette — input can be a palette name, output can be 'NAME' (exact key)
153
+ function convert<P extends Palette>(
154
+ input: ColorInput | string,
155
+ opts: { palette: P; format: 'NAME' },
156
+ ): PaletteKey<P>;
157
+ function convert<P extends Palette>(
158
+ input: ColorInput | string,
159
+ opts: { palette: P; format?: ColorFormat },
160
+ ): ColorValue;
123
161
  ```
124
162
 
125
163
  - **Default format**: `'HEX'`.
126
- - Throws if input format is unrecognized.
127
- - Alpha information is preserved when converting into RGBA/RGB(A) string forms, and discarded for HEX (6-digit) / HSL / HSV / PANTONE output.
164
+ - Throws on unrecognized input (parser-flavored — `identify` / `resolve` return `null` instead).
165
+ - Alpha is preserved only in `'RGBA'` output (canonical `{ r, g, b, a }` object); dropped by `'HEX'` (6-digit), `'RGB'`, `'HSL'`, `'HSV'`, and `'NAME'`.
166
+ - **Palette is caller-supplied**: no palette is pulled unless you import one and pass it. Tree-shake stays intact — `convert`-only bundles include zero palette data.
167
+ - `format: 'NAME'` is **exact-match only**; it throws if the input isn't a pixel-exact palette entry. Use `identify` for nearest-match.
168
+ - Priority: if the input is structurally valid (hex, rgb, hsl, …), that wins over palette-name lookup. So `convert('#ff0000', { palette: brand })` always means "parse as hex."
169
+
170
+ ```ts
171
+ // Structural — no palette imported, nothing bundled
172
+ convert('#ff0000') // '#ff0000' (identity)
173
+ convert('#ff0000', { format: 'RGB' }) // 'rgb(255, 0, 0)'
174
+ convert([255, 0, 0], { format: 'HEX' }) // '#ff0000'
175
+ convert({ h: 0, s: 100, l: 50 }, { format: 'HEX' }) // '#ff0000'
176
+
177
+ // With a palette — palette-name in, any format out
178
+ import { pantone } from 'chromonym';
179
+ convert('185 C', { palette: pantone }) // '#e4002b'
180
+ convert('Pantone 185 C', { palette: pantone }) // '#e4002b' (normalizer strips prefix)
181
+ convert('185 C', { palette: pantone, format: 'HSL' }) // 'hsl(349, 100%, 45%)'
182
+
183
+ // With a palette — color in, exact palette key out
184
+ convert('#e4002b', { palette: pantone, format: 'NAME' }) // '185 C'
185
+ convert('#663399', { palette: web, format: 'NAME' }) // 'rebeccapurple'
186
+ convert('#ff0000', { palette: pantone, format: 'NAME' }) // throws — use identify for nearest
187
+
188
+ // BYO palettes work the same way
189
+ convert('acme red', { palette: brand, format: 'RGB' }) // 'rgb(255, 42, 59)'
190
+ convert('#ff2a3b', { palette: brand, format: 'NAME' }) // 'acme red'
191
+ ```
192
+
193
+ The low-level `pantoneToRgba` / `rgbaToPantone` functions (from `chromonym` or `chromonym/conversions/pantone`) remain available for single-purpose Pantone work when you don't need the full `convert` ceremony.
194
+
195
+ ## Palettes
196
+
197
+ Every palette — yours or ours — is a `Palette<Name>` object. Define it once, pass it anywhere the three verbs accept one.
128
198
 
129
199
  ```ts
130
- convert('#ff0000') // '#ff0000' (identity)
131
- convert('#ff0000', { format: 'RGB' }) // 'rgb(255, 0, 0)'
132
- convert('#ff0000', { format: 'RGBA' }) // { r: 255, g: 0, b: 0, a: 1 }
133
- convert([255, 0, 0], { format: 'HEX' }) // '#ff0000'
134
- convert('rgb(255, 0, 0)', { format: 'HSL' }) // 'hsl(0, 100%, 50%)'
135
- convert({ h: 0, s: 100, l: 50 }, { format: 'HEX' })// '#ff0000'
136
- convert('185 C', { format: 'HEX' }) // '#e4002b'
137
- convert('#e4002b', { format: 'PANTONE' }) // '185C' (nearest Pantone C)
200
+ type Palette<Name extends string = string> = {
201
+ readonly name: string; // human-readable label
202
+ readonly colors: Record<Name, HexColor>; // the lookup data
203
+ readonly normalize: (s: string) => string; // user-input → canonical key
204
+ readonly defaultMetric: DistanceMetric; // used by identify when no metric override
205
+ };
138
206
  ```
139
207
 
140
- ## Colorspaces
208
+ ### Bring your own
209
+
210
+ The primary shape. Any object matching `Palette<Name>` works — no registry, no plugin, no side effects. Bundlers only include the palettes you actually import.
211
+
212
+ ```ts
213
+ import { identify, resolve, type Palette } from 'chromonym';
214
+
215
+ const warhammer = {
216
+ name: 'warhammer40k',
217
+ colors: {
218
+ 'world eaters red': '#8b1a1a',
219
+ 'adeptus red': '#652022',
220
+ 'sons of malice white': '#e8e4d8',
221
+ 'the flawless host purple': '#6b2d7d',
222
+ 'nurgle green': '#748c3f',
223
+ 'alpha legion teal': '#2a6d7a',
224
+ },
225
+ normalize: (s: string) => s.toLowerCase().replace(/[^a-z0-9]/g, ''),
226
+ defaultMetric: 'deltaE2000',
227
+ } as const satisfies Palette;
228
+
229
+ identify('#750c0c', { palette: warhammer })
230
+ // → 'world eaters red' (return type narrows to the palette's key union)
231
+
232
+ resolve('Nurgle Green', { palette: warhammer })
233
+ // → '#748c3f' (your normalizer handles case + punctuation)
234
+ ```
235
+
236
+ ### The three we ship
141
237
 
142
238
  | Name | Entries | Source |
143
239
  |---|---|---|
144
- | `'web'` (default) | 148 | CSS Color Module Level 4 named colors |
145
- | `'x11'` | 658 | X.Org `rgb.txt` (public domain) |
146
- | `'pantone'` | 907 | Pantone Coated (C) — community approximations (not Pantone-licensed) |
240
+ | `web` (default) | 148 | CSS Color Module Level 4 named colors |
241
+ | `x11` | 658 | X.Org `rgb.txt` (public domain) |
242
+ | `pantone` | 907 | Pantone Coated (C) — community approximations (not Pantone-licensed) |
147
243
 
148
- Each colorspace is a pure data object: `Record<string, HexColor>`. Importable directly:
244
+ Importable directly, or via subpath exports for stricter tree-shaking:
149
245
 
150
246
  ```ts
151
247
  import { web, x11, pantone } from 'chromonym';
152
- web.crimson // '#dc143c'
248
+ // or: import { pantone } from 'chromonym/pantone';
249
+
250
+ web.colors.crimson // '#dc143c'
251
+ web.colors.aliceblue // '#f0f8ff' (CSS keyword form — paste-compatible)
252
+ pantone.colors['185 C'] // '#e4002b'
253
+ ```
254
+
255
+ Each palette's keys follow its own domain convention — so `identify`'s output pastes back into whatever ecosystem the name came from. `web` uses the CSS spec's single-word form (`rebeccapurple`), `x11` uses the X.Org docs' spaced form (`'antique white 1'`), `pantone` uses the official print notation (`'185 C'`).
256
+
257
+ *Trivia:* `web.colors.rebeccapurple` (`#663399`) entered CSS Color 4 in 2014 in memory of [Rebecca Meyer](https://meyerweb.com/eric/thoughts/2014/06/19/rebeccapurple/). The X11 set ships every gray name twice (`gray`, `grey`) plus numbered variants up to 99, so `x11.colors['gray 73']` is a real key (`#bababa`). Yes, these are Greys. No, not the kind that visit during sleep paralysis.
258
+
259
+ ### Cross-palette translation
260
+
261
+ "What's this CSS color in X11?" / "What's our brand red in Pantone?" is a *nearest-match* question — palettes almost never share exact hex values, so there's no strict round-trip. That's `identify`'s job. Chain `convert` (strict name → hex) with `identify` (fuzzy hex → nearest name):
262
+
263
+ ```ts
264
+ import { convert, identify, web, x11, pantone } from 'chromonym';
265
+
266
+ // web → x11
267
+ identify(convert('rebeccapurple', { palette: web }), { palette: x11 })
268
+ // → 'dark orchid 4'
269
+
270
+ identify(convert('dodgerblue', { palette: web }), { palette: x11 })
271
+ // → 'dodger blue' (hex shared across both palettes — exact hit)
272
+
273
+ // x11 → pantone
274
+ identify(convert('dark slate gray 4', { palette: x11 }), { palette: pantone })
275
+ // → '5483 C'
276
+
277
+ identify(convert('debian red', { palette: x11 }), { palette: pantone })
278
+ // → '1925 C'
279
+
280
+ // web → pantone (the classic brand-matching case)
281
+ identify(convert('hotpink', { palette: web }), { palette: pantone }) // → '812 C'
282
+ identify(convert('dodgerblue', { palette: web }), { palette: pantone }) // → '279 C'
283
+
284
+ // BYO → pantone (ship your brand palette, find the closest Pantone for print)
285
+ identify(convert('acme red', { palette: brand }), { palette: pantone }) // → '1788 C'
286
+
287
+ // Override the metric for the *nearest-match* leg if you want — input parsing
288
+ // is always exact, so only the second call takes `metric`.
289
+ identify(convert('rebeccapurple', { palette: web }), { palette: pantone, metric: 'deltaEok' })
290
+ // → '526 C' (OKLAB disagrees with ΔE2000's '267 C' in the saturated-purple region)
291
+ ```
292
+
293
+ Why two calls instead of one: `convert` is the strict verb (throws on a name miss), `identify` is the fuzzy one (always returns a nearest). Keeping them separate lets you decide — per call — whether you want a hard error on an unknown input or a best-effort neighbor.
294
+
295
+ If this chain shows up often in your code, wrap it in your own helper:
296
+
297
+ ```ts
298
+ const translate = (name: string, from: Palette, to: Palette) =>
299
+ identify(convert(name, { palette: from }), { palette: to });
153
300
  ```
154
301
 
155
302
  ## Distance metrics
156
303
 
157
- `identify` picks the nearest-named color by comparing your input to every entry in the chosen colorspace. *How* it measures "nearest" is controlled by `metric`. Five options, roughly ordered from fast-and-approximate to slow-and-correct:
304
+ *Six ways to ask "how different are two colors?"*
305
+
306
+ `identify` picks the nearest-named color by comparing your input to every entry in the chosen palette. *How* it measures "nearest" is controlled by `metric`. Six options, roughly ordered from fast-and-approximate to slow-and-correct:
158
307
 
159
308
  | Metric | What it does | When to use |
160
309
  |---|---|---|
161
310
  | `'euclidean-srgb'` | Raw `sqrt(Δr² + Δg² + Δb²)` in sRGB. Fastest. Not perceptually uniform — a unit of distance doesn't mean a unit of "visual difference." | Tight perf budgets; well-separated palettes where you just need the obvious answer. |
162
311
  | `'euclidean-linear'` | Same math, but on linearized (gamma-removed) RGB. Somewhat better in dark regions. Still not perceptual. | Physical-light mixing contexts; incremental upgrade from sRGB Euclidean. |
163
- | `'deltaE76'` | Euclidean in CIELAB (CIE 1976). Simple perceptual metric. 1 ΔE ≈ "just noticeable difference" for most of the gamut. | **Default for web and x11.** Sweet spot: meaningful perceptual accuracy, low cost. |
312
+ | `'deltaE76'` | Euclidean in CIELAB (CIE 1976). Simple perceptual metric. 1 ΔE ≈ "just noticeable difference" for most of the gamut. | **Default for web and x11.** Meaningful perceptual accuracy at low cost. |
164
313
  | `'deltaE94'` | ΔE76 + chroma/hue weighting (CIE 1994). Fixes "saturated colors feel too far apart." | When ΔE76 is over-penalizing saturated matches in your use case. |
165
314
  | `'deltaE2000'` | Full CIEDE2000 formula with blue/purple rotation correction. Industry standard for print, design tools, Pantone workflows. | **Default for pantone.** Use whenever accuracy matters, especially in the blue region where ΔE76/94 break down. |
166
- | `'deltaEok'` | Euclidean distance in OKLAB (Björn Ottosson, 2020). OKLAB is perceptually uniform by construction — no weighting formula needed. Strictly more uniform than CIELAB; often a better nearest-match than ΔE2000 in saturated-blue regions, and cheaper to compute. | Modern default if you want perceptual accuracy without the CIEDE2000 rotation-term complexity. |
315
+ | `'deltaEok'` | Euclidean distance in OKLAB (Björn Ottosson, 2020). OKLAB is perceptually uniform by construction — no weighting formula needed. Strictly more uniform than CIELAB; often a better nearest-match than ΔE2000 in saturated-blue regions, and cheaper to compute. | Use when you want perceptual accuracy without the CIEDE2000 rotation-term complexity. |
316
+
317
+ Each metric trades cost for accuracy — the full scan over 907 Pantone entries is well under 1 ms even with ΔE2000. For interactive UIs, cost is usually irrelevant; drop to a cheaper metric when batch-processing millions of colors or when you need specific tie-breaking behavior.
167
318
 
168
- Each metric trades cost for accuracy — the full scan over 907 Pantone entries is well under 1 ms even with ΔE2000, so for interactive UIs there's no practical reason not to use the most accurate metric. Batch-processing millions of colors is where you'd drop down.
319
+ > **Note on x11's default (ΔE76):** x11 has 658 entries, many of them saturated blue/purple ramps where ΔE76 is a known weak spot. The default optimizes for UI scrubbing speed (~5 µs vs ~90 µs for ΔE2000). If perceptual accuracy matters more than latency, override per call with `{ metric: 'deltaEok' }` — same cost as ΔE76 on cached Lab/OKLAB indexes, better separation in blues.
169
320
 
170
321
  ```ts
171
- // Defaults: deltaE76 for web/x11, deltaE2000 for pantone.
322
+ // Defaults: read from the palette's own `defaultMetric`.
172
323
  identify('#ff8080') // deltaE76 (web default)
173
- identify('#ff8080', { colorspace: 'pantone' }) // deltaE2000 (pantone default)
324
+ identify('#ff8080', { palette: pantone }) // deltaE2000 (pantone default)
174
325
 
175
326
  // Override per-call:
176
327
  identify('#ff8080', { metric: 'euclidean-srgb' }) // fastest
177
328
  identify('#ff8080', { metric: 'deltaE2000' }) // most accurate
178
- identify('#ff8080', { colorspace: 'pantone', metric: 'euclidean-srgb' }) // force fast for Pantone
329
+ identify('#ff8080', { palette: pantone, metric: 'euclidean-srgb' }) // force fast for Pantone
179
330
  ```
180
331
 
181
- The low-level `rgbaToPantone` always uses `'deltaE2000'` — appropriate for Pantone matching, where this is what the industry tools use.
332
+ The low-level `rgbaToPantone` reads `pantone.defaultMetric` (currently `'deltaE2000'`).
182
333
 
183
334
  ## Formats
184
335
 
185
- Format keys are SCREAMING_CAPS — they are used as dispatch keys internally.
186
-
187
336
  | Key | Input accepted | Output shape |
188
337
  |---|---|---|
189
338
  | `'HEX'` | `'#rgb'`, `'#rrggbb'`, `'#rrggbbaa'` | `'#rrggbb'` string |
@@ -191,13 +340,10 @@ Format keys are SCREAMING_CAPS — they are used as dispatch keys internally.
191
340
  | `'RGBA'` | `'rgba(r, g, b, a)'`, `[r, g, b, a]`, `{ r, g, b, a }` | `{ r, g, b, a }` object |
192
341
  | `'HSL'` | `'hsl(h, s%, l%)'`, `{ h, s, l }` | `'hsl(h, s%, l%)'` string |
193
342
  | `'HSV'` | `'hsv(h, s%, v%)'`, `{ h, s, v }` | `'hsv(h, s%, v%)'` string |
194
- | `'PANTONE'` | `'185 C'`, `'185C'`, `'Pantone 185 C'`, etc. | `'185C'`-style string |
195
-
196
- The runtime set `COLOR_FORMATS` is exported for enumeration / membership checks.
197
343
 
198
344
  ## Error handling
199
345
 
200
- The three dispatchers are asymmetric — chosen deliberately, but worth knowing:
346
+ The three dispatchers are asymmetric:
201
347
 
202
348
  | Function | Unrecognized / unknown input | Rationale |
203
349
  |---|---|---|
@@ -205,7 +351,7 @@ The three dispatchers are asymmetric — chosen deliberately, but worth knowing:
205
351
  | `resolve(name)` | returns `null` | lookup-flavored; name may legitimately not exist |
206
352
  | `convert(input)` | **throws** | parser-flavored; malformed color is a caller error |
207
353
 
208
- Low-level converters (`hexToRgba`, `rgbToRgba`, `hslToRgba`, `hsvToRgba`, `pantoneToRgba`) all **throw** on malformed input. The `rgbaTo*` direction never throws. The nearest-match returner `rgbaToPantone` always returns *some* Pantone code.
354
+ Low-level converters (`hexToRgba`, `rgbToRgba`, `hslToRgba`, `hsvToRgba`, `pantoneToRgba`) all **throw** on malformed input. The `rgbaTo*` direction never throws. `rgbaToPantone` is the special case: it always returns a Pantone code regardless of how distant the nearest match is, so don't treat the return as evidence the input was Pantone-like — check the ΔE yourself if that matters.
209
355
 
210
356
  If you want a `null`-returning variant of `convert`, wrap it:
211
357
 
@@ -218,6 +364,7 @@ const tryConvert = (input: ColorInput, opts = {}) => {
218
364
  ## Utilities
219
365
 
220
366
  - **`detectFormat(input): DetectedFormat`** — runtime format detection. Returns one of the format keys or `'UNKNOWN'`.
367
+ - **`isColor(input): boolean`** — type-guard wrapper around `detectFormat`. Returns `true` if the input is a recognized structural color shape.
221
368
  - **`COLOR_FORMATS: ReadonlySet<ColorFormat>`** — the Set of valid format keys.
222
369
  - **Per-format converters** (tree-shakeable) — `hexToRgba`, `rgbaToHex`, `rgbToRgba`, `rgbaToRgb`, `hslToRgba`, `rgbaToHsl`, `hsvToRgba`, `rgbaToHsv`, `pantoneToRgba`, `rgbaToPantone`. Use these directly if you only need one conversion and want maximum tree-shaking.
223
370
 
@@ -226,20 +373,22 @@ const tryConvert = (input: ColorInput, opts = {}) => {
226
373
  - `sideEffects: false` in `package.json`.
227
374
  - All exports are named; no default export.
228
375
  - Barrel uses explicit re-exports.
229
- - Importing `{ resolve }` or `{ identify }` pulls in all three colorspaces (necessary for the string-keyed `colorspace` option). For strict tree-shaking of colorspace data, use the low-level converters and colorspace objects directly.
376
+ - `identify` / `resolve` take the palette **as an object**, not a string key. You only pay for palettes you actually import: calling `identify(hex)` with no override pulls in `web` (148 entries) and nothing else; passing `{ palette: pantone }` adds pantone. X11 and pantone are *not* bundled unless you reference them.
377
+ - BYO palettes have zero library cost beyond your own data.
378
+ - Subpath exports (`chromonym/web`, `chromonym/x11`, `chromonym/pantone`, `chromonym/conversions/hex`, `chromonym/math/deltaE`, etc.) let you import a single palette or converter without going through the root barrel.
230
379
 
231
380
  ## Types
232
381
 
233
- All types are re-exported from the root:
382
+ Re-exported from the root barrel — `import type { ... } from 'chromonym'`:
234
383
 
235
- ```ts
236
- import type {
237
- ColorInput, ColorValue, ColorFormat, ColorspaceName,
238
- HexColor, Rgba, RgbInput, RgbaInput, HslInput, HsvInput, PantoneCode,
239
- Colorspace,
240
- WebColorName, X11ColorName, PantoneColorName,
241
- } from 'chromonym';
242
- ```
384
+ | Category | Types |
385
+ |---|---|
386
+ | Input / output unions | `ColorInput`, `ColorValue` |
387
+ | Format keys | `ColorFormat` |
388
+ | Per-format shapes | `HexColor`, `Rgba`, `RgbInput`, `RgbaInput`, `HslInput`, `HsvInput` |
389
+ | Palette container | `Palette<Name>`, `NormalizeFn` |
390
+ | Distance selector | `DistanceMetric` |
391
+ | Color-name unions | `WebColorName`, `X11ColorName`, `PantoneColorName` |
243
392
 
244
393
  ## Development
245
394
 
@@ -251,19 +400,17 @@ bun test
251
400
  bun run build
252
401
  ```
253
402
 
254
- Regenerating colorspace data:
403
+ Regenerating palette data:
255
404
 
256
405
  ```sh
257
406
  bun run scripts/generate-x11.ts # reads /usr/share/X11/rgb.txt
258
- bun run scripts/generate-pantone.ts # reads color_library (MIT dependency)
407
+ bun run scripts/generate-pantone.ts # reads the color_library npm package (MIT © Radu Dragan)
259
408
  ```
260
409
 
261
410
  ## Coverage
262
411
 
263
412
  [![Coverage sunburst](https://codecov.io/github/simiancraft/chromonym/graphs/sunburst.svg?token=HYWM6G66YE)](https://codecov.io/github/simiancraft/chromonym)
264
413
 
265
- Each ring is a directory; each leaf is a file. Green is covered, red is gaps.
266
-
267
414
  ## See also
268
415
 
269
416
  chromonym deliberately limits its scope to color *naming* (resolving and identifying) and lightweight conversions. For other color-related work, reach for tools that do those things well:
@@ -276,8 +423,10 @@ chromonym deliberately limits its scope to color *naming* (resolving and identif
276
423
 
277
424
  MIT © [the-simian](https://github.com/the-simian)
278
425
 
426
+ <p align="center"><sub>Crafted with care by <a href="https://simiancraft.com">Simiancraft</a>.</sub></p>
427
+
279
428
  ---
280
429
 
281
430
  > ### Pantone® trademark notice
282
431
  >
283
- > **Pantone®** is a registered trademark of **Pantone LLC**. Chromonym is **not affiliated with, endorsed by, or certified by Pantone LLC**. The `pantone` colorspace ships **community-derived sRGB approximations** of the Pantone Coated (C) set. Values will **not** match a licensed Pantone reference exactly and are unsuitable for print color specification. See [`NOTICE.md`](./NOTICE.md) for full text.
432
+ > **Pantone®** is a registered trademark of **Pantone LLC**. Chromonym is **not affiliated with, endorsed by, or certified by Pantone LLC**. The `pantone` palette ships **community-derived sRGB approximations** of the Pantone Coated (C) set. Values will **not** match a licensed Pantone reference exactly and are unsuitable for print color specification. See [`NOTICE.md`](./NOTICE.md) for full text.
@@ -1,4 +1,4 @@
1
- import type { HexColor, Rgba } from '../types';
1
+ import type { HexColor, Rgba } from '../types.js';
2
2
  /**
3
3
  * Parse a hex color string into the canonical Rgba representation.
4
4
  * Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).
@@ -1 +1 @@
1
- {"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,UAAU,CAAC;AAE/C;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAsB7C;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAE9C"}
1
+ {"version":3,"file":"hex.d.ts","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAElD;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,QAAQ,GAAG,IAAI,CAsB7C;AAMD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,QAAQ,CAE9C","sourcesContent":["import { HEX_RE } from '../detectFormat.js';\nimport { clamp } from '../math/clamp.js';\nimport type { HexColor, Rgba } from '../types.js';\n\n/**\n * Parse a hex color string into the canonical Rgba representation.\n * Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).\n * Throws on any other input shape.\n */\nexport function hexToRgba(hex: HexColor): Rgba {\n const match = HEX_RE.exec(hex);\n const body = match?.[1];\n if (!body) throw new Error(`Invalid hex color: ${hex}`);\n\n if (body.length === 3) {\n const r = body.charAt(0);\n const g = body.charAt(1);\n const b = body.charAt(2);\n return {\n r: parseInt(r + r, 16),\n g: parseInt(g + g, 16),\n b: parseInt(b + b, 16),\n a: 1,\n };\n }\n\n const r = parseInt(body.slice(0, 2), 16);\n const g = parseInt(body.slice(2, 4), 16);\n const b = parseInt(body.slice(4, 6), 16);\n const a = body.length === 8 ? parseInt(body.slice(6, 8), 16) / 255 : 1;\n return { r, g, b, a };\n}\n\nfunction channelToHex(n: number): string {\n return clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');\n}\n\n/**\n * Emit a 6-digit lowercase hex string. Alpha is dropped; out-of-range\n * channel values are clamped to 0..255; fractional values are rounded.\n */\nexport function rgbaToHex(rgba: Rgba): HexColor {\n return `#${channelToHex(rgba.r)}${channelToHex(rgba.g)}${channelToHex(rgba.b)}` as HexColor;\n}\n"]}
@@ -1,5 +1,5 @@
1
- import { HEX_RE } from '../detectFormat';
2
- import { clamp } from '../math/clamp';
1
+ import { HEX_RE } from '../detectFormat.js';
2
+ import { clamp } from '../math/clamp.js';
3
3
  /**
4
4
  * Parse a hex color string into the canonical Rgba representation.
5
5
  * Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).
@@ -1 +1 @@
1
- {"version":3,"file":"hex.js","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAGtC;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAa;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;IAExD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,OAAO;YACL,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,CAAC;SACL,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAU;IAClC,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAc,CAAC;AAC9F,CAAC"}
1
+ {"version":3,"file":"hex.js","sourceRoot":"","sources":["../../src/conversions/hex.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAGzC;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,GAAa;IACrC,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;IACxB,IAAI,CAAC,IAAI;QAAE,MAAM,IAAI,KAAK,CAAC,sBAAsB,GAAG,EAAE,CAAC,CAAC;IAExD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACzB,OAAO;YACL,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,QAAQ,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;YACtB,CAAC,EAAE,CAAC;SACL,CAAC;IACJ,CAAC;IAED,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACzC,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;IACvE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;AACxB,CAAC;AAED,SAAS,YAAY,CAAC,CAAS;IAC7B,OAAO,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;AACpE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,SAAS,CAAC,IAAU;IAClC,OAAO,IAAI,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAc,CAAC;AAC9F,CAAC","sourcesContent":["import { HEX_RE } from '../detectFormat.js';\nimport { clamp } from '../math/clamp.js';\nimport type { HexColor, Rgba } from '../types.js';\n\n/**\n * Parse a hex color string into the canonical Rgba representation.\n * Accepts 3-digit (`#rgb`), 6-digit (`#rrggbb`), and 8-digit (`#rrggbbaa`).\n * Throws on any other input shape.\n */\nexport function hexToRgba(hex: HexColor): Rgba {\n const match = HEX_RE.exec(hex);\n const body = match?.[1];\n if (!body) throw new Error(`Invalid hex color: ${hex}`);\n\n if (body.length === 3) {\n const r = body.charAt(0);\n const g = body.charAt(1);\n const b = body.charAt(2);\n return {\n r: parseInt(r + r, 16),\n g: parseInt(g + g, 16),\n b: parseInt(b + b, 16),\n a: 1,\n };\n }\n\n const r = parseInt(body.slice(0, 2), 16);\n const g = parseInt(body.slice(2, 4), 16);\n const b = parseInt(body.slice(4, 6), 16);\n const a = body.length === 8 ? parseInt(body.slice(6, 8), 16) / 255 : 1;\n return { r, g, b, a };\n}\n\nfunction channelToHex(n: number): string {\n return clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');\n}\n\n/**\n * Emit a 6-digit lowercase hex string. Alpha is dropped; out-of-range\n * channel values are clamped to 0..255; fractional values are rounded.\n */\nexport function rgbaToHex(rgba: Rgba): HexColor {\n return `#${channelToHex(rgba.r)}${channelToHex(rgba.g)}${channelToHex(rgba.b)}` as HexColor;\n}\n"]}
@@ -1,4 +1,4 @@
1
- import type { HslInput, HslString, Rgba } from '../types';
1
+ import type { HslInput, HslString, Rgba } from '../types.js';
2
2
  /**
3
3
  * Parse HSL input (string or object) into canonical Rgba.
4
4
  * Hue wraps modulo 360; saturation and lightness are 0..100 percent.