chromonym 3.1.1 → 3.2.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 (81) hide show
  1. package/NOTICE.md +243 -1
  2. package/README.md +47 -42
  3. package/dist/convert.d.ts +73 -21
  4. package/dist/convert.d.ts.map +1 -1
  5. package/dist/convert.js +13 -2
  6. package/dist/convert.js.map +1 -1
  7. package/dist/defineColorPalette.d.ts +53 -0
  8. package/dist/defineColorPalette.d.ts.map +1 -0
  9. package/dist/defineColorPalette.js +103 -0
  10. package/dist/defineColorPalette.js.map +1 -0
  11. package/dist/detectFormat.d.ts +26 -3
  12. package/dist/detectFormat.d.ts.map +1 -1
  13. package/dist/detectFormat.js +20 -0
  14. package/dist/detectFormat.js.map +1 -1
  15. package/dist/identify.d.ts +63 -17
  16. package/dist/identify.d.ts.map +1 -1
  17. package/dist/identify.js.map +1 -1
  18. package/dist/index.d.ts +13 -4
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +12 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/indexing.d.ts +13 -3
  23. package/dist/indexing.d.ts.map +1 -1
  24. package/dist/indexing.js +67 -19
  25. package/dist/indexing.js.map +1 -1
  26. package/dist/palettes/crayola.d.ts +16 -0
  27. package/dist/palettes/crayola.d.ts.map +1 -1
  28. package/dist/palettes/crayola.js +16 -0
  29. package/dist/palettes/crayola.js.map +1 -1
  30. package/dist/palettes/fs595b.d.ts +467 -0
  31. package/dist/palettes/fs595b.d.ts.map +1 -0
  32. package/dist/palettes/fs595b.js +256 -0
  33. package/dist/palettes/fs595b.js.map +1 -0
  34. package/dist/palettes/fs595c.d.ts +1224 -0
  35. package/dist/palettes/fs595c.d.ts.map +1 -0
  36. package/dist/palettes/fs595c.js +633 -0
  37. package/dist/palettes/fs595c.js.map +1 -0
  38. package/dist/palettes/isccNbs.d.ts +562 -0
  39. package/dist/palettes/isccNbs.d.ts.map +1 -0
  40. package/dist/palettes/isccNbs.js +300 -0
  41. package/dist/palettes/isccNbs.js.map +1 -0
  42. package/dist/palettes/nbs.d.ts +578 -0
  43. package/dist/palettes/nbs.d.ts.map +1 -0
  44. package/dist/palettes/nbs.js +309 -0
  45. package/dist/palettes/nbs.js.map +1 -0
  46. package/dist/palettes/ncs.d.ts +3949 -0
  47. package/dist/palettes/ncs.d.ts.map +1 -0
  48. package/dist/palettes/ncs.js +2011 -0
  49. package/dist/palettes/ncs.js.map +1 -0
  50. package/dist/palettes/ntc.d.ts +3170 -0
  51. package/dist/palettes/ntc.d.ts.map +1 -0
  52. package/dist/palettes/ntc.js +1602 -0
  53. package/dist/palettes/ntc.js.map +1 -0
  54. package/dist/palettes/pantone.d.ts +18 -1
  55. package/dist/palettes/pantone.d.ts.map +1 -1
  56. package/dist/palettes/pantone.js +18 -1
  57. package/dist/palettes/pantone.js.map +1 -1
  58. package/dist/palettes/resene.d.ts +2802 -0
  59. package/dist/palettes/resene.d.ts.map +1 -0
  60. package/dist/palettes/resene.js +1422 -0
  61. package/dist/palettes/resene.js.map +1 -0
  62. package/dist/palettes/web.d.ts +10 -0
  63. package/dist/palettes/web.d.ts.map +1 -1
  64. package/dist/palettes/web.js +10 -0
  65. package/dist/palettes/web.js.map +1 -1
  66. package/dist/palettes/x11.d.ts +15 -10
  67. package/dist/palettes/x11.d.ts.map +1 -1
  68. package/dist/palettes/x11.js +15 -10
  69. package/dist/palettes/x11.js.map +1 -1
  70. package/dist/palettes/xkcd.d.ts +1880 -0
  71. package/dist/palettes/xkcd.d.ts.map +1 -0
  72. package/dist/palettes/xkcd.js +955 -0
  73. package/dist/palettes/xkcd.js.map +1 -0
  74. package/dist/resolve.d.ts +58 -14
  75. package/dist/resolve.d.ts.map +1 -1
  76. package/dist/resolve.js +32 -16
  77. package/dist/resolve.js.map +1 -1
  78. package/dist/types.d.ts +47 -0
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js.map +1 -1
  81. package/package.json +43 -4
@@ -1 +1 @@
1
- {"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba, rgbaToHex } from './conversions/hex.js';\nimport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nimport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nimport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nimport { type DetectedFormat, detectFormat } from './detectFormat.js';\nimport { getNameIndex, getReverseNameIndex } from './indexing.js';\nimport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n HexColor,\n HslInput,\n HsvInput,\n Palette,\n Rgba,\n RgbaInput,\n RgbInput,\n} from './types.js';\n\nfunction safeStringify(x: unknown): string {\n if (x === undefined) return 'undefined';\n try {\n const s = JSON.stringify(x);\n return s === undefined ? String(x) : s;\n } catch {\n return Object.prototype.toString.call(x);\n }\n}\n\n/**\n * Normalize any recognized color input to canonical `Rgba`.\n * Pass `knownFormat` when the caller has already run `detectFormat` to skip\n * the redundant detection. Throws on `UNKNOWN` input.\n *\n * Palette names (Pantone codes, BYO keys) are not accepted here — they\n * require palette data to parse. Use `convert(name, { palette })` or\n * `resolve(name, { palette })` at the higher level.\n */\nexport function toRgba(input: ColorInput, knownFormat?: DetectedFormat): Rgba {\n const format = knownFormat ?? detectFormat(input);\n switch (format) {\n case 'HEX':\n return hexToRgba(input as HexColor);\n case 'RGB':\n return rgbToRgba(input as RgbInput);\n case 'RGBA':\n return rgbToRgba(input as RgbaInput);\n case 'HSL':\n return hslToRgba(input as HslInput);\n case 'HSV':\n return hsvToRgba(input as HsvInput);\n default:\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n}\n\n/**\n * Emit a canonical `Rgba` in the target structural format. Palette-independent.\n *\n * For palette-key output (e.g. nearest Pantone code, brand name), use\n * `convert(rgba, { format: 'NAME', palette })` — or `identify` for fuzzy,\n * `rgbaToPantone` for the low-level Pantone-only path.\n */\nexport function fromRgba(rgba: Rgba, format: ColorFormat = 'HEX'): ColorValue {\n switch (format) {\n case 'HEX':\n return rgbaToHex(rgba);\n case 'RGB':\n return rgbaToRgb(rgba);\n case 'RGBA':\n return rgba;\n case 'HSL':\n return rgbaToHsl(rgba);\n case 'HSV':\n return rgbaToHsv(rgba);\n default: {\n // Runtime safety for JS callers bypassing the ColorFormat union.\n const hint =\n format === ('PANTONE' as unknown)\n ? ` — use convert(rgba, { palette: pantone, format: 'NAME' }) or rgbaToPantone from 'chromonym/conversions/pantone'`\n : '';\n throw new Error(`Unsupported format: ${safeStringify(format)}${hint}`);\n }\n }\n}\n\n/**\n * Extract string keys from a Palette's `colors` map.\n * Matches the `PaletteKey<P>` helper in `identify.ts` — kept local so\n * `convert` doesn't depend on `identify`.\n */\ntype PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;\n\n/**\n * Detect the input color format, normalize to Rgba, and emit the result\n * in the requested output format.\n *\n * Without a `palette` option, `convert` is strictly structural —\n * HEX ↔ RGB ↔ RGBA ↔ HSL ↔ HSV, no palette data ever touched, tree-shakes\n * to the bone.\n *\n * With a `palette` option, `convert` understands the palette's naming\n * scheme both ways:\n * - Input can be a palette name (`'185 C'`, `'Acme Red'`) — parsed via\n * `palette.normalize` and looked up in `palette.colors`.\n * - Output can be `format: 'NAME'` — the exact canonical key from\n * `palette.colors`, throws if the color isn't a pixel-exact match (use\n * `identify` for nearest-match).\n *\n * The palette data comes from the caller's own import, so tree-shaking\n * is preserved: callers who don't pass `palette` never pull a palette.\n *\n * Throws on unrecognized input (parser-flavored — `identify`/`resolve`\n * are the `null`-returning alternatives).\n */\n\n// Overload 1: no palette — structural only. Input is strictly ColorInput.\nexport function convert(input: ColorInput, opts?: { format?: ColorFormat }): ColorValue;\n\n// Overload 2: palette present, format 'NAME' — returns a palette key.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: { palette: P; format: 'NAME' },\n): PaletteKey<P>;\n\n// Overload 3: palette present, structural format — returns ColorValue.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: { palette: P; format?: ColorFormat },\n): ColorValue;\n\n// Implementation\nexport function convert(\n input: ColorInput | string,\n opts: { format?: ColorFormat | 'NAME'; palette?: Palette } = {},\n): ColorValue | string {\n const { format = 'HEX', palette } = opts;\n\n // --- Parse input → Rgba ---\n // Structural detection first; palette lookup only as a fallback. This\n // way `convert('#ff0000', { palette: brand })` always means \"parse hex,\"\n // even if the brand palette happens to have a key that normalizes to\n // \"ff0000\".\n const detected = detectFormat(input as ColorInput);\n let rgba: Rgba;\n if (detected !== 'UNKNOWN') {\n rgba = toRgba(input as ColorInput, detected);\n } else if (palette !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(palette).get(palette.normalize(input));\n if (canonical === undefined) {\n throw new Error(\n `Unrecognized color input: ${safeStringify(input)} (not structural, and not found in palette '${palette.name}')`,\n );\n }\n const hex = (palette.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n rgba = hexToRgba(hex);\n } else {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n\n // --- Emit ---\n if (format === 'NAME') {\n if (palette === undefined) {\n throw new Error(`convert(_, { format: 'NAME' }) requires a 'palette' option`);\n }\n // Palette colors are alpha = 1 by construction; a partially-transparent\n // input isn't \"that named color\" in the strict-convert sense. Reject\n // here rather than silently drop alpha during the reverse lookup.\n if (rgba.a !== 1) {\n throw new Error(\n `format: 'NAME' requires fully-opaque input (a === 1). Got a=${rgba.a}. ` +\n `Strip alpha first or use identify() for fuzzy matching.`,\n );\n }\n const hex = rgbaToHex(rgba).toLowerCase();\n const name = getReverseNameIndex(palette).get(hex);\n if (name === undefined) {\n throw new Error(\n `No exact match for ${hex} in palette '${palette.name}'. Use identify() for nearest-match semantics.`,\n );\n }\n return name;\n }\n return fromRgba(rgba, format);\n}\n"]}
1
+ {"version":3,"file":"convert.js","sourceRoot":"","sources":["../src/convert.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba, rgbaToHex } from './conversions/hex.js';\nimport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nimport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nimport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nimport { type DetectedFormat, detectFormat } from './detectFormat.js';\nimport { getNameIndex, getReverseNameIndex } from './indexing.js';\nimport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n HexColor,\n HslInput,\n HsvInput,\n Palette,\n PaletteKey,\n Rgba,\n RgbaInput,\n RgbInput,\n} from './types.js';\n\nfunction safeStringify(x: unknown): string {\n if (x === undefined) return 'undefined';\n try {\n const s = JSON.stringify(x);\n return s === undefined ? String(x) : s;\n } catch {\n return Object.prototype.toString.call(x);\n }\n}\n\n/**\n * Normalize any recognized color input to canonical `Rgba`.\n * Pass `knownFormat` when the caller has already run `detectFormat` to skip\n * the redundant detection. Throws on `UNKNOWN` input.\n *\n * Palette names (Pantone codes, BYO keys) are not accepted here; they\n * require palette data to parse. Use `convert(name, { palette })` or\n * `resolve(name, { palette })` at the higher level.\n *\n * @example\n * toRgba('#ff0000'); // { r: 255, g: 0, b: 0, a: 1 }\n * toRgba('rgb(255, 0, 0)'); // { r: 255, g: 0, b: 0, a: 1 }\n * toRgba([255, 0, 0]); // { r: 255, g: 0, b: 0, a: 1 }\n * toRgba({ h: 0, s: 100, l: 50 }); // { r: 255, g: 0, b: 0, a: 1 }\n */\nexport function toRgba(input: ColorInput, knownFormat?: DetectedFormat): Rgba {\n const format = knownFormat ?? detectFormat(input);\n switch (format) {\n case 'HEX':\n return hexToRgba(input as HexColor);\n case 'RGB':\n return rgbToRgba(input as RgbInput);\n case 'RGBA':\n return rgbToRgba(input as RgbaInput);\n case 'HSL':\n return hslToRgba(input as HslInput);\n case 'HSV':\n return hsvToRgba(input as HsvInput);\n default:\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n}\n\n/**\n * Emit a canonical `Rgba` in the target structural format. Palette-independent.\n *\n * For palette-key output (e.g. nearest Pantone code, brand name), use\n * `convert(rgba, { format: 'NAME', palette })`; or `identify` for fuzzy,\n * `rgbaToPantone` for the low-level Pantone-only path.\n *\n * @example\n * fromRgba({ r: 255, g: 0, b: 0, a: 1 }); // '#ff0000'\n * fromRgba({ r: 255, g: 0, b: 0, a: 1 }, 'HSL'); // 'hsl(0, 100%, 50%)'\n * fromRgba({ r: 255, g: 0, b: 0, a: 0.5 }, 'RGB'); // 'rgb(255, 0, 0)' (alpha dropped)\n */\nexport function fromRgba(rgba: Rgba, format: ColorFormat = 'HEX'): ColorValue {\n switch (format) {\n case 'HEX':\n return rgbaToHex(rgba);\n case 'RGB':\n return rgbaToRgb(rgba);\n case 'RGBA':\n return rgba;\n case 'HSL':\n return rgbaToHsl(rgba);\n case 'HSV':\n return rgbaToHsv(rgba);\n default: {\n // Runtime safety for JS callers bypassing the ColorFormat union.\n const hint =\n format === ('PANTONE' as unknown)\n ? ` — use convert(rgba, { palette: pantone, format: 'NAME' }) or rgbaToPantone from 'chromonym/conversions/pantone'`\n : '';\n throw new Error(`Unsupported format: ${safeStringify(format)}${hint}`);\n }\n }\n}\n\n/**\n * Detect the input color format, normalize to Rgba, and emit the result\n * in the requested output format.\n *\n * Without a `palette` option, `convert` is strictly structural —\n * HEX ↔ RGB ↔ RGBA ↔ HSL ↔ HSV, no palette data ever touched, tree-shakes\n * to the bone.\n *\n * With a `palette` option, `convert` understands the palette's naming\n * scheme both ways:\n * - Input can be a palette name (`'185 C'`, `'Acme Red'`) — parsed via\n * `palette.normalize` and looked up in `palette.colors`.\n * - Output can be `format: 'NAME'` — the exact canonical key from\n * `palette.colors`, throws if the color isn't a pixel-exact match (use\n * `identify` for nearest-match).\n *\n * The palette data comes from the caller's own import, so tree-shaking\n * is preserved: callers who don't pass `palette` never pull a palette.\n *\n * Throws on unrecognized input (parser-flavored — `identify`/`resolve`\n * are the `null`-returning alternatives).\n */\n\n/**\n * Options for the structural (palette-free) variant of {@link convert}.\n * Use when you only need to translate between HEX / RGB / RGBA / HSL / HSV.\n *\n * @example\n * convert('#ff0000', { format: 'HSL' }); // 'hsl(0, 100%, 50%)'\n */\nexport type ConvertStructuralOptions = {\n /**\n * Target output format. Defaults to `'HEX'`.\n */\n readonly format?: ColorFormat;\n};\n\n/**\n * Options for {@link convert} when reverse-looking-up a palette name\n * from an exact color value. Generic so the return narrows to the\n * palette's literal-key union.\n *\n * Throws at runtime if the input isn't a pixel-exact match in the\n * palette; use `identify` for nearest-match semantics.\n *\n * @example\n * convert('#e4002b', { palette: pantone, format: 'NAME' }); // '185 C'\n * convert('#663399', { palette: web, format: 'NAME' }); // 'rebeccapurple'\n */\nexport type ConvertNameOptions<P extends Palette> = {\n /**\n * Palette to reverse-look-up against. Required when `format` is `'NAME'`.\n */\n readonly palette: P;\n /**\n * Output discriminator. `'NAME'` returns the palette's canonical key\n * for the input color.\n */\n readonly format: 'NAME';\n};\n\n/**\n * Options for {@link convert} when a palette is provided but the output\n * is a structural format (HEX / RGB / HSL / HSV). Unlocks palette-name\n * *input*: the `input` may be `'185 C'`, `'Acme Red'`, etc., which is\n * resolved through `palette.normalize` before conversion.\n *\n * @example\n * convert('185 C', { palette: pantone }); // '#e4002b'\n * convert('185 C', { palette: pantone, format: 'HSL' }); // 'hsl(349, 100%, 45%)'\n */\nexport type ConvertPaletteOptions<P extends Palette> = {\n /**\n * Palette used to parse name-shaped inputs.\n */\n readonly palette: P;\n /**\n * Target output format. Defaults to `'HEX'`.\n */\n readonly format?: ColorFormat;\n};\n\n// Overload 1: no palette — structural only. Input is strictly ColorInput.\nexport function convert(input: ColorInput, opts?: ConvertStructuralOptions): ColorValue;\n\n// Overload 2: palette present, format 'NAME' — returns a palette key.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: ConvertNameOptions<P>,\n): PaletteKey<P>;\n\n// Overload 3: palette present, structural format — returns ColorValue.\nexport function convert<P extends Palette>(\n input: ColorInput | string,\n opts: ConvertPaletteOptions<P>,\n): ColorValue;\n\n// Implementation\nexport function convert(\n input: ColorInput | string,\n opts: { format?: ColorFormat | 'NAME'; palette?: Palette } = {},\n): ColorValue | string {\n const { format = 'HEX', palette } = opts;\n\n // --- Parse input → Rgba ---\n // Structural detection first; palette lookup only as a fallback. This\n // way `convert('#ff0000', { palette: brand })` always means \"parse hex,\"\n // even if the brand palette happens to have a key that normalizes to\n // \"ff0000\".\n const detected = detectFormat(input as ColorInput);\n let rgba: Rgba;\n if (detected !== 'UNKNOWN') {\n rgba = toRgba(input as ColorInput, detected);\n } else if (palette !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(palette).get(palette.normalize(input));\n if (canonical === undefined) {\n throw new Error(\n `Unrecognized color input: ${safeStringify(input)} (not structural, and not found in palette '${palette.name}')`,\n );\n }\n const hex = (palette.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n rgba = hexToRgba(hex);\n } else {\n throw new Error(`Unrecognized color input: ${safeStringify(input)}`);\n }\n\n // --- Emit ---\n if (format === 'NAME') {\n if (palette === undefined) {\n throw new Error(`convert(_, { format: 'NAME' }) requires a 'palette' option`);\n }\n // Palette colors are alpha = 1 by construction; a partially-transparent\n // input isn't \"that named color\" in the strict-convert sense. Reject\n // here rather than silently drop alpha during the reverse lookup.\n if (rgba.a !== 1) {\n throw new Error(\n `format: 'NAME' requires fully-opaque input (a === 1). Got a=${rgba.a}. ` +\n `Strip alpha first or use identify() for fuzzy matching.`,\n );\n }\n const hex = rgbaToHex(rgba).toLowerCase();\n const name = getReverseNameIndex(palette).get(hex);\n if (name === undefined) {\n throw new Error(\n `No exact match for ${hex} in palette '${palette.name}'. Use identify() for nearest-match semantics.`,\n );\n }\n return name;\n }\n return fromRgba(rgba, format);\n}\n"]}
@@ -0,0 +1,53 @@
1
+ import type { ColorInput, DistanceMetric, NormalizeFn, Palette } from './types.js';
2
+ /**
3
+ * The authoring shape accepted by {@link defineColorPalette}: same as
4
+ * {@link Palette}, except `colors` values may be any {@link ColorInput}
5
+ * rather than requiring {@link HexColor}. Exported mainly for
6
+ * type-level inspection; most callers should not reference it directly.
7
+ */
8
+ export type DefineColorPaletteInput = {
9
+ readonly name: string;
10
+ readonly colors: Readonly<Record<string, ColorInput>>;
11
+ readonly normalize: NormalizeFn;
12
+ readonly defaultMetric: DistanceMetric;
13
+ };
14
+ /**
15
+ * Author a BYO palette with mixed-format color values. Values are
16
+ * normalized to {@link HexColor} at call time; unparseable entries are
17
+ * dropped with a `console.warn`.
18
+ *
19
+ * The `const` type parameter preserves the literal-key union so downstream
20
+ * `identify`/`resolve`/`convert` narrow their return types to the palette's
21
+ * actual keys, the same way the built-in palettes do.
22
+ *
23
+ * @example
24
+ * // Mixed input formats; all end up as HexColor in the returned palette.
25
+ * const brand = defineColorPalette({
26
+ * name: 'brand',
27
+ * colors: {
28
+ * 'Acme Red': '#ff0044',
29
+ * 'Acme Blue': 'rgb(0, 68, 255)',
30
+ * 'Acme Ink': { r: 20, g: 20, b: 30 },
31
+ * 'Acme Mist': [240, 244, 248],
32
+ * },
33
+ * normalize: (s) => s.toLowerCase().replace(/\s+/g, ''),
34
+ * defaultMetric: 'deltaE2000',
35
+ * });
36
+ *
37
+ * identify('#ff0000', { palette: brand });
38
+ * // typed as "'Acme Red' | 'Acme Blue' | 'Acme Ink' | 'Acme Mist' | null"
39
+ *
40
+ * @example
41
+ * // A bad value doesn't break the palette; it's dropped with a warning.
42
+ * const partial = defineColorPalette({
43
+ * name: 'partial',
44
+ * colors: { good: '#00ff00', bad: 'taco' as never },
45
+ * normalize: (s) => s,
46
+ * defaultMetric: 'deltaE76',
47
+ * });
48
+ * // console.warn: [chromonym] defineColorPalette("partial"): skipping key
49
+ * // "bad"; expected a color, got "taco"
50
+ * // partial.colors === { good: '#00ff00' }
51
+ */
52
+ export declare function defineColorPalette<const P extends DefineColorPaletteInput>(p: P): Palette<Extract<keyof P['colors'], string>>;
53
+ //# sourceMappingURL=defineColorPalette.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineColorPalette.d.ts","sourceRoot":"","sources":["../src/defineColorPalette.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAY,WAAW,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAE7F;;;;;GAKG;AACH,MAAM,MAAM,uBAAuB,GAAG;IACpC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAC;IACtD,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,cAAc,CAAC;CACxC,CAAC;AAaF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,CAAC,SAAS,uBAAuB,EACxE,CAAC,EAAE,CAAC,GACH,OAAO,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC,CAgC7C","sourcesContent":["// BYO-palette authoring helper. See the companion test file for the full\n// matrix of accepted value shapes; see the README \"Bring your own palette\"\n// section for how callers are meant to reach for this.\n//\n// Why this exists: without it, a BYO palette needs\n// `{ colors: {...}, ... } as const satisfies Palette<'Red' | 'Blue'>`\n// which requires listing the key union twice and only accepts `HexColor`\n// values. With this helper, callers pass any `ColorInput` value (hex, rgb\n// string, hsl object, tuple, etc.) and the helper normalizes every entry\n// to `HexColor` at define time, so downstream `identify`/`resolve`/`convert`\n// and the indexing caches still see a uniform hex-only palette.\n//\n// Error policy: on an unparseable value we `console.warn` and drop the\n// key rather than throw, so one bad entry doesn't sink a whole palette.\n// The caller sees the warning with the palette name and the offending\n// value; the resulting palette has the surviving entries and behaves\n// normally.\n\nimport { rgbaToHex } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport type { ColorInput, DistanceMetric, HexColor, NormalizeFn, Palette } from './types.js';\n\n/**\n * The authoring shape accepted by {@link defineColorPalette}: same as\n * {@link Palette}, except `colors` values may be any {@link ColorInput}\n * rather than requiring {@link HexColor}. Exported mainly for\n * type-level inspection; most callers should not reference it directly.\n */\nexport type DefineColorPaletteInput = {\n readonly name: string;\n readonly colors: Readonly<Record<string, ColorInput>>;\n readonly normalize: NormalizeFn;\n readonly defaultMetric: DistanceMetric;\n};\n\nfunction describe(value: unknown): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n try {\n const s = JSON.stringify(value);\n return s ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Author a BYO palette with mixed-format color values. Values are\n * normalized to {@link HexColor} at call time; unparseable entries are\n * dropped with a `console.warn`.\n *\n * The `const` type parameter preserves the literal-key union so downstream\n * `identify`/`resolve`/`convert` narrow their return types to the palette's\n * actual keys, the same way the built-in palettes do.\n *\n * @example\n * // Mixed input formats; all end up as HexColor in the returned palette.\n * const brand = defineColorPalette({\n * name: 'brand',\n * colors: {\n * 'Acme Red': '#ff0044',\n * 'Acme Blue': 'rgb(0, 68, 255)',\n * 'Acme Ink': { r: 20, g: 20, b: 30 },\n * 'Acme Mist': [240, 244, 248],\n * },\n * normalize: (s) => s.toLowerCase().replace(/\\s+/g, ''),\n * defaultMetric: 'deltaE2000',\n * });\n *\n * identify('#ff0000', { palette: brand });\n * // typed as \"'Acme Red' | 'Acme Blue' | 'Acme Ink' | 'Acme Mist' | null\"\n *\n * @example\n * // A bad value doesn't break the palette; it's dropped with a warning.\n * const partial = defineColorPalette({\n * name: 'partial',\n * colors: { good: '#00ff00', bad: 'taco' as never },\n * normalize: (s) => s,\n * defaultMetric: 'deltaE76',\n * });\n * // console.warn: [chromonym] defineColorPalette(\"partial\"): skipping key\n * // \"bad\"; expected a color, got \"taco\"\n * // partial.colors === { good: '#00ff00' }\n */\nexport function defineColorPalette<const P extends DefineColorPaletteInput>(\n p: P,\n): Palette<Extract<keyof P['colors'], string>> {\n // Prototype-free accumulator, not a plain `{}`. A caller hydrating\n // `p.colors` from JSON (common for BYO palettes sourced from a CMS,\n // a brand spec, etc.) can legitimately have a key like `__proto__`\n // or `constructor`; palette data is just names and hex strings, so\n // those names have no special meaning here. With `{}`, assigning\n // `'#ff0000'` to `normalized['__proto__']` would be silently eaten\n // by the `Object.prototype.__proto__` setter (which rejects string\n // values without throwing), and the key would disappear without\n // hitting the bad-value `console.warn` path. `Object.create(null)`\n // has no setter to intercept, so every key round-trips as a plain\n // own property.\n const normalized = Object.create(null) as Record<string, HexColor>;\n for (const [key, value] of Object.entries(p.colors)) {\n try {\n normalized[key] = rgbaToHex(toRgba(value));\n } catch {\n // Library-level diagnostic on user misconfiguration; intentional\n // so authors see the dropped key without blowing up the whole\n // module load.\n console.warn(\n `[chromonym] defineColorPalette(${JSON.stringify(p.name)}): ` +\n `skipping key ${JSON.stringify(key)}; expected a color, got ${describe(value)}`,\n );\n }\n }\n return {\n name: p.name,\n colors: normalized as Readonly<Record<Extract<keyof P['colors'], string>, HexColor>>,\n normalize: p.normalize,\n defaultMetric: p.defaultMetric,\n };\n}\n"]}
@@ -0,0 +1,103 @@
1
+ // BYO-palette authoring helper. See the companion test file for the full
2
+ // matrix of accepted value shapes; see the README "Bring your own palette"
3
+ // section for how callers are meant to reach for this.
4
+ //
5
+ // Why this exists: without it, a BYO palette needs
6
+ // `{ colors: {...}, ... } as const satisfies Palette<'Red' | 'Blue'>`
7
+ // which requires listing the key union twice and only accepts `HexColor`
8
+ // values. With this helper, callers pass any `ColorInput` value (hex, rgb
9
+ // string, hsl object, tuple, etc.) and the helper normalizes every entry
10
+ // to `HexColor` at define time, so downstream `identify`/`resolve`/`convert`
11
+ // and the indexing caches still see a uniform hex-only palette.
12
+ //
13
+ // Error policy: on an unparseable value we `console.warn` and drop the
14
+ // key rather than throw, so one bad entry doesn't sink a whole palette.
15
+ // The caller sees the warning with the palette name and the offending
16
+ // value; the resulting palette has the surviving entries and behaves
17
+ // normally.
18
+ import { rgbaToHex } from './conversions/hex.js';
19
+ import { toRgba } from './convert.js';
20
+ function describe(value) {
21
+ if (value === undefined)
22
+ return 'undefined';
23
+ if (value === null)
24
+ return 'null';
25
+ try {
26
+ const s = JSON.stringify(value);
27
+ return s ?? String(value);
28
+ }
29
+ catch {
30
+ return String(value);
31
+ }
32
+ }
33
+ /**
34
+ * Author a BYO palette with mixed-format color values. Values are
35
+ * normalized to {@link HexColor} at call time; unparseable entries are
36
+ * dropped with a `console.warn`.
37
+ *
38
+ * The `const` type parameter preserves the literal-key union so downstream
39
+ * `identify`/`resolve`/`convert` narrow their return types to the palette's
40
+ * actual keys, the same way the built-in palettes do.
41
+ *
42
+ * @example
43
+ * // Mixed input formats; all end up as HexColor in the returned palette.
44
+ * const brand = defineColorPalette({
45
+ * name: 'brand',
46
+ * colors: {
47
+ * 'Acme Red': '#ff0044',
48
+ * 'Acme Blue': 'rgb(0, 68, 255)',
49
+ * 'Acme Ink': { r: 20, g: 20, b: 30 },
50
+ * 'Acme Mist': [240, 244, 248],
51
+ * },
52
+ * normalize: (s) => s.toLowerCase().replace(/\s+/g, ''),
53
+ * defaultMetric: 'deltaE2000',
54
+ * });
55
+ *
56
+ * identify('#ff0000', { palette: brand });
57
+ * // typed as "'Acme Red' | 'Acme Blue' | 'Acme Ink' | 'Acme Mist' | null"
58
+ *
59
+ * @example
60
+ * // A bad value doesn't break the palette; it's dropped with a warning.
61
+ * const partial = defineColorPalette({
62
+ * name: 'partial',
63
+ * colors: { good: '#00ff00', bad: 'taco' as never },
64
+ * normalize: (s) => s,
65
+ * defaultMetric: 'deltaE76',
66
+ * });
67
+ * // console.warn: [chromonym] defineColorPalette("partial"): skipping key
68
+ * // "bad"; expected a color, got "taco"
69
+ * // partial.colors === { good: '#00ff00' }
70
+ */
71
+ export function defineColorPalette(p) {
72
+ // Prototype-free accumulator, not a plain `{}`. A caller hydrating
73
+ // `p.colors` from JSON (common for BYO palettes sourced from a CMS,
74
+ // a brand spec, etc.) can legitimately have a key like `__proto__`
75
+ // or `constructor`; palette data is just names and hex strings, so
76
+ // those names have no special meaning here. With `{}`, assigning
77
+ // `'#ff0000'` to `normalized['__proto__']` would be silently eaten
78
+ // by the `Object.prototype.__proto__` setter (which rejects string
79
+ // values without throwing), and the key would disappear without
80
+ // hitting the bad-value `console.warn` path. `Object.create(null)`
81
+ // has no setter to intercept, so every key round-trips as a plain
82
+ // own property.
83
+ const normalized = Object.create(null);
84
+ for (const [key, value] of Object.entries(p.colors)) {
85
+ try {
86
+ normalized[key] = rgbaToHex(toRgba(value));
87
+ }
88
+ catch {
89
+ // Library-level diagnostic on user misconfiguration; intentional
90
+ // so authors see the dropped key without blowing up the whole
91
+ // module load.
92
+ console.warn(`[chromonym] defineColorPalette(${JSON.stringify(p.name)}): ` +
93
+ `skipping key ${JSON.stringify(key)}; expected a color, got ${describe(value)}`);
94
+ }
95
+ }
96
+ return {
97
+ name: p.name,
98
+ colors: normalized,
99
+ normalize: p.normalize,
100
+ defaultMetric: p.defaultMetric,
101
+ };
102
+ }
103
+ //# sourceMappingURL=defineColorPalette.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defineColorPalette.js","sourceRoot":"","sources":["../src/defineColorPalette.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,2EAA2E;AAC3E,uDAAuD;AACvD,EAAE;AACF,mDAAmD;AACnD,wEAAwE;AACxE,yEAAyE;AACzE,0EAA0E;AAC1E,yEAAyE;AACzE,6EAA6E;AAC7E,gEAAgE;AAChE,EAAE;AACF,uEAAuE;AACvE,wEAAwE;AACxE,sEAAsE;AACtE,qEAAqE;AACrE,YAAY;AAEZ,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAgBtC,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,WAAW,CAAC;IAC5C,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QAChC,OAAO,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC;IACvB,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,MAAM,UAAU,kBAAkB,CAChC,CAAI;IAEJ,mEAAmE;IACnE,oEAAoE;IACpE,mEAAmE;IACnE,mEAAmE;IACnE,iEAAiE;IACjE,mEAAmE;IACnE,mEAAmE;IACnE,gEAAgE;IAChE,mEAAmE;IACnE,kEAAkE;IAClE,gBAAgB;IAChB,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAA6B,CAAC;IACnE,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC;YACH,UAAU,CAAC,GAAG,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;QAC7C,CAAC;QAAC,MAAM,CAAC;YACP,iEAAiE;YACjE,8DAA8D;YAC9D,eAAe;YACf,OAAO,CAAC,IAAI,CACV,kCAAkC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK;gBAC3D,gBAAgB,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,2BAA2B,QAAQ,CAAC,KAAK,CAAC,EAAE,CAClF,CAAC;QACJ,CAAC;IACH,CAAC;IACD,OAAO;QACL,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,MAAM,EAAE,UAA4E;QACpF,SAAS,EAAE,CAAC,CAAC,SAAS;QACtB,aAAa,EAAE,CAAC,CAAC,aAAa;KAC/B,CAAC;AACJ,CAAC","sourcesContent":["// BYO-palette authoring helper. See the companion test file for the full\n// matrix of accepted value shapes; see the README \"Bring your own palette\"\n// section for how callers are meant to reach for this.\n//\n// Why this exists: without it, a BYO palette needs\n// `{ colors: {...}, ... } as const satisfies Palette<'Red' | 'Blue'>`\n// which requires listing the key union twice and only accepts `HexColor`\n// values. With this helper, callers pass any `ColorInput` value (hex, rgb\n// string, hsl object, tuple, etc.) and the helper normalizes every entry\n// to `HexColor` at define time, so downstream `identify`/`resolve`/`convert`\n// and the indexing caches still see a uniform hex-only palette.\n//\n// Error policy: on an unparseable value we `console.warn` and drop the\n// key rather than throw, so one bad entry doesn't sink a whole palette.\n// The caller sees the warning with the palette name and the offending\n// value; the resulting palette has the surviving entries and behaves\n// normally.\n\nimport { rgbaToHex } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport type { ColorInput, DistanceMetric, HexColor, NormalizeFn, Palette } from './types.js';\n\n/**\n * The authoring shape accepted by {@link defineColorPalette}: same as\n * {@link Palette}, except `colors` values may be any {@link ColorInput}\n * rather than requiring {@link HexColor}. Exported mainly for\n * type-level inspection; most callers should not reference it directly.\n */\nexport type DefineColorPaletteInput = {\n readonly name: string;\n readonly colors: Readonly<Record<string, ColorInput>>;\n readonly normalize: NormalizeFn;\n readonly defaultMetric: DistanceMetric;\n};\n\nfunction describe(value: unknown): string {\n if (value === undefined) return 'undefined';\n if (value === null) return 'null';\n try {\n const s = JSON.stringify(value);\n return s ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/**\n * Author a BYO palette with mixed-format color values. Values are\n * normalized to {@link HexColor} at call time; unparseable entries are\n * dropped with a `console.warn`.\n *\n * The `const` type parameter preserves the literal-key union so downstream\n * `identify`/`resolve`/`convert` narrow their return types to the palette's\n * actual keys, the same way the built-in palettes do.\n *\n * @example\n * // Mixed input formats; all end up as HexColor in the returned palette.\n * const brand = defineColorPalette({\n * name: 'brand',\n * colors: {\n * 'Acme Red': '#ff0044',\n * 'Acme Blue': 'rgb(0, 68, 255)',\n * 'Acme Ink': { r: 20, g: 20, b: 30 },\n * 'Acme Mist': [240, 244, 248],\n * },\n * normalize: (s) => s.toLowerCase().replace(/\\s+/g, ''),\n * defaultMetric: 'deltaE2000',\n * });\n *\n * identify('#ff0000', { palette: brand });\n * // typed as \"'Acme Red' | 'Acme Blue' | 'Acme Ink' | 'Acme Mist' | null\"\n *\n * @example\n * // A bad value doesn't break the palette; it's dropped with a warning.\n * const partial = defineColorPalette({\n * name: 'partial',\n * colors: { good: '#00ff00', bad: 'taco' as never },\n * normalize: (s) => s,\n * defaultMetric: 'deltaE76',\n * });\n * // console.warn: [chromonym] defineColorPalette(\"partial\"): skipping key\n * // \"bad\"; expected a color, got \"taco\"\n * // partial.colors === { good: '#00ff00' }\n */\nexport function defineColorPalette<const P extends DefineColorPaletteInput>(\n p: P,\n): Palette<Extract<keyof P['colors'], string>> {\n // Prototype-free accumulator, not a plain `{}`. A caller hydrating\n // `p.colors` from JSON (common for BYO palettes sourced from a CMS,\n // a brand spec, etc.) can legitimately have a key like `__proto__`\n // or `constructor`; palette data is just names and hex strings, so\n // those names have no special meaning here. With `{}`, assigning\n // `'#ff0000'` to `normalized['__proto__']` would be silently eaten\n // by the `Object.prototype.__proto__` setter (which rejects string\n // values without throwing), and the key would disappear without\n // hitting the bad-value `console.warn` path. `Object.create(null)`\n // has no setter to intercept, so every key round-trips as a plain\n // own property.\n const normalized = Object.create(null) as Record<string, HexColor>;\n for (const [key, value] of Object.entries(p.colors)) {\n try {\n normalized[key] = rgbaToHex(toRgba(value));\n } catch {\n // Library-level diagnostic on user misconfiguration; intentional\n // so authors see the dropped key without blowing up the whole\n // module load.\n console.warn(\n `[chromonym] defineColorPalette(${JSON.stringify(p.name)}): ` +\n `skipping key ${JSON.stringify(key)}; expected a color, got ${describe(value)}`,\n );\n }\n }\n return {\n name: p.name,\n colors: normalized as Readonly<Record<Extract<keyof P['colors'], string>, HexColor>>,\n normalize: p.normalize,\n defaultMetric: p.defaultMetric,\n };\n}\n"]}
@@ -1,8 +1,11 @@
1
1
  import type { ColorFormat, ColorInput } from './types.js';
2
2
  /**
3
- * A color comes in, a format key comes out.
4
- * 'UNKNOWN' signals the caller that the input isn't a recognized color shape.
5
- * Used as the dispatch key for format-specific converters.
3
+ * Return type of {@link detectFormat}. Either a {@link ColorFormat} key
4
+ * (`'HEX'` / `'RGB'` / `'RGBA'` / `'HSL'` / `'HSV'`) or the sentinel
5
+ * string `'UNKNOWN'` when the input doesn't match any recognized color
6
+ * shape. The sentinel is the dispatch miss condition for
7
+ * format-specific converters; prefer {@link isColor} when you only
8
+ * need a boolean guard.
6
9
  */
7
10
  export type DetectedFormat = ColorFormat | 'UNKNOWN';
8
11
  /** Shared with `src/conversions/hex.ts`. Capture group is used there; .test() ignores it here. */
@@ -13,5 +16,25 @@ export declare const HEX_RE: RegExp;
13
16
  * and more ergonomic than hand-rolling `detectFormat(x) !== 'UNKNOWN'`.
14
17
  */
15
18
  export declare function isColor(input: unknown): input is ColorInput;
19
+ /**
20
+ * Classify a color input by shape, returning the {@link ColorFormat}
21
+ * key used to dispatch format-specific converters. Returns the
22
+ * sentinel `'UNKNOWN'` when the input doesn't match any recognized
23
+ * shape; callers that only need a boolean guard should prefer
24
+ * {@link isColor} instead.
25
+ *
26
+ * Pure shape detection; no value validation. `detectFormat('#zzz')`
27
+ * still returns `'UNKNOWN'` because the hex regex rejects it, but
28
+ * `detectFormat({ r: 999, g: -1, b: 'nope' })` returns `'RGB'` (the
29
+ * shape matches; the values are the converter's problem).
30
+ *
31
+ * @example
32
+ * detectFormat('#ff0000'); // 'HEX'
33
+ * detectFormat('rgb(255, 0, 0)'); // 'RGB'
34
+ * detectFormat([255, 0, 0]); // 'RGB'
35
+ * detectFormat([255, 0, 0, 0.5]); // 'RGBA'
36
+ * detectFormat({ h: 0, s: 100, l: 50 });// 'HSL'
37
+ * detectFormat('banana'); // 'UNKNOWN'
38
+ */
16
39
  export declare function detectFormat(input: ColorInput): DetectedFormat;
17
40
  //# sourceMappingURL=detectFormat.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"detectFormat.d.ts","sourceRoot":"","sources":["../src/detectFormat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,SAAS,CAAC;AAErD,kGAAkG;AAClG,eAAO,MAAM,MAAM,QAA8C,CAAC;AAMlE;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAE3D;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CA8B9D","sourcesContent":["import type { ColorFormat, ColorInput } from './types.js';\n\n/**\n * A color comes in, a format key comes out.\n * 'UNKNOWN' signals the caller that the input isn't a recognized color shape.\n * Used as the dispatch key for format-specific converters.\n */\nexport type DetectedFormat = ColorFormat | 'UNKNOWN';\n\n/** Shared with `src/conversions/hex.ts`. Capture group is used there; .test() ignores it here. */\nexport const HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst RGBA_STR_RE = /^rgba\\s*\\(/i;\nconst RGB_STR_RE = /^rgb\\s*\\(/i;\nconst HSL_STR_RE = /^hsl\\s*\\(/i;\nconst HSV_STR_RE = /^hsv\\s*\\(/i;\n\n/**\n * Runtime type-guard — returns `true` if `input` looks like something any of\n * chromonym's converters can parse. Cheaper than catching a `convert` throw\n * and more ergonomic than hand-rolling `detectFormat(x) !== 'UNKNOWN'`.\n */\nexport function isColor(input: unknown): input is ColorInput {\n return detectFormat(input as ColorInput) !== 'UNKNOWN';\n}\n\nexport function detectFormat(input: ColorInput): DetectedFormat {\n if (typeof input === 'string') {\n // Runtime guard against JS callers that bypass the string template union.\n if ((input as string).length === 0) return 'UNKNOWN';\n if (HEX_RE.test(input)) return 'HEX';\n if (RGBA_STR_RE.test(input)) return 'RGBA';\n if (RGB_STR_RE.test(input)) return 'RGB';\n if (HSL_STR_RE.test(input)) return 'HSL';\n if (HSV_STR_RE.test(input)) return 'HSV';\n return 'UNKNOWN';\n }\n\n if (Array.isArray(input)) {\n if (input.length === 4) return 'RGBA';\n if (input.length === 3) return 'RGB';\n return 'UNKNOWN';\n }\n\n if (input !== null && typeof input === 'object') {\n // Use Object.hasOwn — `in` walks the prototype chain and can be\n // abused by attacker-controlled prototypes to misclassify inputs.\n const has = Object.hasOwn;\n if (has(input, 'h') && has(input, 'v')) return 'HSV';\n if (has(input, 'h') && has(input, 'l')) return 'HSL';\n if (has(input, 'r') && has(input, 'a')) return 'RGBA';\n if (has(input, 'r')) return 'RGB';\n return 'UNKNOWN';\n }\n\n return 'UNKNOWN';\n}\n"]}
1
+ {"version":3,"file":"detectFormat.d.ts","sourceRoot":"","sources":["../src/detectFormat.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE1D;;;;;;;GAOG;AACH,MAAM,MAAM,cAAc,GAAG,WAAW,GAAG,SAAS,CAAC;AAErD,kGAAkG;AAClG,eAAO,MAAM,MAAM,QAA8C,CAAC;AAMlE;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,UAAU,CAE3D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,CA8B9D","sourcesContent":["import type { ColorFormat, ColorInput } from './types.js';\n\n/**\n * Return type of {@link detectFormat}. Either a {@link ColorFormat} key\n * (`'HEX'` / `'RGB'` / `'RGBA'` / `'HSL'` / `'HSV'`) or the sentinel\n * string `'UNKNOWN'` when the input doesn't match any recognized color\n * shape. The sentinel is the dispatch miss condition for\n * format-specific converters; prefer {@link isColor} when you only\n * need a boolean guard.\n */\nexport type DetectedFormat = ColorFormat | 'UNKNOWN';\n\n/** Shared with `src/conversions/hex.ts`. Capture group is used there; .test() ignores it here. */\nexport const HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst RGBA_STR_RE = /^rgba\\s*\\(/i;\nconst RGB_STR_RE = /^rgb\\s*\\(/i;\nconst HSL_STR_RE = /^hsl\\s*\\(/i;\nconst HSV_STR_RE = /^hsv\\s*\\(/i;\n\n/**\n * Runtime type-guard — returns `true` if `input` looks like something any of\n * chromonym's converters can parse. Cheaper than catching a `convert` throw\n * and more ergonomic than hand-rolling `detectFormat(x) !== 'UNKNOWN'`.\n */\nexport function isColor(input: unknown): input is ColorInput {\n return detectFormat(input as ColorInput) !== 'UNKNOWN';\n}\n\n/**\n * Classify a color input by shape, returning the {@link ColorFormat}\n * key used to dispatch format-specific converters. Returns the\n * sentinel `'UNKNOWN'` when the input doesn't match any recognized\n * shape; callers that only need a boolean guard should prefer\n * {@link isColor} instead.\n *\n * Pure shape detection; no value validation. `detectFormat('#zzz')`\n * still returns `'UNKNOWN'` because the hex regex rejects it, but\n * `detectFormat({ r: 999, g: -1, b: 'nope' })` returns `'RGB'` (the\n * shape matches; the values are the converter's problem).\n *\n * @example\n * detectFormat('#ff0000'); // 'HEX'\n * detectFormat('rgb(255, 0, 0)'); // 'RGB'\n * detectFormat([255, 0, 0]); // 'RGB'\n * detectFormat([255, 0, 0, 0.5]); // 'RGBA'\n * detectFormat({ h: 0, s: 100, l: 50 });// 'HSL'\n * detectFormat('banana'); // 'UNKNOWN'\n */\nexport function detectFormat(input: ColorInput): DetectedFormat {\n if (typeof input === 'string') {\n // Runtime guard against JS callers that bypass the string template union.\n if ((input as string).length === 0) return 'UNKNOWN';\n if (HEX_RE.test(input)) return 'HEX';\n if (RGBA_STR_RE.test(input)) return 'RGBA';\n if (RGB_STR_RE.test(input)) return 'RGB';\n if (HSL_STR_RE.test(input)) return 'HSL';\n if (HSV_STR_RE.test(input)) return 'HSV';\n return 'UNKNOWN';\n }\n\n if (Array.isArray(input)) {\n if (input.length === 4) return 'RGBA';\n if (input.length === 3) return 'RGB';\n return 'UNKNOWN';\n }\n\n if (input !== null && typeof input === 'object') {\n // Use Object.hasOwn — `in` walks the prototype chain and can be\n // abused by attacker-controlled prototypes to misclassify inputs.\n const has = Object.hasOwn;\n if (has(input, 'h') && has(input, 'v')) return 'HSV';\n if (has(input, 'h') && has(input, 'l')) return 'HSL';\n if (has(input, 'r') && has(input, 'a')) return 'RGBA';\n if (has(input, 'r')) return 'RGB';\n return 'UNKNOWN';\n }\n\n return 'UNKNOWN';\n}\n"]}
@@ -12,6 +12,26 @@ const HSV_STR_RE = /^hsv\s*\(/i;
12
12
  export function isColor(input) {
13
13
  return detectFormat(input) !== 'UNKNOWN';
14
14
  }
15
+ /**
16
+ * Classify a color input by shape, returning the {@link ColorFormat}
17
+ * key used to dispatch format-specific converters. Returns the
18
+ * sentinel `'UNKNOWN'` when the input doesn't match any recognized
19
+ * shape; callers that only need a boolean guard should prefer
20
+ * {@link isColor} instead.
21
+ *
22
+ * Pure shape detection; no value validation. `detectFormat('#zzz')`
23
+ * still returns `'UNKNOWN'` because the hex regex rejects it, but
24
+ * `detectFormat({ r: 999, g: -1, b: 'nope' })` returns `'RGB'` (the
25
+ * shape matches; the values are the converter's problem).
26
+ *
27
+ * @example
28
+ * detectFormat('#ff0000'); // 'HEX'
29
+ * detectFormat('rgb(255, 0, 0)'); // 'RGB'
30
+ * detectFormat([255, 0, 0]); // 'RGB'
31
+ * detectFormat([255, 0, 0, 0.5]); // 'RGBA'
32
+ * detectFormat({ h: 0, s: 100, l: 50 });// 'HSL'
33
+ * detectFormat('banana'); // 'UNKNOWN'
34
+ */
15
35
  export function detectFormat(input) {
16
36
  if (typeof input === 'string') {
17
37
  // Runtime guard against JS callers that bypass the string template union.
@@ -1 +1 @@
1
- {"version":3,"file":"detectFormat.js","sourceRoot":"","sources":["../src/detectFormat.ts"],"names":[],"mappings":"AASA,kGAAkG;AAClG,MAAM,CAAC,MAAM,MAAM,GAAG,2CAA2C,CAAC;AAClE,MAAM,WAAW,GAAG,aAAa,CAAC;AAClC,MAAM,UAAU,GAAG,YAAY,CAAC;AAChC,MAAM,UAAU,GAAG,YAAY,CAAC;AAChC,MAAM,UAAU,GAAG,YAAY,CAAC;AAEhC;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,YAAY,CAAC,KAAmB,CAAC,KAAK,SAAS,CAAC;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,0EAA0E;QAC1E,IAAK,KAAgB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACrD,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAC3C,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,gEAAgE;QAChE,kEAAkE;QAClE,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1B,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;QACtD,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import type { ColorFormat, ColorInput } from './types.js';\n\n/**\n * A color comes in, a format key comes out.\n * 'UNKNOWN' signals the caller that the input isn't a recognized color shape.\n * Used as the dispatch key for format-specific converters.\n */\nexport type DetectedFormat = ColorFormat | 'UNKNOWN';\n\n/** Shared with `src/conversions/hex.ts`. Capture group is used there; .test() ignores it here. */\nexport const HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst RGBA_STR_RE = /^rgba\\s*\\(/i;\nconst RGB_STR_RE = /^rgb\\s*\\(/i;\nconst HSL_STR_RE = /^hsl\\s*\\(/i;\nconst HSV_STR_RE = /^hsv\\s*\\(/i;\n\n/**\n * Runtime type-guard — returns `true` if `input` looks like something any of\n * chromonym's converters can parse. Cheaper than catching a `convert` throw\n * and more ergonomic than hand-rolling `detectFormat(x) !== 'UNKNOWN'`.\n */\nexport function isColor(input: unknown): input is ColorInput {\n return detectFormat(input as ColorInput) !== 'UNKNOWN';\n}\n\nexport function detectFormat(input: ColorInput): DetectedFormat {\n if (typeof input === 'string') {\n // Runtime guard against JS callers that bypass the string template union.\n if ((input as string).length === 0) return 'UNKNOWN';\n if (HEX_RE.test(input)) return 'HEX';\n if (RGBA_STR_RE.test(input)) return 'RGBA';\n if (RGB_STR_RE.test(input)) return 'RGB';\n if (HSL_STR_RE.test(input)) return 'HSL';\n if (HSV_STR_RE.test(input)) return 'HSV';\n return 'UNKNOWN';\n }\n\n if (Array.isArray(input)) {\n if (input.length === 4) return 'RGBA';\n if (input.length === 3) return 'RGB';\n return 'UNKNOWN';\n }\n\n if (input !== null && typeof input === 'object') {\n // Use Object.hasOwn — `in` walks the prototype chain and can be\n // abused by attacker-controlled prototypes to misclassify inputs.\n const has = Object.hasOwn;\n if (has(input, 'h') && has(input, 'v')) return 'HSV';\n if (has(input, 'h') && has(input, 'l')) return 'HSL';\n if (has(input, 'r') && has(input, 'a')) return 'RGBA';\n if (has(input, 'r')) return 'RGB';\n return 'UNKNOWN';\n }\n\n return 'UNKNOWN';\n}\n"]}
1
+ {"version":3,"file":"detectFormat.js","sourceRoot":"","sources":["../src/detectFormat.ts"],"names":[],"mappings":"AAYA,kGAAkG;AAClG,MAAM,CAAC,MAAM,MAAM,GAAG,2CAA2C,CAAC;AAClE,MAAM,WAAW,GAAG,aAAa,CAAC;AAClC,MAAM,UAAU,GAAG,YAAY,CAAC;AAChC,MAAM,UAAU,GAAG,YAAY,CAAC;AAChC,MAAM,UAAU,GAAG,YAAY,CAAC;AAEhC;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,OAAO,YAAY,CAAC,KAAmB,CAAC,KAAK,SAAS,CAAC;AACzD,CAAC;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,0EAA0E;QAC1E,IAAK,KAAgB,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QACrD,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,WAAW,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QAC3C,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,IAAI,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACzC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,MAAM,CAAC;QACtC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,gEAAgE;QAChE,kEAAkE;QAClE,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC;QAC1B,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrD,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;QACtD,IAAI,GAAG,CAAC,KAAK,EAAE,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QAClC,OAAO,SAAS,CAAC;IACnB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC","sourcesContent":["import type { ColorFormat, ColorInput } from './types.js';\n\n/**\n * Return type of {@link detectFormat}. Either a {@link ColorFormat} key\n * (`'HEX'` / `'RGB'` / `'RGBA'` / `'HSL'` / `'HSV'`) or the sentinel\n * string `'UNKNOWN'` when the input doesn't match any recognized color\n * shape. The sentinel is the dispatch miss condition for\n * format-specific converters; prefer {@link isColor} when you only\n * need a boolean guard.\n */\nexport type DetectedFormat = ColorFormat | 'UNKNOWN';\n\n/** Shared with `src/conversions/hex.ts`. Capture group is used there; .test() ignores it here. */\nexport const HEX_RE = /^#([0-9a-f]{3}|[0-9a-f]{6}|[0-9a-f]{8})$/i;\nconst RGBA_STR_RE = /^rgba\\s*\\(/i;\nconst RGB_STR_RE = /^rgb\\s*\\(/i;\nconst HSL_STR_RE = /^hsl\\s*\\(/i;\nconst HSV_STR_RE = /^hsv\\s*\\(/i;\n\n/**\n * Runtime type-guard — returns `true` if `input` looks like something any of\n * chromonym's converters can parse. Cheaper than catching a `convert` throw\n * and more ergonomic than hand-rolling `detectFormat(x) !== 'UNKNOWN'`.\n */\nexport function isColor(input: unknown): input is ColorInput {\n return detectFormat(input as ColorInput) !== 'UNKNOWN';\n}\n\n/**\n * Classify a color input by shape, returning the {@link ColorFormat}\n * key used to dispatch format-specific converters. Returns the\n * sentinel `'UNKNOWN'` when the input doesn't match any recognized\n * shape; callers that only need a boolean guard should prefer\n * {@link isColor} instead.\n *\n * Pure shape detection; no value validation. `detectFormat('#zzz')`\n * still returns `'UNKNOWN'` because the hex regex rejects it, but\n * `detectFormat({ r: 999, g: -1, b: 'nope' })` returns `'RGB'` (the\n * shape matches; the values are the converter's problem).\n *\n * @example\n * detectFormat('#ff0000'); // 'HEX'\n * detectFormat('rgb(255, 0, 0)'); // 'RGB'\n * detectFormat([255, 0, 0]); // 'RGB'\n * detectFormat([255, 0, 0, 0.5]); // 'RGBA'\n * detectFormat({ h: 0, s: 100, l: 50 });// 'HSL'\n * detectFormat('banana'); // 'UNKNOWN'\n */\nexport function detectFormat(input: ColorInput): DetectedFormat {\n if (typeof input === 'string') {\n // Runtime guard against JS callers that bypass the string template union.\n if ((input as string).length === 0) return 'UNKNOWN';\n if (HEX_RE.test(input)) return 'HEX';\n if (RGBA_STR_RE.test(input)) return 'RGBA';\n if (RGB_STR_RE.test(input)) return 'RGB';\n if (HSL_STR_RE.test(input)) return 'HSL';\n if (HSV_STR_RE.test(input)) return 'HSV';\n return 'UNKNOWN';\n }\n\n if (Array.isArray(input)) {\n if (input.length === 4) return 'RGBA';\n if (input.length === 3) return 'RGB';\n return 'UNKNOWN';\n }\n\n if (input !== null && typeof input === 'object') {\n // Use Object.hasOwn — `in` walks the prototype chain and can be\n // abused by attacker-controlled prototypes to misclassify inputs.\n const has = Object.hasOwn;\n if (has(input, 'h') && has(input, 'v')) return 'HSV';\n if (has(input, 'h') && has(input, 'l')) return 'HSL';\n if (has(input, 'r') && has(input, 'a')) return 'RGBA';\n if (has(input, 'r')) return 'RGB';\n return 'UNKNOWN';\n }\n\n return 'UNKNOWN';\n}\n"]}
@@ -1,18 +1,65 @@
1
1
  import { web } from './palettes/web.js';
2
- import type { ColorInput, DistanceMetric, HexColor, Palette } from './types.js';
3
- /** Extract string keys from a Palette's `colors` map. */
4
- type PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;
2
+ import type { ColorInput, DistanceMetric, HexColor, Palette, PaletteKey } from './types.js';
5
3
  /**
6
4
  * One ranked nearest-match entry when `identify` is called with `k`.
7
5
  * `value` is the palette's stored hex for the match (useful for rendering
8
- * swatches). `distance` is in the selected metric's natural units — ΔE
9
- * for `deltaE*` metrics, channel-unit Euclidean for the others.
6
+ * swatches). `distance` is in the selected metric's natural units: ΔE for
7
+ * `deltaE*` metrics, channel-unit Euclidean for the others.
10
8
  */
11
9
  export type IdentifyMatch<Name extends string> = {
12
10
  readonly name: Name;
13
11
  readonly value: HexColor;
14
12
  readonly distance: number;
15
13
  };
14
+ /**
15
+ * Options for {@link identify}. Generic over the target palette so the
16
+ * return type narrows to its literal-key union.
17
+ *
18
+ * @example
19
+ * identify('#ff0000', { palette: pantone, metric: 'deltaE2000' });
20
+ * identify('rebeccapurple', { palette: x11, source: web });
21
+ */
22
+ export type IdentifyOptions<P extends Palette = typeof web> = {
23
+ /**
24
+ * Target palette to search in. Defaults to `web` (CSS named colors).
25
+ */
26
+ readonly palette?: P;
27
+ /**
28
+ * Source palette for cross-palette name input: when `input` is a name
29
+ * from a different palette (e.g. `'rebeccapurple'` when `palette` is
30
+ * `pantone`), set `source` to the palette that owns the name so it can
31
+ * be resolved to an Rgba before the nearest-match lookup.
32
+ *
33
+ * @remarks
34
+ * The `input` argument isn't validated against `source`'s keys at
35
+ * compile time; unknown names resolve to `null` at runtime.
36
+ */
37
+ readonly source?: Palette;
38
+ /**
39
+ * Override the distance metric. Defaults to `palette.defaultMetric`
40
+ * (web/x11: `'deltaE76'`, pantone: `'deltaE2000'`, crayola: `'deltaEok'`).
41
+ */
42
+ readonly metric?: DistanceMetric;
43
+ };
44
+ /**
45
+ * Options for the ranked top-k variant of {@link identify}. Returns an
46
+ * array of {@link IdentifyMatch} entries instead of a single name.
47
+ *
48
+ * @example
49
+ * identify('#ff0080', { palette: pantone, k: 3 });
50
+ * // [
51
+ * // { name: '219 C', value: '#da1884', distance: 2.1 },
52
+ * // { name: '226 C', value: '#d0006f', distance: 3.9 },
53
+ * // { name: 'Red 032 C',value: '#ef3340', distance: 5.4 },
54
+ * // ]
55
+ */
56
+ export type IdentifyRankedOptions<P extends Palette = typeof web> = IdentifyOptions<P> & {
57
+ /**
58
+ * Number of ranked matches to return. When present, `identify`
59
+ * returns `IdentifyMatch[]` instead of a single key.
60
+ */
61
+ readonly k: number;
62
+ };
16
63
  /**
17
64
  * Identify the nearest-named color for any color input (or palette name
18
65
  * from another palette via `source`).
@@ -33,18 +80,17 @@ export type IdentifyMatch<Name extends string> = {
33
80
  * (web/x11: `deltaE76`, pantone: `deltaE2000`, crayola: `deltaEok`).
34
81
  * Override with `metric`.
35
82
  *
83
+ * Tie-breaking: on exact-distance ties, the first-declared entry in
84
+ * the palette's `colors` object wins. Deterministic across metrics
85
+ * and across the single-match / top-k paths.
86
+ *
87
+ * `k` is forgiving: fractional values round, negatives and zero
88
+ * return `[]`, and `NaN` / `±Infinity` return `[]` too. No throws on
89
+ * invalid `k` — callers can safely pipe a slider value in without
90
+ * pre-validating.
91
+ *
36
92
  * Returns `null` (or `[]` when `k` is set) if the input can't be parsed.
37
93
  */
38
- export declare function identify<P extends Palette = typeof web>(input: ColorInput | string, opts?: {
39
- palette?: P;
40
- source?: Palette;
41
- metric?: DistanceMetric;
42
- }): PaletteKey<P> | null;
43
- export declare function identify<P extends Palette = typeof web>(input: ColorInput | string, opts: {
44
- palette?: P;
45
- source?: Palette;
46
- metric?: DistanceMetric;
47
- k: number;
48
- }): Array<IdentifyMatch<PaletteKey<P>>>;
49
- export {};
94
+ export declare function identify<P extends Palette = typeof web>(input: ColorInput | string, opts?: IdentifyOptions<P>): PaletteKey<P> | null;
95
+ export declare function identify<P extends Palette = typeof web>(input: ColorInput | string, opts: IdentifyRankedOptions<P>): Array<IdentifyMatch<PaletteKey<P>>>;
50
96
  //# sourceMappingURL=identify.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"identify.d.ts","sourceRoot":"","sources":["../src/identify.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAQ,MAAM,YAAY,CAAC;AAEtF,yDAAyD;AACzD,KAAK,UAAU,CAAC,CAAC,SAAS,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAC;AAExE;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,IAAI,SAAS,MAAM,IAAI;IAC/C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAmBF;;;;;;;;;;;;;;;;;;;;;GAqBG;AAGH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,GAAG,EACrD,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,cAAc,CAAA;CAAE,GAChE,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AAGxB,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,GAAG,EACrD,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,EAAE;IAAE,OAAO,CAAC,EAAE,CAAC,CAAC;IAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1E,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport { detectFormat } from './detectFormat.js';\nimport { getNameIndex, nearest, nearestAll } from './indexing.js';\nimport { web } from './palettes/web.js';\nimport type { ColorInput, DistanceMetric, HexColor, Palette, Rgba } from './types.js';\n\n/** Extract string keys from a Palette's `colors` map. */\ntype PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;\n\n/**\n * One ranked nearest-match entry when `identify` is called with `k`.\n * `value` is the palette's stored hex for the match (useful for rendering\n * swatches). `distance` is in the selected metric's natural units — ΔE\n * for `deltaE*` metrics, channel-unit Euclidean for the others.\n */\nexport type IdentifyMatch<Name extends string> = {\n readonly name: Name;\n readonly value: HexColor;\n readonly distance: number;\n};\n\n/**\n * Parse a string input that may be a color literal or a palette name.\n * Returns the canonical Rgba, or `null` if nothing matches.\n */\nfunction parseInput(input: ColorInput | string, source?: Palette): Rgba | null {\n const detected = detectFormat(input as ColorInput);\n if (detected !== 'UNKNOWN') return toRgba(input as ColorInput, detected);\n if (source !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(source).get(source.normalize(input));\n if (canonical === undefined) return null;\n const hex = (source.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) return null;\n return hexToRgba(hex);\n }\n return null;\n}\n\n/**\n * Identify the nearest-named color for any color input (or palette name\n * from another palette via `source`).\n *\n * **Simple case — color in, name out:**\n * identify('#ff0000') → 'red'\n * identify('#ff0000', { palette: pantone }) → '172 C'\n *\n * **Cross-palette — name from one palette, nearest in another:**\n * identify('rebeccapurple', { palette: pantone, source: web }) → '267 C'\n * identify('Razzmatazz', { palette: pantone, source: crayola }) → '213 C'\n *\n * **Top-k ranked matches with ΔE distances:**\n * identify('#ff0000', { palette: pantone, k: 3 })\n * // [{ name: '172 C', value: '#fa4616', distance: 3.2 }, ...]\n *\n * The distance metric defaults to the target palette's `defaultMetric`\n * (web/x11: `deltaE76`, pantone: `deltaE2000`, crayola: `deltaEok`).\n * Override with `metric`.\n *\n * Returns `null` (or `[]` when `k` is set) if the input can't be parsed.\n */\n\n// Overload 1: no `k` — single name (or null). Unchanged legacy shape.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts?: { palette?: P; source?: Palette; metric?: DistanceMetric },\n): PaletteKey<P> | null;\n\n// Overload 2: `k` present — ranked Match[] with distances.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts: { palette?: P; source?: Palette; metric?: DistanceMetric; k: number },\n): Array<IdentifyMatch<PaletteKey<P>>>;\n\n// Implementation.\nexport function identify(\n input: ColorInput | string,\n opts: { palette?: Palette; source?: Palette; metric?: DistanceMetric; k?: number } = {},\n): string | null | Array<IdentifyMatch<string>> {\n const palette = opts.palette ?? web;\n const metric = opts.metric ?? palette.defaultMetric;\n const rgba = parseInput(input, opts.source);\n\n if (opts.k !== undefined) {\n if (rgba === null) return [];\n const raw = nearestAll(rgba, palette, metric, opts.k);\n return raw.map((m) => {\n const value = (palette.colors as Record<string, HexColor>)[m.name];\n return { name: m.name, value: value as HexColor, distance: m.distance };\n });\n }\n\n if (rgba === null) return null;\n const name = nearest(rgba, palette, metric);\n return name || null;\n}\n"]}
1
+ {"version":3,"file":"identify.d.ts","sourceRoot":"","sources":["../src/identify.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AACxC,OAAO,KAAK,EAAE,UAAU,EAAE,cAAc,EAAE,QAAQ,EAAE,OAAO,EAAE,UAAU,EAAQ,MAAM,YAAY,CAAC;AAElG;;;;;GAKG;AACH,MAAM,MAAM,aAAa,CAAC,IAAI,SAAS,MAAM,IAAI;IAC/C,QAAQ,CAAC,IAAI,EAAE,IAAI,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CAC3B,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,GAAG,IAAI;IAC5D;;OAEG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IACrB;;;;;;;;;OASG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B;;;OAGG;IACH,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;CAClC,CAAC;AAEF;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,qBAAqB,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,GAAG,IAAI,eAAe,CAAC,CAAC,CAAC,GAAG;IACvF;;;OAGG;IACH,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAmBF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAYH,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,GAAG,EACrD,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,CAAC,EAAE,eAAe,CAAC,CAAC,CAAC,GACxB,UAAU,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;AAGxB,wBAAgB,QAAQ,CAAC,CAAC,SAAS,OAAO,GAAG,OAAO,GAAG,EACrD,KAAK,EAAE,UAAU,GAAG,MAAM,EAC1B,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC7B,KAAK,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport { detectFormat } from './detectFormat.js';\nimport { getNameIndex, nearest, nearestAll } from './indexing.js';\nimport { web } from './palettes/web.js';\nimport type { ColorInput, DistanceMetric, HexColor, Palette, PaletteKey, Rgba } from './types.js';\n\n/**\n * One ranked nearest-match entry when `identify` is called with `k`.\n * `value` is the palette's stored hex for the match (useful for rendering\n * swatches). `distance` is in the selected metric's natural units: ΔE for\n * `deltaE*` metrics, channel-unit Euclidean for the others.\n */\nexport type IdentifyMatch<Name extends string> = {\n readonly name: Name;\n readonly value: HexColor;\n readonly distance: number;\n};\n\n/**\n * Options for {@link identify}. Generic over the target palette so the\n * return type narrows to its literal-key union.\n *\n * @example\n * identify('#ff0000', { palette: pantone, metric: 'deltaE2000' });\n * identify('rebeccapurple', { palette: x11, source: web });\n */\nexport type IdentifyOptions<P extends Palette = typeof web> = {\n /**\n * Target palette to search in. Defaults to `web` (CSS named colors).\n */\n readonly palette?: P;\n /**\n * Source palette for cross-palette name input: when `input` is a name\n * from a different palette (e.g. `'rebeccapurple'` when `palette` is\n * `pantone`), set `source` to the palette that owns the name so it can\n * be resolved to an Rgba before the nearest-match lookup.\n *\n * @remarks\n * The `input` argument isn't validated against `source`'s keys at\n * compile time; unknown names resolve to `null` at runtime.\n */\n readonly source?: Palette;\n /**\n * Override the distance metric. Defaults to `palette.defaultMetric`\n * (web/x11: `'deltaE76'`, pantone: `'deltaE2000'`, crayola: `'deltaEok'`).\n */\n readonly metric?: DistanceMetric;\n};\n\n/**\n * Options for the ranked top-k variant of {@link identify}. Returns an\n * array of {@link IdentifyMatch} entries instead of a single name.\n *\n * @example\n * identify('#ff0080', { palette: pantone, k: 3 });\n * // [\n * // { name: '219 C', value: '#da1884', distance: 2.1 },\n * // { name: '226 C', value: '#d0006f', distance: 3.9 },\n * // { name: 'Red 032 C',value: '#ef3340', distance: 5.4 },\n * // ]\n */\nexport type IdentifyRankedOptions<P extends Palette = typeof web> = IdentifyOptions<P> & {\n /**\n * Number of ranked matches to return. When present, `identify`\n * returns `IdentifyMatch[]` instead of a single key.\n */\n readonly k: number;\n};\n\n/**\n * Parse a string input that may be a color literal or a palette name.\n * Returns the canonical Rgba, or `null` if nothing matches.\n */\nfunction parseInput(input: ColorInput | string, source?: Palette): Rgba | null {\n const detected = detectFormat(input as ColorInput);\n if (detected !== 'UNKNOWN') return toRgba(input as ColorInput, detected);\n if (source !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(source).get(source.normalize(input));\n if (canonical === undefined) return null;\n const hex = (source.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) return null;\n return hexToRgba(hex);\n }\n return null;\n}\n\n/**\n * Identify the nearest-named color for any color input (or palette name\n * from another palette via `source`).\n *\n * **Simple case — color in, name out:**\n * identify('#ff0000') → 'red'\n * identify('#ff0000', { palette: pantone }) → '172 C'\n *\n * **Cross-palette — name from one palette, nearest in another:**\n * identify('rebeccapurple', { palette: pantone, source: web }) → '267 C'\n * identify('Razzmatazz', { palette: pantone, source: crayola }) → '213 C'\n *\n * **Top-k ranked matches with ΔE distances:**\n * identify('#ff0000', { palette: pantone, k: 3 })\n * // [{ name: '172 C', value: '#fa4616', distance: 3.2 }, ...]\n *\n * The distance metric defaults to the target palette's `defaultMetric`\n * (web/x11: `deltaE76`, pantone: `deltaE2000`, crayola: `deltaEok`).\n * Override with `metric`.\n *\n * Tie-breaking: on exact-distance ties, the first-declared entry in\n * the palette's `colors` object wins. Deterministic across metrics\n * and across the single-match / top-k paths.\n *\n * `k` is forgiving: fractional values round, negatives and zero\n * return `[]`, and `NaN` / `±Infinity` return `[]` too. No throws on\n * invalid `k` — callers can safely pipe a slider value in without\n * pre-validating.\n *\n * Returns `null` (or `[]` when `k` is set) if the input can't be parsed.\n */\n\n// Input accepts `ColorInput | string`. The `string` widening is load-bearing\n// for two flows: (1) cross-palette name lookup (`identify(name, { source })`),\n// and (2) interactive callers passing untrusted user text; the library\n// returns `null` on unparseable input rather than throwing, so consumers can\n// feed it directly from a text field. Tightening to `ColorInput`-only would\n// break both patterns and force every interactive consumer to cast or\n// validate at every call site. Runtime safety is maintained by `parseInput`\n// returning `null` for anything it can't interpret.\n\n// Overload 1: no `k` — single name (or null). Unchanged legacy shape.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts?: IdentifyOptions<P>,\n): PaletteKey<P> | null;\n\n// Overload 2: `k` present — ranked Match[] with distances.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts: IdentifyRankedOptions<P>,\n): Array<IdentifyMatch<PaletteKey<P>>>;\n\n// Implementation.\nexport function identify(\n input: ColorInput | string,\n opts: IdentifyOptions & { k?: number } = {},\n): string | null | Array<IdentifyMatch<string>> {\n const palette = opts.palette ?? web;\n const metric = opts.metric ?? palette.defaultMetric;\n const rgba = parseInput(input, opts.source);\n\n if (opts.k !== undefined) {\n if (rgba === null) return [];\n const raw = nearestAll(rgba, palette, metric, opts.k);\n return raw.map((m) => {\n const value = (palette.colors as Record<string, HexColor>)[m.name];\n return { name: m.name, value: value as HexColor, distance: m.distance };\n });\n }\n\n if (rgba === null) return null;\n const name = nearest(rgba, palette, metric);\n return name || null;\n}\n"]}
@@ -1 +1 @@
1
- {"version":3,"file":"identify.js","sourceRoot":"","sources":["../src/identify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAkBxC;;;GAGG;AACH,SAAS,UAAU,CAAC,KAA0B,EAAE,MAAgB;IAC9D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAmB,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAmB,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,GAAG,GAAI,MAAM,CAAC,MAAmC,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAqCD,kBAAkB;AAClB,MAAM,UAAU,QAAQ,CACtB,KAA0B,EAC1B,IAAI,GAAiF,EAAE;IAEvF,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE5C,IAAI,IAAI,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,KAAK,GAAI,OAAO,CAAC,MAAmC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACnE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,IAAI,IAAI,IAAI,CAAC;AACtB,CAAC","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport { detectFormat } from './detectFormat.js';\nimport { getNameIndex, nearest, nearestAll } from './indexing.js';\nimport { web } from './palettes/web.js';\nimport type { ColorInput, DistanceMetric, HexColor, Palette, Rgba } from './types.js';\n\n/** Extract string keys from a Palette's `colors` map. */\ntype PaletteKey<P extends Palette> = Extract<keyof P['colors'], string>;\n\n/**\n * One ranked nearest-match entry when `identify` is called with `k`.\n * `value` is the palette's stored hex for the match (useful for rendering\n * swatches). `distance` is in the selected metric's natural units — ΔE\n * for `deltaE*` metrics, channel-unit Euclidean for the others.\n */\nexport type IdentifyMatch<Name extends string> = {\n readonly name: Name;\n readonly value: HexColor;\n readonly distance: number;\n};\n\n/**\n * Parse a string input that may be a color literal or a palette name.\n * Returns the canonical Rgba, or `null` if nothing matches.\n */\nfunction parseInput(input: ColorInput | string, source?: Palette): Rgba | null {\n const detected = detectFormat(input as ColorInput);\n if (detected !== 'UNKNOWN') return toRgba(input as ColorInput, detected);\n if (source !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(source).get(source.normalize(input));\n if (canonical === undefined) return null;\n const hex = (source.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) return null;\n return hexToRgba(hex);\n }\n return null;\n}\n\n/**\n * Identify the nearest-named color for any color input (or palette name\n * from another palette via `source`).\n *\n * **Simple case — color in, name out:**\n * identify('#ff0000') → 'red'\n * identify('#ff0000', { palette: pantone }) → '172 C'\n *\n * **Cross-palette — name from one palette, nearest in another:**\n * identify('rebeccapurple', { palette: pantone, source: web }) → '267 C'\n * identify('Razzmatazz', { palette: pantone, source: crayola }) → '213 C'\n *\n * **Top-k ranked matches with ΔE distances:**\n * identify('#ff0000', { palette: pantone, k: 3 })\n * // [{ name: '172 C', value: '#fa4616', distance: 3.2 }, ...]\n *\n * The distance metric defaults to the target palette's `defaultMetric`\n * (web/x11: `deltaE76`, pantone: `deltaE2000`, crayola: `deltaEok`).\n * Override with `metric`.\n *\n * Returns `null` (or `[]` when `k` is set) if the input can't be parsed.\n */\n\n// Overload 1: no `k` — single name (or null). Unchanged legacy shape.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts?: { palette?: P; source?: Palette; metric?: DistanceMetric },\n): PaletteKey<P> | null;\n\n// Overload 2: `k` present — ranked Match[] with distances.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts: { palette?: P; source?: Palette; metric?: DistanceMetric; k: number },\n): Array<IdentifyMatch<PaletteKey<P>>>;\n\n// Implementation.\nexport function identify(\n input: ColorInput | string,\n opts: { palette?: Palette; source?: Palette; metric?: DistanceMetric; k?: number } = {},\n): string | null | Array<IdentifyMatch<string>> {\n const palette = opts.palette ?? web;\n const metric = opts.metric ?? palette.defaultMetric;\n const rgba = parseInput(input, opts.source);\n\n if (opts.k !== undefined) {\n if (rgba === null) return [];\n const raw = nearestAll(rgba, palette, metric, opts.k);\n return raw.map((m) => {\n const value = (palette.colors as Record<string, HexColor>)[m.name];\n return { name: m.name, value: value as HexColor, distance: m.distance };\n });\n }\n\n if (rgba === null) return null;\n const name = nearest(rgba, palette, metric);\n return name || null;\n}\n"]}
1
+ {"version":3,"file":"identify.js","sourceRoot":"","sources":["../src/identify.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AACjD,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACtC,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAkExC;;;GAGG;AACH,SAAS,UAAU,CAAC,KAA0B,EAAE,MAAgB;IAC9D,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAmB,CAAC,CAAC;IACnD,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,KAAmB,EAAE,QAAQ,CAAC,CAAC;IACzE,IAAI,MAAM,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QACtD,MAAM,SAAS,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;QACpE,IAAI,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACzC,MAAM,GAAG,GAAI,MAAM,CAAC,MAAmC,CAAC,SAAS,CAAC,CAAC;QACnE,IAAI,GAAG,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACnC,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAuDD,kBAAkB;AAClB,MAAM,UAAU,QAAQ,CACtB,KAA0B,EAC1B,IAAI,GAAqC,EAAE;IAE3C,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,GAAG,CAAC;IACpC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,OAAO,CAAC,aAAa,CAAC;IACpD,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAE5C,IAAI,IAAI,CAAC,CAAC,KAAK,SAAS,EAAE,CAAC;QACzB,IAAI,IAAI,KAAK,IAAI;YAAE,OAAO,EAAE,CAAC;QAC7B,MAAM,GAAG,GAAG,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC;QACtD,OAAO,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE;YACnB,MAAM,KAAK,GAAI,OAAO,CAAC,MAAmC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACnE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,KAAiB,EAAE,QAAQ,EAAE,CAAC,CAAC,QAAQ,EAAE,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,IAAI,CAAC;IAC/B,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;IAC5C,OAAO,IAAI,IAAI,IAAI,CAAC;AACtB,CAAC","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { toRgba } from './convert.js';\nimport { detectFormat } from './detectFormat.js';\nimport { getNameIndex, nearest, nearestAll } from './indexing.js';\nimport { web } from './palettes/web.js';\nimport type { ColorInput, DistanceMetric, HexColor, Palette, PaletteKey, Rgba } from './types.js';\n\n/**\n * One ranked nearest-match entry when `identify` is called with `k`.\n * `value` is the palette's stored hex for the match (useful for rendering\n * swatches). `distance` is in the selected metric's natural units: ΔE for\n * `deltaE*` metrics, channel-unit Euclidean for the others.\n */\nexport type IdentifyMatch<Name extends string> = {\n readonly name: Name;\n readonly value: HexColor;\n readonly distance: number;\n};\n\n/**\n * Options for {@link identify}. Generic over the target palette so the\n * return type narrows to its literal-key union.\n *\n * @example\n * identify('#ff0000', { palette: pantone, metric: 'deltaE2000' });\n * identify('rebeccapurple', { palette: x11, source: web });\n */\nexport type IdentifyOptions<P extends Palette = typeof web> = {\n /**\n * Target palette to search in. Defaults to `web` (CSS named colors).\n */\n readonly palette?: P;\n /**\n * Source palette for cross-palette name input: when `input` is a name\n * from a different palette (e.g. `'rebeccapurple'` when `palette` is\n * `pantone`), set `source` to the palette that owns the name so it can\n * be resolved to an Rgba before the nearest-match lookup.\n *\n * @remarks\n * The `input` argument isn't validated against `source`'s keys at\n * compile time; unknown names resolve to `null` at runtime.\n */\n readonly source?: Palette;\n /**\n * Override the distance metric. Defaults to `palette.defaultMetric`\n * (web/x11: `'deltaE76'`, pantone: `'deltaE2000'`, crayola: `'deltaEok'`).\n */\n readonly metric?: DistanceMetric;\n};\n\n/**\n * Options for the ranked top-k variant of {@link identify}. Returns an\n * array of {@link IdentifyMatch} entries instead of a single name.\n *\n * @example\n * identify('#ff0080', { palette: pantone, k: 3 });\n * // [\n * // { name: '219 C', value: '#da1884', distance: 2.1 },\n * // { name: '226 C', value: '#d0006f', distance: 3.9 },\n * // { name: 'Red 032 C',value: '#ef3340', distance: 5.4 },\n * // ]\n */\nexport type IdentifyRankedOptions<P extends Palette = typeof web> = IdentifyOptions<P> & {\n /**\n * Number of ranked matches to return. When present, `identify`\n * returns `IdentifyMatch[]` instead of a single key.\n */\n readonly k: number;\n};\n\n/**\n * Parse a string input that may be a color literal or a palette name.\n * Returns the canonical Rgba, or `null` if nothing matches.\n */\nfunction parseInput(input: ColorInput | string, source?: Palette): Rgba | null {\n const detected = detectFormat(input as ColorInput);\n if (detected !== 'UNKNOWN') return toRgba(input as ColorInput, detected);\n if (source !== undefined && typeof input === 'string') {\n const canonical = getNameIndex(source).get(source.normalize(input));\n if (canonical === undefined) return null;\n const hex = (source.colors as Record<string, HexColor>)[canonical];\n if (hex === undefined) return null;\n return hexToRgba(hex);\n }\n return null;\n}\n\n/**\n * Identify the nearest-named color for any color input (or palette name\n * from another palette via `source`).\n *\n * **Simple case — color in, name out:**\n * identify('#ff0000') → 'red'\n * identify('#ff0000', { palette: pantone }) → '172 C'\n *\n * **Cross-palette — name from one palette, nearest in another:**\n * identify('rebeccapurple', { palette: pantone, source: web }) → '267 C'\n * identify('Razzmatazz', { palette: pantone, source: crayola }) → '213 C'\n *\n * **Top-k ranked matches with ΔE distances:**\n * identify('#ff0000', { palette: pantone, k: 3 })\n * // [{ name: '172 C', value: '#fa4616', distance: 3.2 }, ...]\n *\n * The distance metric defaults to the target palette's `defaultMetric`\n * (web/x11: `deltaE76`, pantone: `deltaE2000`, crayola: `deltaEok`).\n * Override with `metric`.\n *\n * Tie-breaking: on exact-distance ties, the first-declared entry in\n * the palette's `colors` object wins. Deterministic across metrics\n * and across the single-match / top-k paths.\n *\n * `k` is forgiving: fractional values round, negatives and zero\n * return `[]`, and `NaN` / `±Infinity` return `[]` too. No throws on\n * invalid `k` — callers can safely pipe a slider value in without\n * pre-validating.\n *\n * Returns `null` (or `[]` when `k` is set) if the input can't be parsed.\n */\n\n// Input accepts `ColorInput | string`. The `string` widening is load-bearing\n// for two flows: (1) cross-palette name lookup (`identify(name, { source })`),\n// and (2) interactive callers passing untrusted user text; the library\n// returns `null` on unparseable input rather than throwing, so consumers can\n// feed it directly from a text field. Tightening to `ColorInput`-only would\n// break both patterns and force every interactive consumer to cast or\n// validate at every call site. Runtime safety is maintained by `parseInput`\n// returning `null` for anything it can't interpret.\n\n// Overload 1: no `k` — single name (or null). Unchanged legacy shape.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts?: IdentifyOptions<P>,\n): PaletteKey<P> | null;\n\n// Overload 2: `k` present — ranked Match[] with distances.\nexport function identify<P extends Palette = typeof web>(\n input: ColorInput | string,\n opts: IdentifyRankedOptions<P>,\n): Array<IdentifyMatch<PaletteKey<P>>>;\n\n// Implementation.\nexport function identify(\n input: ColorInput | string,\n opts: IdentifyOptions & { k?: number } = {},\n): string | null | Array<IdentifyMatch<string>> {\n const palette = opts.palette ?? web;\n const metric = opts.metric ?? palette.defaultMetric;\n const rgba = parseInput(input, opts.source);\n\n if (opts.k !== undefined) {\n if (rgba === null) return [];\n const raw = nearestAll(rgba, palette, metric, opts.k);\n return raw.map((m) => {\n const value = (palette.colors as Record<string, HexColor>)[m.name];\n return { name: m.name, value: value as HexColor, distance: m.distance };\n });\n }\n\n if (rgba === null) return null;\n const name = nearest(rgba, palette, metric);\n return name || null;\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -3,14 +3,23 @@ export { hslToRgba, rgbaToHsl } from './conversions/hsl.js';
3
3
  export { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';
4
4
  export { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';
5
5
  export { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';
6
- export { convert, fromRgba, toRgba } from './convert.js';
6
+ export { type ConvertNameOptions, type ConvertPaletteOptions, type ConvertStructuralOptions, convert, fromRgba, toRgba, } from './convert.js';
7
+ export { type DefineColorPaletteInput, defineColorPalette, } from './defineColorPalette.js';
7
8
  export { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';
8
- export { type IdentifyMatch, identify } from './identify.js';
9
+ export { type IdentifyMatch, type IdentifyOptions, type IdentifyRankedOptions, identify, } from './identify.js';
9
10
  export { type CrayolaColorName, crayola } from './palettes/crayola.js';
11
+ export { type Fs595bColorName, fs595b } from './palettes/fs595b.js';
12
+ export { type Fs595cColorName, fs595c } from './palettes/fs595c.js';
13
+ export { type IsccNbsColorName, isccNbs } from './palettes/isccNbs.js';
14
+ export { type NbsColorName, nbs } from './palettes/nbs.js';
15
+ export { type NcsColorName, ncs } from './palettes/ncs.js';
16
+ export { type NtcColorName, ntc } from './palettes/ntc.js';
10
17
  export { type PantoneColorName, pantone } from './palettes/pantone.js';
18
+ export { type ReseneColorName, resene } from './palettes/resene.js';
11
19
  export { type WebColorName, web } from './palettes/web.js';
12
20
  export { type X11ColorName, x11 } from './palettes/x11.js';
13
- export { type ResolveMatch, resolve } from './resolve.js';
14
- export type { ColorFormat, ColorInput, ColorValue, DistanceMetric, HexColor, HslInput, HslObject, HslString, HsvInput, HsvObject, HsvString, NormalizeFn, Palette, Rgba, RgbaInput, RgbaString, RgbaTuple, RgbInput, RgbObject, RgbString, RgbTuple, } from './types.js';
21
+ export { type XkcdColorName, xkcd } from './palettes/xkcd.js';
22
+ export { type ResolveFuzzyOptions, type ResolveMatch, type ResolveOptions, resolve, } from './resolve.js';
23
+ export type { ColorFormat, ColorInput, ColorValue, DistanceMetric, HexColor, HslInput, HslObject, HslString, HsvInput, HsvObject, HsvString, NormalizeFn, Palette, PaletteKey, Rgba, RgbaInput, RgbaString, RgbaTuple, RgbInput, RgbObject, RgbString, RgbTuple, } from './types.js';
15
24
  export { COLOR_FORMATS } from './types.js';
16
25
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AACzD,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EAAE,KAAK,aAAa,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAC7D,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC1D,YAAY,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,EACP,IAAI,EACJ,SAAS,EACT,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Public API barrel — explicit named re-exports only (tree-shake friendly).\n// No default export; no side effects at module scope.\n\nexport { hexToRgba, rgbaToHex } from './conversions/hex.js';\nexport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nexport { convert, fromRgba, toRgba } from './convert.js';\nexport { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';\nexport { type IdentifyMatch, identify } from './identify.js';\nexport { type CrayolaColorName, crayola } from './palettes/crayola.js';\nexport { type PantoneColorName, pantone } from './palettes/pantone.js';\nexport { type WebColorName, web } from './palettes/web.js';\nexport { type X11ColorName, x11 } from './palettes/x11.js';\nexport { type ResolveMatch, resolve } from './resolve.js';\nexport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n DistanceMetric,\n HexColor,\n HslInput,\n HslObject,\n HslString,\n HsvInput,\n HsvObject,\n HsvString,\n NormalizeFn,\n Palette,\n Rgba,\n RgbaInput,\n RgbaString,\n RgbaTuple,\n RgbInput,\n RgbObject,\n RgbString,\n RgbTuple,\n} from './types.js';\nexport { COLOR_FORMATS } from './types.js';\n"]}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACxE,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EACL,KAAK,kBAAkB,EACvB,KAAK,qBAAqB,EAC1B,KAAK,wBAAwB,EAC7B,OAAO,EACP,QAAQ,EACR,MAAM,GACP,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,KAAK,uBAAuB,EAC5B,kBAAkB,GACnB,MAAM,yBAAyB,CAAC;AACjC,OAAO,EAAE,KAAK,cAAc,EAAE,YAAY,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC/E,OAAO,EACL,KAAK,aAAa,EAClB,KAAK,eAAe,EACpB,KAAK,qBAAqB,EAC1B,QAAQ,GACT,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,EAAE,KAAK,eAAe,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AACpE,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,EACL,KAAK,mBAAmB,EACxB,KAAK,YAAY,EACjB,KAAK,cAAc,EACnB,OAAO,GACR,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,WAAW,EACX,UAAU,EACV,UAAU,EACV,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,WAAW,EACX,OAAO,EACP,UAAU,EACV,IAAI,EACJ,SAAS,EACT,UAAU,EACV,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,GACT,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC","sourcesContent":["// Public API barrel — explicit named re-exports only (tree-shake friendly).\n// No default export; no side effects at module scope.\n\nexport { hexToRgba, rgbaToHex } from './conversions/hex.js';\nexport { hslToRgba, rgbaToHsl } from './conversions/hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './conversions/hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './conversions/pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './conversions/rgb.js';\nexport {\n type ConvertNameOptions,\n type ConvertPaletteOptions,\n type ConvertStructuralOptions,\n convert,\n fromRgba,\n toRgba,\n} from './convert.js';\nexport {\n type DefineColorPaletteInput,\n defineColorPalette,\n} from './defineColorPalette.js';\nexport { type DetectedFormat, detectFormat, isColor } from './detectFormat.js';\nexport {\n type IdentifyMatch,\n type IdentifyOptions,\n type IdentifyRankedOptions,\n identify,\n} from './identify.js';\nexport { type CrayolaColorName, crayola } from './palettes/crayola.js';\nexport { type Fs595bColorName, fs595b } from './palettes/fs595b.js';\nexport { type Fs595cColorName, fs595c } from './palettes/fs595c.js';\nexport { type IsccNbsColorName, isccNbs } from './palettes/isccNbs.js';\nexport { type NbsColorName, nbs } from './palettes/nbs.js';\nexport { type NcsColorName, ncs } from './palettes/ncs.js';\nexport { type NtcColorName, ntc } from './palettes/ntc.js';\nexport { type PantoneColorName, pantone } from './palettes/pantone.js';\nexport { type ReseneColorName, resene } from './palettes/resene.js';\nexport { type WebColorName, web } from './palettes/web.js';\nexport { type X11ColorName, x11 } from './palettes/x11.js';\nexport { type XkcdColorName, xkcd } from './palettes/xkcd.js';\nexport {\n type ResolveFuzzyOptions,\n type ResolveMatch,\n type ResolveOptions,\n resolve,\n} from './resolve.js';\nexport type {\n ColorFormat,\n ColorInput,\n ColorValue,\n DistanceMetric,\n HexColor,\n HslInput,\n HslObject,\n HslString,\n HsvInput,\n HsvObject,\n HsvString,\n NormalizeFn,\n Palette,\n PaletteKey,\n Rgba,\n RgbaInput,\n RgbaString,\n RgbaTuple,\n RgbInput,\n RgbObject,\n RgbString,\n RgbTuple,\n} from './types.js';\nexport { COLOR_FORMATS } from './types.js';\n"]}