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.
- package/NOTICE.md +243 -1
- package/README.md +47 -42
- package/dist/convert.d.ts +73 -21
- package/dist/convert.d.ts.map +1 -1
- package/dist/convert.js +13 -2
- package/dist/convert.js.map +1 -1
- package/dist/defineColorPalette.d.ts +53 -0
- package/dist/defineColorPalette.d.ts.map +1 -0
- package/dist/defineColorPalette.js +103 -0
- package/dist/defineColorPalette.js.map +1 -0
- package/dist/detectFormat.d.ts +26 -3
- package/dist/detectFormat.d.ts.map +1 -1
- package/dist/detectFormat.js +20 -0
- package/dist/detectFormat.js.map +1 -1
- package/dist/identify.d.ts +63 -17
- package/dist/identify.d.ts.map +1 -1
- package/dist/identify.js.map +1 -1
- package/dist/index.d.ts +13 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/indexing.d.ts +13 -3
- package/dist/indexing.d.ts.map +1 -1
- package/dist/indexing.js +67 -19
- package/dist/indexing.js.map +1 -1
- package/dist/palettes/crayola.d.ts +16 -0
- package/dist/palettes/crayola.d.ts.map +1 -1
- package/dist/palettes/crayola.js +16 -0
- package/dist/palettes/crayola.js.map +1 -1
- package/dist/palettes/fs595b.d.ts +467 -0
- package/dist/palettes/fs595b.d.ts.map +1 -0
- package/dist/palettes/fs595b.js +256 -0
- package/dist/palettes/fs595b.js.map +1 -0
- package/dist/palettes/fs595c.d.ts +1224 -0
- package/dist/palettes/fs595c.d.ts.map +1 -0
- package/dist/palettes/fs595c.js +633 -0
- package/dist/palettes/fs595c.js.map +1 -0
- package/dist/palettes/isccNbs.d.ts +562 -0
- package/dist/palettes/isccNbs.d.ts.map +1 -0
- package/dist/palettes/isccNbs.js +300 -0
- package/dist/palettes/isccNbs.js.map +1 -0
- package/dist/palettes/nbs.d.ts +578 -0
- package/dist/palettes/nbs.d.ts.map +1 -0
- package/dist/palettes/nbs.js +309 -0
- package/dist/palettes/nbs.js.map +1 -0
- package/dist/palettes/ncs.d.ts +3949 -0
- package/dist/palettes/ncs.d.ts.map +1 -0
- package/dist/palettes/ncs.js +2011 -0
- package/dist/palettes/ncs.js.map +1 -0
- package/dist/palettes/ntc.d.ts +3170 -0
- package/dist/palettes/ntc.d.ts.map +1 -0
- package/dist/palettes/ntc.js +1602 -0
- package/dist/palettes/ntc.js.map +1 -0
- package/dist/palettes/pantone.d.ts +18 -1
- package/dist/palettes/pantone.d.ts.map +1 -1
- package/dist/palettes/pantone.js +18 -1
- package/dist/palettes/pantone.js.map +1 -1
- package/dist/palettes/resene.d.ts +2802 -0
- package/dist/palettes/resene.d.ts.map +1 -0
- package/dist/palettes/resene.js +1422 -0
- package/dist/palettes/resene.js.map +1 -0
- package/dist/palettes/web.d.ts +10 -0
- package/dist/palettes/web.d.ts.map +1 -1
- package/dist/palettes/web.js +10 -0
- package/dist/palettes/web.js.map +1 -1
- package/dist/palettes/x11.d.ts +15 -10
- package/dist/palettes/x11.d.ts.map +1 -1
- package/dist/palettes/x11.js +15 -10
- package/dist/palettes/x11.js.map +1 -1
- package/dist/palettes/xkcd.d.ts +1880 -0
- package/dist/palettes/xkcd.d.ts.map +1 -0
- package/dist/palettes/xkcd.js +955 -0
- package/dist/palettes/xkcd.js.map +1 -0
- package/dist/resolve.d.ts +58 -14
- package/dist/resolve.d.ts.map +1 -1
- package/dist/resolve.js +32 -16
- package/dist/resolve.js.map +1 -1
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +43 -4
package/dist/convert.js.map
CHANGED
|
@@ -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"]}
|
package/dist/detectFormat.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import type { ColorFormat, ColorInput } from './types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
* '
|
|
5
|
-
*
|
|
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
|
|
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"]}
|
package/dist/detectFormat.js
CHANGED
|
@@ -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.
|
package/dist/detectFormat.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detectFormat.js","sourceRoot":"","sources":["../src/detectFormat.ts"],"names":[],"mappings":"
|
|
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"]}
|
package/dist/identify.d.ts
CHANGED
|
@@ -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
|
|
9
|
-
*
|
|
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
|
-
|
|
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
|
package/dist/identify.d.ts.map
CHANGED
|
@@ -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,
|
|
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"]}
|
package/dist/identify.js.map
CHANGED
|
@@ -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;
|
|
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
|
|
14
|
-
export
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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"]}
|