chromonym 3.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/conversions/hsl.d.ts.map +1 -1
- package/dist/conversions/hsl.js.map +1 -1
- package/dist/conversions/hsv.d.ts.map +1 -1
- package/dist/conversions/hsv.js.map +1 -1
- package/dist/indexing.d.ts +2 -14
- package/dist/indexing.d.ts.map +1 -1
- package/dist/indexing.js +2 -13
- package/dist/indexing.js.map +1 -1
- package/package.json +7 -4
- package/dist/conversions/index.d.ts +0 -6
- package/dist/conversions/index.d.ts.map +0 -1
- package/dist/conversions/index.js +0 -6
- package/dist/conversions/index.js.map +0 -1
- package/dist/palettes/index.d.ts +0 -5
- package/dist/palettes/index.d.ts.map +0 -1
- package/dist/palettes/index.js +0 -5
- package/dist/palettes/index.js.map +0 -1
package/README.md
CHANGED
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
[](https://www.npmjs.com/package/chromonym)
|
|
14
14
|
[](https://www.npmjs.com/package/chromonym)
|
|
15
15
|
[](https://github.com/simiancraft/chromonym/actions/workflows/ci.yml)
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
[](https://codecov.io/github/simiancraft/chromonym)
|
|
17
|
+
[](https://bundlephobia.com/package/chromonym)
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<code>identify</code> • <code>resolve</code> • <code>convert</code>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hsl.d.ts","sourceRoot":"","sources":["../../src/conversions/hsl.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAK7D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAqC/C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAyB/C","sourcesContent":["import { requireFinite } from '../math/clamp.js';\nimport { hueSectorToPrime } from '../math/hueSector.js';\nimport type { HslInput, HslString, Rgba } from '../types.js';\n\nconst HSL_RE =\n /^hsl\\s*\\(\\s*(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*\\)$/i;\n\n/**\n * Parse HSL input (string or object) into canonical Rgba.\n * Hue wraps modulo 360; saturation and lightness are 0..100 percent.\n */\nexport function hslToRgba(input: HslInput): Rgba {\n let h: number;\n let s: number;\n let l: number;\n\n if (typeof input === 'string') {\n const match = HSL_RE.exec(input);\n if (!match) throw new Error(`Invalid hsl string: ${input}`);\n h = Number(match[1]);\n s = Number(match[2]);\n l = Number(match[3]);\n } else {\n h = requireFinite(input.h, 'h');\n s = requireFinite(input.s, 's');\n l = requireFinite(input.l, 'l');\n }\n\n const saturation = s / 100;\n const lightness = l / 100;\n\n if (saturation === 0) {\n const gray = Math.round(lightness * 255);\n return { r: gray, g: gray, b: gray, a: 1 };\n }\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;\n const hueSector = (((h % 360) + 360) % 360) / 60;\n const secondary = chroma * (1 - Math.abs((hueSector % 2) - 1));\n const lightnessOffset = lightness - chroma / 2;\n\n const [rPrime, gPrime, bPrime] = hueSectorToPrime(hueSector, chroma, secondary);\n return {\n r: Math.round((rPrime + lightnessOffset) * 255),\n g: Math.round((gPrime + lightnessOffset) * 255),\n b: Math.round((bPrime + lightnessOffset) * 255),\n a: 1,\n };\n}\n\n/**\n * Emit an `hsl(h, s%, l%)` string from Rgba. Alpha is dropped; all\n * three components are rounded to whole numbers.\n */\nexport function rgbaToHsl(rgba: Rgba): HslString {\n const rNorm = rgba.r / 255;\n const gNorm = rgba.g / 255;\n const bNorm = rgba.b / 255;\n const max = Math.max(rNorm, gNorm, bNorm);\n const min = Math.min(rNorm, gNorm, bNorm);\n const lightness = (max + min) / 2;\n\n if (max === min) {\n return `hsl(0, 0%, ${Math.round(lightness * 100)}%)` as HslString;\n }\n\n const delta = max - min;\n const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);\n let hue: number;\n if (max === rNorm) {\n hue = (gNorm - bNorm) / delta + (gNorm < bNorm ? 6 : 0);\n } else if (max === gNorm) {\n hue = (bNorm - rNorm) / delta + 2;\n } else {\n hue = (rNorm - gNorm) / delta + 4;\n }\n hue *= 60;\n\n return `hsl(${Math.round(hue)}, ${Math.round(saturation * 100)}%, ${Math.round(lightness * 100)}%)
|
|
1
|
+
{"version":3,"file":"hsl.d.ts","sourceRoot":"","sources":["../../src/conversions/hsl.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAK7D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAqC/C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAyB/C","sourcesContent":["import { requireFinite } from '../math/clamp.js';\nimport { hueSectorToPrime } from '../math/hueSector.js';\nimport type { HslInput, HslString, Rgba } from '../types.js';\n\nconst HSL_RE =\n /^hsl\\s*\\(\\s*(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*\\)$/i;\n\n/**\n * Parse HSL input (string or object) into canonical Rgba.\n * Hue wraps modulo 360; saturation and lightness are 0..100 percent.\n */\nexport function hslToRgba(input: HslInput): Rgba {\n let h: number;\n let s: number;\n let l: number;\n\n if (typeof input === 'string') {\n const match = HSL_RE.exec(input);\n if (!match) throw new Error(`Invalid hsl string: ${input}`);\n h = Number(match[1]);\n s = Number(match[2]);\n l = Number(match[3]);\n } else {\n h = requireFinite(input.h, 'h');\n s = requireFinite(input.s, 's');\n l = requireFinite(input.l, 'l');\n }\n\n const saturation = s / 100;\n const lightness = l / 100;\n\n if (saturation === 0) {\n const gray = Math.round(lightness * 255);\n return { r: gray, g: gray, b: gray, a: 1 };\n }\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;\n const hueSector = (((h % 360) + 360) % 360) / 60;\n const secondary = chroma * (1 - Math.abs((hueSector % 2) - 1));\n const lightnessOffset = lightness - chroma / 2;\n\n const [rPrime, gPrime, bPrime] = hueSectorToPrime(hueSector, chroma, secondary);\n return {\n r: Math.round((rPrime + lightnessOffset) * 255),\n g: Math.round((gPrime + lightnessOffset) * 255),\n b: Math.round((bPrime + lightnessOffset) * 255),\n a: 1,\n };\n}\n\n/**\n * Emit an `hsl(h, s%, l%)` string from Rgba. Alpha is dropped; all\n * three components are rounded to whole numbers.\n */\nexport function rgbaToHsl(rgba: Rgba): HslString {\n const rNorm = rgba.r / 255;\n const gNorm = rgba.g / 255;\n const bNorm = rgba.b / 255;\n const max = Math.max(rNorm, gNorm, bNorm);\n const min = Math.min(rNorm, gNorm, bNorm);\n const lightness = (max + min) / 2;\n\n if (max === min) {\n return `hsl(0, 0%, ${Math.round(lightness * 100)}%)` as HslString;\n }\n\n const delta = max - min;\n const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);\n let hue: number;\n if (max === rNorm) {\n hue = (gNorm - bNorm) / delta + (gNorm < bNorm ? 6 : 0);\n } else if (max === gNorm) {\n hue = (bNorm - rNorm) / delta + 2;\n } else {\n hue = (rNorm - gNorm) / delta + 4;\n }\n hue *= 60;\n\n return `hsl(${Math.round(hue)}, ${Math.round(saturation * 100)}%, ${Math.round(lightness * 100)}%)`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hsl.js","sourceRoot":"","sources":["../../src/conversions/hsl.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"hsl.js","sourceRoot":"","sources":["../../src/conversions/hsl.ts"],"names":[],"mappings":"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","sourcesContent":["import { requireFinite } from '../math/clamp.js';\nimport { hueSectorToPrime } from '../math/hueSector.js';\nimport type { HslInput, HslString, Rgba } from '../types.js';\n\nconst HSL_RE =\n /^hsl\\s*\\(\\s*(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*\\)$/i;\n\n/**\n * Parse HSL input (string or object) into canonical Rgba.\n * Hue wraps modulo 360; saturation and lightness are 0..100 percent.\n */\nexport function hslToRgba(input: HslInput): Rgba {\n let h: number;\n let s: number;\n let l: number;\n\n if (typeof input === 'string') {\n const match = HSL_RE.exec(input);\n if (!match) throw new Error(`Invalid hsl string: ${input}`);\n h = Number(match[1]);\n s = Number(match[2]);\n l = Number(match[3]);\n } else {\n h = requireFinite(input.h, 'h');\n s = requireFinite(input.s, 's');\n l = requireFinite(input.l, 'l');\n }\n\n const saturation = s / 100;\n const lightness = l / 100;\n\n if (saturation === 0) {\n const gray = Math.round(lightness * 255);\n return { r: gray, g: gray, b: gray, a: 1 };\n }\n\n const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;\n const hueSector = (((h % 360) + 360) % 360) / 60;\n const secondary = chroma * (1 - Math.abs((hueSector % 2) - 1));\n const lightnessOffset = lightness - chroma / 2;\n\n const [rPrime, gPrime, bPrime] = hueSectorToPrime(hueSector, chroma, secondary);\n return {\n r: Math.round((rPrime + lightnessOffset) * 255),\n g: Math.round((gPrime + lightnessOffset) * 255),\n b: Math.round((bPrime + lightnessOffset) * 255),\n a: 1,\n };\n}\n\n/**\n * Emit an `hsl(h, s%, l%)` string from Rgba. Alpha is dropped; all\n * three components are rounded to whole numbers.\n */\nexport function rgbaToHsl(rgba: Rgba): HslString {\n const rNorm = rgba.r / 255;\n const gNorm = rgba.g / 255;\n const bNorm = rgba.b / 255;\n const max = Math.max(rNorm, gNorm, bNorm);\n const min = Math.min(rNorm, gNorm, bNorm);\n const lightness = (max + min) / 2;\n\n if (max === min) {\n return `hsl(0, 0%, ${Math.round(lightness * 100)}%)` as HslString;\n }\n\n const delta = max - min;\n const saturation = lightness > 0.5 ? delta / (2 - max - min) : delta / (max + min);\n let hue: number;\n if (max === rNorm) {\n hue = (gNorm - bNorm) / delta + (gNorm < bNorm ? 6 : 0);\n } else if (max === gNorm) {\n hue = (bNorm - rNorm) / delta + 2;\n } else {\n hue = (rNorm - gNorm) / delta + 4;\n }\n hue *= 60;\n\n return `hsl(${Math.round(hue)}, ${Math.round(saturation * 100)}%, ${Math.round(lightness * 100)}%)`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hsv.d.ts","sourceRoot":"","sources":["../../src/conversions/hsv.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAK7D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CA+B/C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAuB/C","sourcesContent":["import { requireFinite } from '../math/clamp.js';\nimport { hueSectorToPrime } from '../math/hueSector.js';\nimport type { HsvInput, HsvString, Rgba } from '../types.js';\n\nconst HSV_RE =\n /^hsv\\s*\\(\\s*(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*\\)$/i;\n\n/**\n * Parse HSV input (string or object) into canonical Rgba.\n * Hue wraps modulo 360; saturation and value are 0..100 percent.\n */\nexport function hsvToRgba(input: HsvInput): Rgba {\n let h: number;\n let s: number;\n let v: number;\n\n if (typeof input === 'string') {\n const match = HSV_RE.exec(input);\n if (!match) throw new Error(`Invalid hsv string: ${input}`);\n h = Number(match[1]);\n s = Number(match[2]);\n v = Number(match[3]);\n } else {\n h = requireFinite(input.h, 'h');\n s = requireFinite(input.s, 's');\n v = requireFinite(input.v, 'v');\n }\n\n const saturation = s / 100;\n const value = v / 100;\n const chroma = value * saturation;\n const hueSector = (((h % 360) + 360) % 360) / 60;\n const secondary = chroma * (1 - Math.abs((hueSector % 2) - 1));\n const valueOffset = value - chroma;\n\n const [rPrime, gPrime, bPrime] = hueSectorToPrime(hueSector, chroma, secondary);\n return {\n r: Math.round((rPrime + valueOffset) * 255),\n g: Math.round((gPrime + valueOffset) * 255),\n b: Math.round((bPrime + valueOffset) * 255),\n a: 1,\n };\n}\n\n/**\n * Emit an `hsv(h, s%, v%)` string from Rgba. Alpha is dropped; all\n * three components are rounded to whole numbers.\n */\nexport function rgbaToHsv(rgba: Rgba): HsvString {\n const rNorm = rgba.r / 255;\n const gNorm = rgba.g / 255;\n const bNorm = rgba.b / 255;\n const max = Math.max(rNorm, gNorm, bNorm);\n const min = Math.min(rNorm, gNorm, bNorm);\n const delta = max - min;\n const value = max;\n const saturation = max === 0 ? 0 : delta / max;\n\n let hue = 0;\n if (delta !== 0) {\n if (max === rNorm) {\n hue = (gNorm - bNorm) / delta + (gNorm < bNorm ? 6 : 0);\n } else if (max === gNorm) {\n hue = (bNorm - rNorm) / delta + 2;\n } else {\n hue = (rNorm - gNorm) / delta + 4;\n }\n hue *= 60;\n }\n\n return `hsv(${Math.round(hue)}, ${Math.round(saturation * 100)}%, ${Math.round(value * 100)}%)
|
|
1
|
+
{"version":3,"file":"hsv.d.ts","sourceRoot":"","sources":["../../src/conversions/hsv.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,aAAa,CAAC;AAK7D;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CA+B/C;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,SAAS,CAuB/C","sourcesContent":["import { requireFinite } from '../math/clamp.js';\nimport { hueSectorToPrime } from '../math/hueSector.js';\nimport type { HsvInput, HsvString, Rgba } from '../types.js';\n\nconst HSV_RE =\n /^hsv\\s*\\(\\s*(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*\\)$/i;\n\n/**\n * Parse HSV input (string or object) into canonical Rgba.\n * Hue wraps modulo 360; saturation and value are 0..100 percent.\n */\nexport function hsvToRgba(input: HsvInput): Rgba {\n let h: number;\n let s: number;\n let v: number;\n\n if (typeof input === 'string') {\n const match = HSV_RE.exec(input);\n if (!match) throw new Error(`Invalid hsv string: ${input}`);\n h = Number(match[1]);\n s = Number(match[2]);\n v = Number(match[3]);\n } else {\n h = requireFinite(input.h, 'h');\n s = requireFinite(input.s, 's');\n v = requireFinite(input.v, 'v');\n }\n\n const saturation = s / 100;\n const value = v / 100;\n const chroma = value * saturation;\n const hueSector = (((h % 360) + 360) % 360) / 60;\n const secondary = chroma * (1 - Math.abs((hueSector % 2) - 1));\n const valueOffset = value - chroma;\n\n const [rPrime, gPrime, bPrime] = hueSectorToPrime(hueSector, chroma, secondary);\n return {\n r: Math.round((rPrime + valueOffset) * 255),\n g: Math.round((gPrime + valueOffset) * 255),\n b: Math.round((bPrime + valueOffset) * 255),\n a: 1,\n };\n}\n\n/**\n * Emit an `hsv(h, s%, v%)` string from Rgba. Alpha is dropped; all\n * three components are rounded to whole numbers.\n */\nexport function rgbaToHsv(rgba: Rgba): HsvString {\n const rNorm = rgba.r / 255;\n const gNorm = rgba.g / 255;\n const bNorm = rgba.b / 255;\n const max = Math.max(rNorm, gNorm, bNorm);\n const min = Math.min(rNorm, gNorm, bNorm);\n const delta = max - min;\n const value = max;\n const saturation = max === 0 ? 0 : delta / max;\n\n let hue = 0;\n if (delta !== 0) {\n if (max === rNorm) {\n hue = (gNorm - bNorm) / delta + (gNorm < bNorm ? 6 : 0);\n } else if (max === gNorm) {\n hue = (bNorm - rNorm) / delta + 2;\n } else {\n hue = (rNorm - gNorm) / delta + 4;\n }\n hue *= 60;\n }\n\n return `hsv(${Math.round(hue)}, ${Math.round(saturation * 100)}%, ${Math.round(value * 100)}%)`;\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hsv.js","sourceRoot":"","sources":["../../src/conversions/hsv.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"hsv.js","sourceRoot":"","sources":["../../src/conversions/hsv.ts"],"names":[],"mappings":"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","sourcesContent":["import { requireFinite } from '../math/clamp.js';\nimport { hueSectorToPrime } from '../math/hueSector.js';\nimport type { HsvInput, HsvString, Rgba } from '../types.js';\n\nconst HSV_RE =\n /^hsv\\s*\\(\\s*(-?\\d+(?:\\.\\d+)?)\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*,\\s*(-?\\d+(?:\\.\\d+)?)%?\\s*\\)$/i;\n\n/**\n * Parse HSV input (string or object) into canonical Rgba.\n * Hue wraps modulo 360; saturation and value are 0..100 percent.\n */\nexport function hsvToRgba(input: HsvInput): Rgba {\n let h: number;\n let s: number;\n let v: number;\n\n if (typeof input === 'string') {\n const match = HSV_RE.exec(input);\n if (!match) throw new Error(`Invalid hsv string: ${input}`);\n h = Number(match[1]);\n s = Number(match[2]);\n v = Number(match[3]);\n } else {\n h = requireFinite(input.h, 'h');\n s = requireFinite(input.s, 's');\n v = requireFinite(input.v, 'v');\n }\n\n const saturation = s / 100;\n const value = v / 100;\n const chroma = value * saturation;\n const hueSector = (((h % 360) + 360) % 360) / 60;\n const secondary = chroma * (1 - Math.abs((hueSector % 2) - 1));\n const valueOffset = value - chroma;\n\n const [rPrime, gPrime, bPrime] = hueSectorToPrime(hueSector, chroma, secondary);\n return {\n r: Math.round((rPrime + valueOffset) * 255),\n g: Math.round((gPrime + valueOffset) * 255),\n b: Math.round((bPrime + valueOffset) * 255),\n a: 1,\n };\n}\n\n/**\n * Emit an `hsv(h, s%, v%)` string from Rgba. Alpha is dropped; all\n * three components are rounded to whole numbers.\n */\nexport function rgbaToHsv(rgba: Rgba): HsvString {\n const rNorm = rgba.r / 255;\n const gNorm = rgba.g / 255;\n const bNorm = rgba.b / 255;\n const max = Math.max(rNorm, gNorm, bNorm);\n const min = Math.min(rNorm, gNorm, bNorm);\n const delta = max - min;\n const value = max;\n const saturation = max === 0 ? 0 : delta / max;\n\n let hue = 0;\n if (delta !== 0) {\n if (max === rNorm) {\n hue = (gNorm - bNorm) / delta + (gNorm < bNorm ? 6 : 0);\n } else if (max === gNorm) {\n hue = (bNorm - rNorm) / delta + 2;\n } else {\n hue = (rNorm - gNorm) / delta + 4;\n }\n hue *= 60;\n }\n\n return `hsv(${Math.round(hue)}, ${Math.round(saturation * 100)}%, ${Math.round(value * 100)}%)`;\n}\n"]}
|
package/dist/indexing.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DistanceMetric,
|
|
1
|
+
import type { DistanceMetric, Palette, Rgba } from './types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Per-palette lookup indexes for name resolution and nearest-match.
|
|
4
4
|
*
|
|
@@ -6,8 +6,6 @@ import type { DistanceMetric, NormalizeFn, Palette, Rgba } from './types.js';
|
|
|
6
6
|
* indexes are built once per process and reused by every caller —
|
|
7
7
|
* including BYO palettes the user supplies.
|
|
8
8
|
*/
|
|
9
|
-
export { pantoneNormalize, standardNormalize } from './palettes/normalize.js';
|
|
10
|
-
export type { NormalizeFn };
|
|
11
9
|
type AnyPalette = Palette<string>;
|
|
12
10
|
/**
|
|
13
11
|
* Normalized-name → canonical-key map for a given palette. Built once
|
|
@@ -25,17 +23,6 @@ export declare function getNameIndex(space: AnyPalette): Map<string, string>;
|
|
|
25
23
|
* input with `rgbaToHex(...).toLowerCase()` before lookup.
|
|
26
24
|
*/
|
|
27
25
|
export declare function getReverseNameIndex(space: AnyPalette): Map<string, string>;
|
|
28
|
-
/**
|
|
29
|
-
* Canonical-key → Rgba entries for a given palette, built by parsing
|
|
30
|
-
* each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).
|
|
31
|
-
*/
|
|
32
|
-
export declare function getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]>;
|
|
33
|
-
/**
|
|
34
|
-
* Find the nearest-named entry in a palette to the given Rgba, using
|
|
35
|
-
* squared Euclidean distance in sRGB (alpha ignored). Used by `identify`
|
|
36
|
-
* and `rgbaToPantone` when metric === 'euclidean-srgb'.
|
|
37
|
-
*/
|
|
38
|
-
export declare function nearestByRgb(target: Rgba, space: AnyPalette): string;
|
|
39
26
|
/**
|
|
40
27
|
* Find the nearest-named entry in a palette under the specified distance
|
|
41
28
|
* metric. Dispatches to the right index (rgba / linear-rgb / Lab) and
|
|
@@ -65,4 +52,5 @@ export declare function nearestAll(target: Rgba, space: AnyPalette, metric: Dist
|
|
|
65
52
|
name: string;
|
|
66
53
|
distance: number;
|
|
67
54
|
}>;
|
|
55
|
+
export {};
|
|
68
56
|
//# sourceMappingURL=indexing.d.ts.map
|
package/dist/indexing.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexing.d.ts","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAY,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEvF;;;;;;GAMG;AAKH,OAAO,EAAE,gBAAgB,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC9E,YAAY,EAAE,WAAW,EAAE,CAAC;AAE5B,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAKlC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWnE;AAID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAS9E;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,GAAG,MAAM,CAYpE;AAsED;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAavF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,cAAc,EACtB,CAAC,CAAC,EAAE,MAAM,GACT,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2C3C","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, HexColor, NormalizeFn, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\n// Re-export normalizers from their own module for backward compat with any\n// caller that expected them here. The functions live in ./palettes/normalize\n// so palette modules can import them without pulling this file's math graph.\nexport { pantoneNormalize, standardNormalize } from './palettes/normalize.js';\nexport type { NormalizeFn };\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nexport function getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(\n ([name, hex]) => [name, hexToRgba(hex as HexColor)] as const,\n );\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nexport function nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"indexing.d.ts","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhE;;;;;;GAMG;AAEH,KAAK,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AAKlC;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAWnE;AAID;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,UAAU,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAU1E;AAsGD;;;;;;;;;GASG;AACH,wBAAgB,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,cAAc,GAAG,MAAM,CAavF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,UAAU,CACxB,MAAM,EAAE,IAAI,EACZ,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,cAAc,EACtB,CAAC,CAAC,EAAE,MAAM,GACT,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CA2C3C","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
|
package/dist/indexing.js
CHANGED
|
@@ -2,17 +2,6 @@ import { hexToRgba } from './conversions/hex.js';
|
|
|
2
2
|
import { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';
|
|
3
3
|
import { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';
|
|
4
4
|
import { squaredDistanceRgb } from './math/euclideanDistance.js';
|
|
5
|
-
/**
|
|
6
|
-
* Per-palette lookup indexes for name resolution and nearest-match.
|
|
7
|
-
*
|
|
8
|
-
* Caches are keyed by the Palette wrapper (WeakMap), so each palette's
|
|
9
|
-
* indexes are built once per process and reused by every caller —
|
|
10
|
-
* including BYO palettes the user supplies.
|
|
11
|
-
*/
|
|
12
|
-
// Re-export normalizers from their own module for backward compat with any
|
|
13
|
-
// caller that expected them here. The functions live in ./palettes/normalize
|
|
14
|
-
// so palette modules can import them without pulling this file's math graph.
|
|
15
|
-
export { pantoneNormalize, standardNormalize } from './palettes/normalize.js';
|
|
16
5
|
const nameIndexCache = new WeakMap();
|
|
17
6
|
const rgbaIndexCache = new WeakMap();
|
|
18
7
|
/**
|
|
@@ -57,7 +46,7 @@ export function getReverseNameIndex(space) {
|
|
|
57
46
|
* Canonical-key → Rgba entries for a given palette, built by parsing
|
|
58
47
|
* each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).
|
|
59
48
|
*/
|
|
60
|
-
|
|
49
|
+
function getRgbaIndex(space) {
|
|
61
50
|
let idx = rgbaIndexCache.get(space);
|
|
62
51
|
if (idx === undefined) {
|
|
63
52
|
idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)]);
|
|
@@ -70,7 +59,7 @@ export function getRgbaIndex(space) {
|
|
|
70
59
|
* squared Euclidean distance in sRGB (alpha ignored). Used by `identify`
|
|
71
60
|
* and `rgbaToPantone` when metric === 'euclidean-srgb'.
|
|
72
61
|
*/
|
|
73
|
-
|
|
62
|
+
function nearestByRgb(target, space) {
|
|
74
63
|
const entries = getRgbaIndex(space);
|
|
75
64
|
let bestName = entries[0]?.[0] ?? '';
|
|
76
65
|
let bestDistance = Number.POSITIVE_INFINITY;
|
package/dist/indexing.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, HexColor, NormalizeFn, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\n// Re-export normalizers from their own module for backward compat with any\n// caller that expected them here. The functions live in ./palettes/normalize\n// so palette modules can import them without pulling this file's math graph.\nexport { pantoneNormalize, standardNormalize } from './palettes/normalize.js';\nexport type { NormalizeFn };\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nexport function getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(\n ([name, hex]) => [name, hexToRgba(hex as HexColor)] as const,\n );\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nexport function nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"indexing.js","sourceRoot":"","sources":["../src/indexing.ts"],"names":[],"mappings":"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","sourcesContent":["import { hexToRgba } from './conversions/hex.js';\nimport { rgbaToLab, rgbaToLinearRgb, rgbaToOklab } from './math/colorSpace.js';\nimport { deltaE76Squared, deltaE94, deltaE2000, deltaEokSquared } from './math/deltaE.js';\nimport { squaredDistanceRgb } from './math/euclideanDistance.js';\nimport type { DistanceMetric, Palette, Rgba } from './types.js';\n\n/**\n * Per-palette lookup indexes for name resolution and nearest-match.\n *\n * Caches are keyed by the Palette wrapper (WeakMap), so each palette's\n * indexes are built once per process and reused by every caller —\n * including BYO palettes the user supplies.\n */\n\ntype AnyPalette = Palette<string>;\n\nconst nameIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\nconst rgbaIndexCache = new WeakMap<AnyPalette, Array<readonly [string, Rgba]>>();\n\n/**\n * Normalized-name → canonical-key map for a given palette. Built once\n * per palette using its own `normalize` function.\n */\nexport function getNameIndex(space: AnyPalette): Map<string, string> {\n let idx = nameIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n const { normalize } = space;\n for (const key of Object.keys(space.colors)) {\n idx.set(normalize(key), key);\n }\n nameIndexCache.set(space, idx);\n }\n return idx;\n}\n\nconst reverseIndexCache = new WeakMap<AnyPalette, Map<string, string>>();\n\n/**\n * Hex → canonical-key map (reverse of `palette.colors`). Built once per\n * palette. Used by `convert(color, { palette, format: 'NAME' })` for\n * O(1) exact reverse lookup. Returns `undefined` on miss — the caller\n * decides whether that's an error (convert throws; use `identify` for\n * nearest-match semantics).\n *\n * Hex values are lowercased for the key so callers can normalize their\n * input with `rgbaToHex(...).toLowerCase()` before lookup.\n */\nexport function getReverseNameIndex(space: AnyPalette): Map<string, string> {\n let idx = reverseIndexCache.get(space);\n if (idx === undefined) {\n idx = new Map();\n for (const [name, hex] of Object.entries(space.colors)) {\n idx.set((hex as string).toLowerCase(), name);\n }\n reverseIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Canonical-key → Rgba entries for a given palette, built by parsing\n * each hex value once. Used by nearest-match lookups (identify, rgbaToPantone).\n */\nfunction getRgbaIndex(space: AnyPalette): Array<readonly [string, Rgba]> {\n let idx = rgbaIndexCache.get(space);\n if (idx === undefined) {\n idx = Object.entries(space.colors).map(([name, hex]) => [name, hexToRgba(hex)] as const);\n rgbaIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Find the nearest-named entry in a palette to the given Rgba, using\n * squared Euclidean distance in sRGB (alpha ignored). Used by `identify`\n * and `rgbaToPantone` when metric === 'euclidean-srgb'.\n */\nfunction nearestByRgb(target: Rgba, space: AnyPalette): string {\n const entries = getRgbaIndex(space);\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = squaredDistanceRgb(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Precomputed Lab entries for delta-E metrics. Same shape as rgba index.\ntype LabTriple = readonly [number, number, number];\nconst labIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = labIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLab(rgba)] as const);\n labIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed linear-RGB entries for euclidean-linear metric.\nconst linearIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getLinearIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = linearIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToLinearRgb(rgba)] as const);\n linearIndexCache.set(space, idx);\n }\n return idx;\n}\n\n// Precomputed OKLAB entries for deltaEok metric.\nconst oklabIndexCache = new WeakMap<AnyPalette, Array<readonly [string, LabTriple]>>();\n\nfunction getOklabIndex(space: AnyPalette): Array<readonly [string, LabTriple]> {\n let idx = oklabIndexCache.get(space);\n if (idx === undefined) {\n idx = getRgbaIndex(space).map(([name, rgba]) => [name, rgbaToOklab(rgba)] as const);\n oklabIndexCache.set(space, idx);\n }\n return idx;\n}\n\n/**\n * Generic argmin over an `[name, value]` entries array with a custom\n * distance function. Factored so the three metric branches in `nearest`\n * share one loop body instead of duplicating it three times.\n */\nfunction argminBy<T>(\n entries: Array<readonly [string, T]>,\n target: T,\n dist: (a: T, b: T) => number,\n): string {\n let bestName = entries[0]?.[0] ?? '';\n let bestDistance = Number.POSITIVE_INFINITY;\n for (const [name, candidate] of entries) {\n const d = dist(target, candidate);\n if (d < bestDistance) {\n bestDistance = d;\n bestName = name;\n }\n }\n return bestName;\n}\n\n// Squared Euclidean on a [number, number, number] triple. Used for the\n// linear-RGB metric (sharing shape with Lab but different meaning).\nfunction squaredDistanceTriple(a: LabTriple, b: LabTriple): number {\n const d0 = a[0] - b[0];\n const d1 = a[1] - b[1];\n const d2 = a[2] - b[2];\n return d0 * d0 + d1 * d1 + d2 * d2;\n}\n\n/**\n * Find the nearest-named entry in a palette under the specified distance\n * metric. Dispatches to the right index (rgba / linear-rgb / Lab) and\n * the right distance function. See `DistanceMetric` in types.ts for how\n * to pick a metric.\n *\n * `metric` is required — callers should pass `space.defaultMetric` if\n * they want the palette's declared preference. A silent default would\n * hide the distance-metric choice from the hot path.\n */\nexport function nearest(target: Rgba, space: AnyPalette, metric: DistanceMetric): string {\n if (metric === 'euclidean-srgb') return nearestByRgb(target, space);\n if (metric === 'euclidean-linear') {\n return argminBy(getLinearIndex(space), rgbaToLinearRgb(target), squaredDistanceTriple);\n }\n if (metric === 'deltaEok') {\n return argminBy(getOklabIndex(space), rgbaToOklab(target), deltaEokSquared);\n }\n const labTarget = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') return argminBy(labIndex, labTarget, deltaE76Squared);\n if (metric === 'deltaE94') return argminBy(labIndex, labTarget, deltaE94);\n return argminBy(labIndex, labTarget, deltaE2000);\n}\n\n/**\n * Rank every palette entry by distance to `target`. Returns an array of\n * `{ name, distance }` sorted ascending (nearest first). Optional `k`\n * truncates to the top-k matches.\n *\n * Distances are **real** metric values (not squared) — ΔE76 / ΔE94 /\n * ΔE2000 / ΔE-OK are in standard ΔE units (≈1 = just-noticeable for\n * most of the gamut on ΔE76/2000); `euclidean-srgb` / `euclidean-linear`\n * are in their respective channel-unit Euclidean distances.\n *\n * Cost: O(n log n) for the sort vs O(n) for `nearest` — acceptable for\n * top-K queries. For strict \"what's the single nearest?\" stay on\n * `nearest` / `identify`.\n */\nexport function nearestAll(\n target: Rgba,\n space: AnyPalette,\n metric: DistanceMetric,\n k?: number,\n): Array<{ name: string; distance: number }> {\n const ranked: Array<[string, number]> = [];\n\n if (metric === 'euclidean-srgb') {\n for (const [name, candidate] of getRgbaIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceRgb(target, candidate))]);\n }\n } else if (metric === 'euclidean-linear') {\n const tgt = rgbaToLinearRgb(target);\n for (const [name, candidate] of getLinearIndex(space)) {\n ranked.push([name, Math.sqrt(squaredDistanceTriple(tgt, candidate))]);\n }\n } else if (metric === 'deltaEok') {\n const tgt = rgbaToOklab(target);\n for (const [name, candidate] of getOklabIndex(space)) {\n ranked.push([name, Math.sqrt(deltaEokSquared(tgt, candidate))]);\n }\n } else {\n const tgt = rgbaToLab(target);\n const labIndex = getLabIndex(space);\n if (metric === 'deltaE76') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, Math.sqrt(deltaE76Squared(tgt, candidate))]);\n }\n } else if (metric === 'deltaE94') {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE94(tgt, candidate)]);\n }\n } else {\n for (const [name, candidate] of labIndex) {\n ranked.push([name, deltaE2000(tgt, candidate)]);\n }\n }\n }\n\n ranked.sort((a, b) => a[1] - b[1]);\n const limit = k === undefined ? ranked.length : Math.min(Math.max(k, 0), ranked.length);\n const result: Array<{ name: string; distance: number }> = new Array(limit);\n for (let i = 0; i < limit; i++) {\n const entry = ranked[i];\n if (entry !== undefined) result[i] = { name: entry[0], distance: entry[1] };\n }\n return result;\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chromonym",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.1.0",
|
|
4
4
|
"description": "One color-naming API for any palette. Identify, resolve, and convert colors across your own palette or the built-in CSS, X11, Pantone, and Crayola sets — fully typed, tree-shakeable, perceptual distance metrics (CIEDE2000 / OKLAB).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"color",
|
|
@@ -126,16 +126,19 @@
|
|
|
126
126
|
"lint:fix": "biome check --write",
|
|
127
127
|
"format": "biome format --write .",
|
|
128
128
|
"test": "bun test",
|
|
129
|
+
"check:package": "publint && attw --pack --profile esm-only",
|
|
130
|
+
"check:knip": "knip",
|
|
129
131
|
"demo": "bun run build && cd demo && bun run dev",
|
|
130
|
-
"prepublishOnly": "bun run build"
|
|
132
|
+
"prepublishOnly": "bun run build && bun run check:package"
|
|
131
133
|
},
|
|
132
134
|
"devDependencies": {
|
|
135
|
+
"@arethetypeswrong/cli": "^0.18.2",
|
|
133
136
|
"@biomejs/biome": "^2.2.0",
|
|
134
|
-
"@semantic-release/changelog": "^6.0.3",
|
|
135
|
-
"@semantic-release/git": "^10.0.1",
|
|
136
137
|
"@semantic-release/npm": "^13.1.5",
|
|
137
138
|
"@types/bun": "^1.3.12",
|
|
138
139
|
"@typescript/native-preview": "7.0.0-dev.20260417.1",
|
|
140
|
+
"knip": "^6.5.0",
|
|
141
|
+
"publint": "^0.3.18",
|
|
139
142
|
"semantic-release": "^25.0.3"
|
|
140
143
|
},
|
|
141
144
|
"publishConfig": {
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { hexToRgba, rgbaToHex } from './hex.js';
|
|
2
|
-
export { hslToRgba, rgbaToHsl } from './hsl.js';
|
|
3
|
-
export { hsvToRgba, rgbaToHsv } from './hsv.js';
|
|
4
|
-
export { pantoneToRgba, rgbaToPantone } from './pantone.js';
|
|
5
|
-
export { rgbaToRgb, rgbToRgba } from './rgb.js';
|
|
6
|
-
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/conversions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC","sourcesContent":["export { hexToRgba, rgbaToHex } from './hex.js';\nexport { hslToRgba, rgbaToHsl } from './hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './rgb.js';\n"]}
|
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
export { hexToRgba, rgbaToHex } from './hex.js';
|
|
2
|
-
export { hslToRgba, rgbaToHsl } from './hsl.js';
|
|
3
|
-
export { hsvToRgba, rgbaToHsv } from './hsv.js';
|
|
4
|
-
export { pantoneToRgba, rgbaToPantone } from './pantone.js';
|
|
5
|
-
export { rgbaToRgb, rgbToRgba } from './rgb.js';
|
|
6
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/conversions/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC","sourcesContent":["export { hexToRgba, rgbaToHex } from './hex.js';\nexport { hslToRgba, rgbaToHsl } from './hsl.js';\nexport { hsvToRgba, rgbaToHsv } from './hsv.js';\nexport { pantoneToRgba, rgbaToPantone } from './pantone.js';\nexport { rgbaToRgb, rgbToRgba } from './rgb.js';\n"]}
|
package/dist/palettes/index.d.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/palettes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,KAAK,gBAAgB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAE,KAAK,YAAY,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC","sourcesContent":["export { type CrayolaColorName, crayola } from './crayola.js';\nexport { type PantoneColorName, pantone } from './pantone.js';\nexport { type WebColorName, web } from './web.js';\nexport { type X11ColorName, x11 } from './x11.js';\n"]}
|
package/dist/palettes/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/palettes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAyB,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAyB,OAAO,EAAE,MAAM,cAAc,CAAC;AAC9D,OAAO,EAAqB,GAAG,EAAE,MAAM,UAAU,CAAC;AAClD,OAAO,EAAqB,GAAG,EAAE,MAAM,UAAU,CAAC","sourcesContent":["export { type CrayolaColorName, crayola } from './crayola.js';\nexport { type PantoneColorName, pantone } from './pantone.js';\nexport { type WebColorName, web } from './web.js';\nexport { type X11ColorName, x11 } from './x11.js';\n"]}
|