color-map 2.2.0 → 2.3.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/.claude-plugin/plugin.json +12 -0
- package/.codex-plugin/plugin.json +12 -0
- package/.cursor-plugin/plugin.json +12 -0
- package/CHANGELOG.md +94 -0
- package/README.md +71 -8
- package/bin/cli-CNBftDBE.js +6414 -0
- package/bin/color-map.js +9 -0
- package/bin/index.js +1 -0
- package/cjs/color/index.d.ts +5 -0
- package/cjs/color/index.js +52 -0
- package/cjs/color-wNrkrhC7.js +374 -0
- package/cjs/color-wNrkrhC7.js.map +1 -0
- package/cjs/contrast-XjF5Rwui.js +293 -0
- package/cjs/contrast-XjF5Rwui.js.map +1 -0
- package/cjs/gamut-CG1J2aat.d.ts +158 -0
- package/cjs/gamut-CG1J2aat.d.ts.map +1 -0
- package/cjs/index-BcEqCPHz.d.ts +137 -0
- package/cjs/index-BcEqCPHz.d.ts.map +1 -0
- package/cjs/index-CGKCqRqD.d.ts +111 -0
- package/cjs/index-CGKCqRqD.d.ts.map +1 -0
- package/cjs/index-Cv3iUUYE.d.ts +171 -0
- package/cjs/index-Cv3iUUYE.d.ts.map +1 -0
- package/cjs/index-Dw43Hj4v.d.ts +41 -0
- package/cjs/index-Dw43Hj4v.d.ts.map +1 -0
- package/cjs/index-Dy8UARqO.d.ts +72 -0
- package/cjs/index-Dy8UARqO.d.ts.map +1 -0
- package/cjs/index.d.ts +26 -36
- package/cjs/index.d.ts.map +1 -1
- package/cjs/index.js +83 -62
- package/cjs/index.js.map +1 -1
- package/cjs/legacy/index.d.ts +2 -0
- package/cjs/legacy/index.js +75 -0
- package/cjs/legacy/index.js.map +1 -0
- package/cjs/mix-Cs4owSbW.js +158 -0
- package/cjs/mix-Cs4owSbW.js.map +1 -0
- package/cjs/mix-DGlKoUw4.d.ts +31 -0
- package/cjs/mix-DGlKoUw4.d.ts.map +1 -0
- package/cjs/palette/index.d.ts +3 -0
- package/cjs/palette/index.js +8 -0
- package/cjs/palette-DyFV_KoJ.js +332 -0
- package/cjs/palette-DyFV_KoJ.js.map +1 -0
- package/cjs/parse-8mt2OMKf.js +1508 -0
- package/cjs/parse-8mt2OMKf.js.map +1 -0
- package/cjs/scale/index.d.ts +2 -0
- package/cjs/scale/index.js +11 -0
- package/cjs/scale-Cq9pXrc1.js +318 -0
- package/cjs/scale-Cq9pXrc1.js.map +1 -0
- package/cjs/semantic-CrEIo8tS.d.ts +52 -0
- package/cjs/semantic-CrEIo8tS.d.ts.map +1 -0
- package/cjs/serialize-C69DAmoE.d.ts +25 -0
- package/cjs/serialize-C69DAmoE.d.ts.map +1 -0
- package/cjs/serialize-Ci2X0cPN.js +103 -0
- package/cjs/serialize-Ci2X0cPN.js.map +1 -0
- package/cjs/tokens/index.d.ts +2 -0
- package/cjs/tokens/index.js +6 -0
- package/cjs/tokens-CzXxaMgM.js +168 -0
- package/cjs/tokens-CzXxaMgM.js.map +1 -0
- package/cjs/tone-BAhu-QzQ.js +118 -0
- package/cjs/tone-BAhu-QzQ.js.map +1 -0
- package/dist/color-map.es5.js.map +1 -1
- package/esm/color/a98-rgb.d.ts +7 -0
- package/esm/color/a98-rgb.d.ts.map +1 -0
- package/esm/color/a98-rgb.js +13 -0
- package/esm/color/a98-rgb.js.map +1 -0
- package/esm/color/argb.d.ts +6 -0
- package/esm/color/argb.d.ts.map +1 -0
- package/esm/color/argb.js +25 -0
- package/esm/color/argb.js.map +1 -0
- package/esm/color/calc.js +82 -0
- package/esm/color/calc.js.map +1 -0
- package/esm/color/compare.d.ts +20 -0
- package/esm/color/compare.d.ts.map +1 -0
- package/esm/color/compare.js +46 -0
- package/esm/color/compare.js.map +1 -0
- package/esm/color/contrast.d.ts +107 -0
- package/esm/color/contrast.d.ts.map +1 -0
- package/esm/color/contrast.js +242 -0
- package/esm/color/contrast.js.map +1 -0
- package/esm/color/convert.d.ts +9 -0
- package/esm/color/convert.d.ts.map +1 -0
- package/esm/color/convert.js +45 -0
- package/esm/color/convert.js.map +1 -0
- package/esm/color/display-p3-linear.d.ts +7 -0
- package/esm/color/display-p3-linear.d.ts.map +1 -0
- package/esm/color/display-p3-linear.js +12 -0
- package/esm/color/display-p3-linear.js.map +1 -0
- package/esm/color/display-p3.d.ts +7 -0
- package/esm/color/display-p3.d.ts.map +1 -0
- package/esm/color/display-p3.js +12 -0
- package/esm/color/display-p3.js.map +1 -0
- package/esm/color/expr.d.ts +43 -0
- package/esm/color/expr.d.ts.map +1 -0
- package/esm/color/expr.js +160 -0
- package/esm/color/expr.js.map +1 -0
- package/esm/color/gamut.d.ts +31 -0
- package/esm/color/gamut.d.ts.map +1 -0
- package/esm/color/gamut.js +210 -0
- package/esm/color/gamut.js.map +1 -0
- package/esm/color/hsl.d.ts +7 -0
- package/esm/color/hsl.d.ts.map +1 -0
- package/esm/color/hsl.js +67 -0
- package/esm/color/hsl.js.map +1 -0
- package/esm/color/hwb.d.ts +7 -0
- package/esm/color/hwb.d.ts.map +1 -0
- package/esm/color/hwb.js +40 -0
- package/esm/color/hwb.js.map +1 -0
- package/esm/color/index.d.ts +28 -0
- package/esm/color/index.js +27 -0
- package/esm/color/lab.d.ts +7 -0
- package/esm/color/lab.d.ts.map +1 -0
- package/esm/color/lab.js +33 -0
- package/esm/color/lab.js.map +1 -0
- package/esm/color/lch.d.ts +7 -0
- package/esm/color/lch.d.ts.map +1 -0
- package/esm/color/lch.js +11 -0
- package/esm/color/lch.js.map +1 -0
- package/esm/color/math.js +32 -0
- package/esm/color/math.js.map +1 -0
- package/esm/color/matrices.js +180 -0
- package/esm/color/matrices.js.map +1 -0
- package/esm/color/mix.d.ts +30 -0
- package/esm/color/mix.d.ts.map +1 -0
- package/esm/color/mix.js +152 -0
- package/esm/color/mix.js.map +1 -0
- package/esm/color/named.js +11 -0
- package/esm/color/named.js.map +1 -0
- package/esm/color/oklab.d.ts +7 -0
- package/esm/color/oklab.d.ts.map +1 -0
- package/esm/color/oklab.js +21 -0
- package/esm/color/oklab.js.map +1 -0
- package/esm/color/oklch.d.ts +7 -0
- package/esm/color/oklch.d.ts.map +1 -0
- package/esm/color/oklch.js +11 -0
- package/esm/color/oklch.js.map +1 -0
- package/esm/color/parse.d.ts +9 -0
- package/esm/color/parse.d.ts.map +1 -0
- package/esm/color/parse.js +452 -0
- package/esm/color/parse.js.map +1 -0
- package/esm/color/polar.js +27 -0
- package/esm/color/polar.js.map +1 -0
- package/esm/color/prophoto-rgb.d.ts +7 -0
- package/esm/color/prophoto-rgb.d.ts.map +1 -0
- package/esm/color/prophoto-rgb.js +18 -0
- package/esm/color/prophoto-rgb.js.map +1 -0
- package/esm/color/rec2020.d.ts +7 -0
- package/esm/color/rec2020.d.ts.map +1 -0
- package/esm/color/rec2020.js +13 -0
- package/esm/color/rec2020.js.map +1 -0
- package/esm/color/resolve.d.ts +18 -0
- package/esm/color/resolve.d.ts.map +1 -0
- package/esm/color/resolve.js +98 -0
- package/esm/color/resolve.js.map +1 -0
- package/esm/color/rgb.js +20 -0
- package/esm/color/rgb.js.map +1 -0
- package/esm/color/serialize.d.ts +24 -0
- package/esm/color/serialize.d.ts.map +1 -0
- package/esm/color/serialize.js +88 -0
- package/esm/color/serialize.js.map +1 -0
- package/esm/color/spaces.d.ts +6 -0
- package/esm/color/spaces.d.ts.map +1 -0
- package/esm/color/spaces.js +42 -0
- package/esm/color/spaces.js.map +1 -0
- package/esm/color/srgb-linear.d.ts +7 -0
- package/esm/color/srgb-linear.d.ts.map +1 -0
- package/esm/color/srgb-linear.js +12 -0
- package/esm/color/srgb-linear.js.map +1 -0
- package/esm/color/srgb.d.ts +7 -0
- package/esm/color/srgb.d.ts.map +1 -0
- package/esm/color/srgb.js +12 -0
- package/esm/color/srgb.js.map +1 -0
- package/esm/color/transfer.js +11 -0
- package/esm/color/transfer.js.map +1 -0
- package/esm/color/types.d.ts +23 -0
- package/esm/color/types.d.ts.map +1 -0
- package/esm/color/unknown-name.js +16 -0
- package/esm/color/unknown-name.js.map +1 -0
- package/esm/color/xyz-d50.d.ts +7 -0
- package/esm/color/xyz-d50.d.ts.map +1 -0
- package/esm/color/xyz-d50.js +15 -0
- package/esm/color/xyz-d50.js.map +1 -0
- package/esm/color/xyz-d65.d.ts +4 -0
- package/esm/color/xyz-d65.d.ts.map +1 -0
- package/esm/color/xyz-d65.js +10 -0
- package/esm/color/xyz-d65.js.map +1 -0
- package/esm/index.d.ts +63 -6
- package/esm/index.d.ts.map +1 -0
- package/esm/index.js +54 -5
- package/esm/index.js.map +1 -0
- package/esm/legacy/createColors.d.ts +9 -0
- package/esm/legacy/createColors.d.ts.map +1 -0
- package/esm/{createColors.js → legacy/createColors.js} +7 -2
- package/esm/legacy/createColors.js.map +1 -0
- package/esm/legacy/createColorsFromMap.d.ts +9 -0
- package/esm/legacy/createColorsFromMap.d.ts.map +1 -0
- package/esm/{createColorsFromMap.js → legacy/createColorsFromMap.js} +4 -2
- package/esm/legacy/createColorsFromMap.js.map +1 -0
- package/esm/legacy/index.d.ts +6 -0
- package/esm/legacy/index.js +5 -0
- package/esm/legacy/interfaces.d.ts +19 -0
- package/esm/legacy/interfaces.d.ts.map +1 -0
- package/esm/{rgbHex.d.ts → legacy/rgbHex.d.ts} +1 -4
- package/esm/legacy/rgbHex.d.ts.map +1 -0
- package/esm/{rgbHex.js → legacy/rgbHex.js} +1 -2
- package/esm/legacy/rgbHex.js.map +1 -0
- package/esm/legacy/rgbaString.d.ts +4 -0
- package/esm/{rgbaString.d.ts.map → legacy/rgbaString.d.ts.map} +1 -1
- package/esm/legacy/rgbaString.js +8 -0
- package/esm/legacy/rgbaString.js.map +1 -0
- package/esm/palette/dark.d.ts +23 -0
- package/esm/palette/dark.d.ts.map +1 -0
- package/esm/palette/dark.js +54 -0
- package/esm/palette/dark.js.map +1 -0
- package/esm/palette/harmony.d.ts +56 -0
- package/esm/palette/harmony.d.ts.map +1 -0
- package/esm/palette/harmony.js +115 -0
- package/esm/palette/harmony.js.map +1 -0
- package/esm/palette/index.d.ts +5 -0
- package/esm/palette/index.js +5 -0
- package/esm/palette/semantic.d.ts +53 -0
- package/esm/palette/semantic.d.ts.map +1 -0
- package/esm/palette/semantic.js +96 -0
- package/esm/palette/semantic.js.map +1 -0
- package/esm/palette/validate.d.ts +39 -0
- package/esm/palette/validate.d.ts.map +1 -0
- package/esm/palette/validate.js +56 -0
- package/esm/palette/validate.js.map +1 -0
- package/esm/scale/brand.d.ts +21 -0
- package/esm/scale/brand.d.ts.map +1 -0
- package/esm/scale/brand.js +63 -0
- package/esm/scale/brand.js.map +1 -0
- package/esm/scale/contrast-scale.d.ts +33 -0
- package/esm/scale/contrast-scale.d.ts.map +1 -0
- package/esm/scale/contrast-scale.js +26 -0
- package/esm/scale/contrast-scale.js.map +1 -0
- package/esm/scale/gradient.d.ts +38 -0
- package/esm/scale/gradient.d.ts.map +1 -0
- package/esm/scale/gradient.js +73 -0
- package/esm/scale/gradient.js.map +1 -0
- package/esm/scale/index.d.ts +6 -0
- package/esm/scale/index.js +6 -0
- package/esm/scale/scale.d.ts +31 -0
- package/esm/scale/scale.d.ts.map +1 -0
- package/esm/scale/scale.js +152 -0
- package/esm/scale/scale.js.map +1 -0
- package/esm/scale/tone.d.ts +25 -0
- package/esm/scale/tone.d.ts.map +1 -0
- package/esm/scale/tone.js +96 -0
- package/esm/scale/tone.js.map +1 -0
- package/esm/tokens/css.d.ts +18 -0
- package/esm/tokens/css.d.ts.map +1 -0
- package/esm/tokens/css.js +23 -0
- package/esm/tokens/css.js.map +1 -0
- package/esm/tokens/dtcg.d.ts +33 -0
- package/esm/tokens/dtcg.d.ts.map +1 -0
- package/esm/tokens/dtcg.js +42 -0
- package/esm/tokens/dtcg.js.map +1 -0
- package/esm/tokens/index.d.ts +5 -0
- package/esm/tokens/index.js +4 -0
- package/esm/tokens/tailwind.d.ts +19 -0
- package/esm/tokens/tailwind.d.ts.map +1 -0
- package/esm/tokens/tailwind.js +19 -0
- package/esm/tokens/tailwind.js.map +1 -0
- package/esm/tokens/tokens.d.ts +11 -0
- package/esm/tokens/tokens.d.ts.map +1 -0
- package/esm/tokens/tokens.js +77 -0
- package/esm/tokens/tokens.js.map +1 -0
- package/llms.txt +29 -0
- package/package.json +276 -64
- package/plugin.json +18 -0
- package/schemas/contrast.schema.json +24 -0
- package/schemas/convert.schema.json +60 -0
- package/schemas/export.schema.json +106 -0
- package/schemas/find-contrast.schema.json +51 -0
- package/schemas/harmony.schema.json +58 -0
- package/schemas/mix.schema.json +72 -0
- package/schemas/palette.schema.json +56 -0
- package/schemas/scale.schema.json +86 -0
- package/skills/color-map/SKILL.md +38 -0
- package/src/cli/bin.ts +11 -0
- package/src/cli/cli.ts +40 -0
- package/src/cli/commands/contrast.ts +36 -0
- package/src/cli/commands/convert.ts +37 -0
- package/src/cli/commands/export.ts +102 -0
- package/src/cli/commands/find-contrast.ts +37 -0
- package/src/cli/commands/harmony.ts +30 -0
- package/src/cli/commands/mix.ts +29 -0
- package/src/cli/commands/palette.ts +66 -0
- package/src/cli/commands/scale.ts +75 -0
- package/src/cli/index.ts +2 -0
- package/src/cli/schema-command.ts +31 -0
- package/src/cli/schemas.ts +86 -0
- package/src/cli/shared.ts +72 -0
- package/src/cli/testing.ts +27 -0
- package/src/color/a98-rgb.ts +11 -0
- package/src/color/argb.ts +21 -0
- package/src/color/calc.ts +84 -0
- package/src/color/compare.ts +53 -0
- package/src/color/contrast.ts +277 -0
- package/src/color/convert.ts +43 -0
- package/src/color/display-p3-linear.ts +9 -0
- package/src/color/display-p3.ts +9 -0
- package/src/color/expr.ts +158 -0
- package/src/color/gamut.ts +215 -0
- package/src/color/hsl.ts +57 -0
- package/src/color/hwb.ts +26 -0
- package/src/color/index.ts +43 -0
- package/src/color/lab.ts +29 -0
- package/src/color/lch.ts +8 -0
- package/src/color/math.ts +30 -0
- package/src/color/matrices.ts +91 -0
- package/src/color/mix.ts +158 -0
- package/src/color/named.ts +13 -0
- package/src/color/oklab.ts +19 -0
- package/src/color/oklch.ts +8 -0
- package/src/color/parse.ts +355 -0
- package/src/color/polar.ts +17 -0
- package/src/color/prophoto-rgb.ts +18 -0
- package/src/color/rec2020.ts +11 -0
- package/src/color/resolve.ts +87 -0
- package/src/color/rgb.ts +20 -0
- package/src/color/serialize.ts +96 -0
- package/src/color/spaces.ts +42 -0
- package/src/color/srgb-linear.ts +9 -0
- package/src/color/srgb.ts +9 -0
- package/src/color/test-utils.ts +10 -0
- package/src/color/transfer.ts +14 -0
- package/src/color/types.ts +41 -0
- package/src/color/unknown-name.ts +15 -0
- package/src/color/xyz-d50.ts +13 -0
- package/src/color/xyz-d65.ts +8 -0
- package/src/index.ts +37 -0
- package/{ts → src/legacy}/createColors.ts +6 -0
- package/{ts → src/legacy}/createColorsFromMap.ts +3 -0
- package/{ts → src/legacy}/interfaces.ts +4 -0
- package/{ts → src/legacy}/rgbaString.ts +1 -0
- package/src/palette/dark.ts +55 -0
- package/src/palette/harmony.ts +107 -0
- package/src/palette/index.ts +18 -0
- package/src/palette/semantic.ts +120 -0
- package/src/palette/validate.ts +82 -0
- package/src/scale/brand.ts +50 -0
- package/src/scale/contrast-scale.ts +48 -0
- package/src/scale/gradient.ts +91 -0
- package/src/scale/index.ts +5 -0
- package/src/scale/scale.ts +127 -0
- package/src/scale/tone.ts +69 -0
- package/src/tokens/css.ts +32 -0
- package/src/tokens/dtcg.ts +62 -0
- package/src/tokens/index.ts +11 -0
- package/src/tokens/tailwind.ts +24 -0
- package/src/tokens/tokens.ts +82 -0
- package/esm/createColors.d.ts +0 -6
- package/esm/createColors.d.ts.map +0 -1
- package/esm/createColors.js.map +0 -1
- package/esm/createColorsFromMap.d.ts +0 -9
- package/esm/createColorsFromMap.d.ts.map +0 -1
- package/esm/createColorsFromMap.js.map +0 -1
- package/esm/interfaces.d.ts +0 -18
- package/esm/interfaces.d.ts.map +0 -1
- package/esm/rgbHex.d.ts.map +0 -1
- package/esm/rgbHex.js.map +0 -1
- package/esm/rgbaString.d.ts +0 -6
- package/esm/rgbaString.js +0 -8
- package/esm/rgbaString.js.map +0 -1
- /package/{ts → src/legacy}/index.ts +0 -0
- /package/{ts → src/legacy}/rgbHex.ts +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calc.js","names":[],"sources":["../../src/color/calc.ts"],"sourcesContent":["import { fail, UNITS } from './parse.js'\n\nconst TOKEN = /\\s*(?:(\\d*\\.?\\d+(?:e[+-]?\\d+)?)([a-z%]*)|([a-z][a-z0-9-]*)|([-+*/(),]))/y\n\n/**\n * Evaluates the `calc()` subset relative colors accept: numbers (angles become\n * degrees), `+ - * /`, parentheses, `calc`, `min`, `max`, `clamp`, the\n * constants `pi` and `e`, and the channel keywords in `vars`.\n */\nexport function evaluate(text: string, vars: Record<string, number>): number {\n\tconst tokens: string[] = []\n\tTOKEN.lastIndex = 0\n\tlet end = 0\n\tfor (let m = TOKEN.exec(text); m; m = TOKEN.exec(text)) {\n\t\tend = TOKEN.lastIndex\n\t\tif (m[1] === undefined) tokens.push(m[3] ?? m[4])\n\t\telse {\n\t\t\tconst unit = UNITS[m[2]]\n\t\t\tif (unit === undefined) return fail(text, 'units other than angles are not supported in calc()', m[1] + m[2])\n\t\t\ttokens.push(String(Number(m[1]) * unit))\n\t\t}\n\t}\n\tif (end !== text.trimEnd().length) return fail(text, 'invalid calc() expression')\n\tlet pos = 0\n\tconst next = (): string => tokens[pos++]\n\tconst eat = (t: string): boolean => (tokens[pos] === t ? ++pos > 0 : false)\n\tconst need = (t: string): void => {\n\t\tif (!eat(t)) fail(text, `expected \"${t}\" in calc()`, tokens[pos])\n\t}\n\tfunction sum(): number {\n\t\tlet v = product()\n\t\tfor (;;) {\n\t\t\tif (eat('+')) v += product()\n\t\t\telse if (eat('-')) v -= product()\n\t\t\telse return v\n\t\t}\n\t}\n\tfunction product(): number {\n\t\tlet v = unary()\n\t\tfor (;;) {\n\t\t\tif (eat('*')) v *= unary()\n\t\t\telse if (eat('/')) v /= unary()\n\t\t\telse return v\n\t\t}\n\t}\n\tfunction unary(): number {\n\t\tif (eat('-')) return -unary()\n\t\tif (eat('+')) return unary()\n\t\treturn atom()\n\t}\n\tfunction args(): number[] {\n\t\tconst list = [sum()]\n\t\twhile (eat(',')) list.push(sum())\n\t\tneed(')')\n\t\treturn list\n\t}\n\tfunction atom(): number {\n\t\tconst t = next()\n\t\tif (t === undefined) return fail(text, 'unfinished calc() expression')\n\t\tif (/^[\\d.]/.test(t)) return Number(t)\n\t\tif (t === '(') {\n\t\t\tconst v = sum()\n\t\t\tneed(')')\n\t\t\treturn v\n\t\t}\n\t\tif (!/^[a-z]/.test(t)) return fail(text, 'unexpected token', t)\n\t\tif (eat('(')) {\n\t\t\tconst list = args()\n\t\t\tif (t === 'calc' && list.length === 1) return list[0]\n\t\t\tif (t === 'min') return Math.min(...list)\n\t\t\tif (t === 'max') return Math.max(...list)\n\t\t\tif (t === 'clamp' && list.length === 3) return Math.min(list[2], Math.max(list[0], list[1]))\n\t\t\treturn fail(text, 'unknown function', t)\n\t\t}\n\t\tif (t === 'pi') return Math.PI\n\t\tif (t === 'e') return Math.E\n\t\tconst v = vars[t]\n\t\tif (typeof v === 'number') return v\n\t\treturn fail(text, 'unknown keyword', t)\n\t}\n\tconst value = sum()\n\tif (pos < tokens.length) return fail(text, 'unexpected token', tokens[pos])\n\treturn value\n}\n"],"mappings":";;AAEA,MAAM,QAAQ;;;;;;AAOd,SAAgB,SAAS,MAAc,MAAsC;CAC5E,MAAM,SAAmB,CAAC;CAC1B,MAAM,YAAY;CAClB,IAAI,MAAM;CACV,KAAK,IAAI,IAAI,MAAM,KAAK,IAAI,GAAG,GAAG,IAAI,MAAM,KAAK,IAAI,GAAG;EACvD,MAAM,MAAM;EACZ,IAAI,EAAE,OAAO,KAAA,GAAW,OAAO,KAAK,EAAE,MAAM,EAAE,EAAE;OAC3C;GACJ,MAAM,OAAO,MAAM,EAAE;GACrB,IAAI,SAAS,KAAA,GAAW,OAAO,KAAK,MAAM,uDAAuD,EAAE,KAAK,EAAE,EAAE;GAC5G,OAAO,KAAK,OAAO,OAAO,EAAE,EAAE,IAAI,IAAI,CAAC;EACxC;CACD;CACA,IAAI,QAAQ,KAAK,QAAQ,CAAC,CAAC,QAAQ,OAAO,KAAK,MAAM,2BAA2B;CAChF,IAAI,MAAM;CACV,MAAM,aAAqB,OAAO;CAClC,MAAM,OAAO,MAAwB,OAAO,SAAS,IAAI,EAAE,MAAM,IAAI;CACrE,MAAM,QAAQ,MAAoB;EACjC,IAAI,CAAC,IAAI,CAAC,GAAG,KAAK,MAAM,aAAa,EAAE,cAAc,OAAO,IAAI;CACjE;CACA,SAAS,MAAc;EACtB,IAAI,IAAI,QAAQ;EAChB,SACC,IAAI,IAAI,GAAG,GAAG,KAAK,QAAQ;OACtB,IAAI,IAAI,GAAG,GAAG,KAAK,QAAQ;OAC3B,OAAO;CAEd;CACA,SAAS,UAAkB;EAC1B,IAAI,IAAI,MAAM;EACd,SACC,IAAI,IAAI,GAAG,GAAG,KAAK,MAAM;OACpB,IAAI,IAAI,GAAG,GAAG,KAAK,MAAM;OACzB,OAAO;CAEd;CACA,SAAS,QAAgB;EACxB,IAAI,IAAI,GAAG,GAAG,OAAO,CAAC,MAAM;EAC5B,IAAI,IAAI,GAAG,GAAG,OAAO,MAAM;EAC3B,OAAO,KAAK;CACb;CACA,SAAS,OAAiB;EACzB,MAAM,OAAO,CAAC,IAAI,CAAC;EACnB,OAAO,IAAI,GAAG,GAAG,KAAK,KAAK,IAAI,CAAC;EAChC,KAAK,GAAG;EACR,OAAO;CACR;CACA,SAAS,OAAe;EACvB,MAAM,IAAI,KAAK;EACf,IAAI,MAAM,KAAA,GAAW,OAAO,KAAK,MAAM,8BAA8B;EACrE,IAAI,SAAS,KAAK,CAAC,GAAG,OAAO,OAAO,CAAC;EACrC,IAAI,MAAM,KAAK;GACd,MAAM,IAAI,IAAI;GACd,KAAK,GAAG;GACR,OAAO;EACR;EACA,IAAI,CAAC,SAAS,KAAK,CAAC,GAAG,OAAO,KAAK,MAAM,oBAAoB,CAAC;EAC9D,IAAI,IAAI,GAAG,GAAG;GACb,MAAM,OAAO,KAAK;GAClB,IAAI,MAAM,UAAU,KAAK,WAAW,GAAG,OAAO,KAAK;GACnD,IAAI,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG,IAAI;GACxC,IAAI,MAAM,OAAO,OAAO,KAAK,IAAI,GAAG,IAAI;GACxC,IAAI,MAAM,WAAW,KAAK,WAAW,GAAG,OAAO,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE,CAAC;GAC3F,OAAO,KAAK,MAAM,oBAAoB,CAAC;EACxC;EACA,IAAI,MAAM,MAAM,OAAO,KAAK;EAC5B,IAAI,MAAM,KAAK,OAAO,KAAK;EAC3B,MAAM,IAAI,KAAK;EACf,IAAI,OAAO,MAAM,UAAU,OAAO;EAClC,OAAO,KAAK,MAAM,mBAAmB,CAAC;CACvC;CACA,MAAM,QAAQ,IAAI;CAClB,IAAI,MAAM,OAAO,QAAQ,OAAO,KAAK,MAAM,oBAAoB,OAAO,IAAI;CAC1E,OAAO;AACR"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Color, Space } from "./types.js";
|
|
2
|
+
/** Euclidean distance in OKLab, the ΔEOK of CSS Color 4 gamut mapping. */
|
|
3
|
+
export declare function deltaEOk(a: Color, b: Color): number;
|
|
4
|
+
/**
|
|
5
|
+
* Builds {@link deltaEOk} over the spaces given, plus `oklab`, so a bundle
|
|
6
|
+
* contains only those. A color in a space not given throws, naming that space.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createDeltaEOk(...spaces: Space[]): (a: Color, b: Color) => number;
|
|
9
|
+
/**
|
|
10
|
+
* True when both colors are the same within `epsilon` per component. Colors in
|
|
11
|
+
* different spaces are compared in the first one's space. `none` equals only
|
|
12
|
+
* `none`, and a missing alpha equals 1.
|
|
13
|
+
*/
|
|
14
|
+
export declare function equals(a: Color, b: Color, epsilon?: number): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Builds {@link equals} over the spaces given, so a bundle contains only those.
|
|
17
|
+
* It needs no space of its own: give the spaces of both colors.
|
|
18
|
+
*/
|
|
19
|
+
export declare function createEquals(...spaces: Space[]): (a: Color, b: Color, epsilon?: number) => boolean;
|
|
20
|
+
//# sourceMappingURL=compare.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.d.ts","names":[],"sources":["../../src/color/compare.ts"],"mappings":";;wBAMgB,SAAS,GAAG,OAAO,GAAG;;;;;wBAetB,kBAAkB,QAAQ,WAAW,GAAG,OAAO,GAAG;;;;;;wBAalD,OAAO,GAAG,OAAO,GAAG,OAAO;;;;;wBAe3B,gBAAgB,QAAQ,WAAW,GAAG,OAAO,GAAG,OAAO"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { componentsOver, convertWith } from "./convert.js";
|
|
2
|
+
import { oklab } from "./oklab.js";
|
|
3
|
+
import { componentsIn } from "./spaces.js";
|
|
4
|
+
// src/color/compare.ts
|
|
5
|
+
/** Euclidean distance in OKLab, the ΔEOK of CSS Color 4 gamut mapping. */
|
|
6
|
+
function deltaEOk(a, b) {
|
|
7
|
+
return deltaEOkWith(componentsIn, a, b);
|
|
8
|
+
}
|
|
9
|
+
/** {@link deltaEOk} over the spaces `componentsIn` knows, which must include `oklab`. */
|
|
10
|
+
function deltaEOkWith(componentsIn, a, b) {
|
|
11
|
+
const [l1, a1, b1] = componentsIn(a, "oklab");
|
|
12
|
+
const [l2, a2, b2] = componentsIn(b, "oklab");
|
|
13
|
+
return Math.hypot(l1 - l2, a1 - a2, b1 - b2);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Builds {@link deltaEOk} over the spaces given, plus `oklab`, so a bundle
|
|
17
|
+
* contains only those. A color in a space not given throws, naming that space.
|
|
18
|
+
*/
|
|
19
|
+
function createDeltaEOk(...spaces) {
|
|
20
|
+
const over = componentsOver([oklab, ...spaces]);
|
|
21
|
+
return (a, b) => deltaEOkWith(over, a, b);
|
|
22
|
+
}
|
|
23
|
+
const same = (x, y, epsilon) => x === y || x != null && y != null && Math.abs(x - y) <= epsilon;
|
|
24
|
+
/**
|
|
25
|
+
* True when both colors are the same within `epsilon` per component. Colors in
|
|
26
|
+
* different spaces are compared in the first one's space. `none` equals only
|
|
27
|
+
* `none`, and a missing alpha equals 1.
|
|
28
|
+
*/
|
|
29
|
+
function equals(a, b, epsilon = 1e-6) {
|
|
30
|
+
return equalsWith(componentsIn, a, b, epsilon);
|
|
31
|
+
}
|
|
32
|
+
function equalsWith(componentsIn, a, b, epsilon) {
|
|
33
|
+
const other = convertWith(componentsIn, b, a.colorSpace);
|
|
34
|
+
return same(a.alpha ?? 1, other.alpha ?? 1, epsilon) && a.components.every((c, i) => same(c, other.components[i], epsilon));
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Builds {@link equals} over the spaces given, so a bundle contains only those.
|
|
38
|
+
* It needs no space of its own: give the spaces of both colors.
|
|
39
|
+
*/
|
|
40
|
+
function createEquals(...spaces) {
|
|
41
|
+
const over = componentsOver(spaces);
|
|
42
|
+
return (a, b, epsilon = 1e-6) => equalsWith(over, a, b, epsilon);
|
|
43
|
+
}
|
|
44
|
+
export { createDeltaEOk, createEquals, deltaEOk, deltaEOkWith, equals };
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=compare.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compare.js","names":[],"sources":["../../src/color/compare.ts"],"sourcesContent":["import { type ComponentsIn, componentsOver, convertWith } from './convert.js'\nimport { oklab } from './oklab.js'\nimport { componentsIn } from './spaces.js'\nimport type { Color, Space } from './types.js'\n\n/** Euclidean distance in OKLab, the ΔEOK of CSS Color 4 gamut mapping. */\nexport function deltaEOk(a: Color, b: Color): number {\n\treturn deltaEOkWith(componentsIn, a, b)\n}\n\n/** {@link deltaEOk} over the spaces `componentsIn` knows, which must include `oklab`. */\nexport function deltaEOkWith(componentsIn: ComponentsIn, a: Color, b: Color): number {\n\tconst [l1, a1, b1] = componentsIn(a, 'oklab') as number[]\n\tconst [l2, a2, b2] = componentsIn(b, 'oklab') as number[]\n\treturn Math.hypot(l1 - l2, a1 - a2, b1 - b2)\n}\n\n/**\n * Builds {@link deltaEOk} over the spaces given, plus `oklab`, so a bundle\n * contains only those. A color in a space not given throws, naming that space.\n */\nexport function createDeltaEOk(...spaces: Space[]): (a: Color, b: Color) => number {\n\tconst over = componentsOver([oklab, ...spaces])\n\treturn (a, b) => deltaEOkWith(over, a, b)\n}\n\nconst same = (x: number | null | undefined, y: number | null | undefined, epsilon: number): boolean =>\n\tx === y || (x != null && y != null && Math.abs(x - y) <= epsilon)\n\n/**\n * True when both colors are the same within `epsilon` per component. Colors in\n * different spaces are compared in the first one's space. `none` equals only\n * `none`, and a missing alpha equals 1.\n */\nexport function equals(a: Color, b: Color, epsilon = 1e-6): boolean {\n\treturn equalsWith(componentsIn, a, b, epsilon)\n}\n\nfunction equalsWith(componentsIn: ComponentsIn, a: Color, b: Color, epsilon: number): boolean {\n\tconst other = convertWith(componentsIn, b, a.colorSpace)\n\treturn (\n\t\tsame(a.alpha ?? 1, other.alpha ?? 1, epsilon) && a.components.every((c, i) => same(c, other.components[i], epsilon))\n\t)\n}\n\n/**\n * Builds {@link equals} over the spaces given, so a bundle contains only those.\n * It needs no space of its own: give the spaces of both colors.\n */\nexport function createEquals(...spaces: Space[]): (a: Color, b: Color, epsilon?: number) => boolean {\n\tconst over = componentsOver(spaces)\n\treturn (a, b, epsilon = 1e-6) => equalsWith(over, a, b, epsilon)\n}\n"],"mappings":";;;;;AAMA,SAAgB,SAAS,GAAU,GAAkB;CACpD,OAAO,aAAa,cAAc,GAAG,CAAC;AACvC;;AAGA,SAAgB,aAAa,cAA4B,GAAU,GAAkB;CACpF,MAAM,CAAC,IAAI,IAAI,MAAM,aAAa,GAAG,OAAO;CAC5C,MAAM,CAAC,IAAI,IAAI,MAAM,aAAa,GAAG,OAAO;CAC5C,OAAO,KAAK,MAAM,KAAK,IAAI,KAAK,IAAI,KAAK,EAAE;AAC5C;;;;;AAMA,SAAgB,eAAe,GAAG,QAAiD;CAClF,MAAM,OAAO,eAAe,CAAC,OAAO,GAAG,MAAM,CAAC;CAC9C,QAAQ,GAAG,MAAM,aAAa,MAAM,GAAG,CAAC;AACzC;AAEA,MAAM,QAAQ,GAA8B,GAA8B,YACzE,MAAM,KAAM,KAAK,QAAQ,KAAK,QAAQ,KAAK,IAAI,IAAI,CAAC,KAAK;;;;;;AAO1D,SAAgB,OAAO,GAAU,GAAU,UAAU,MAAe;CACnE,OAAO,WAAW,cAAc,GAAG,GAAG,OAAO;AAC9C;AAEA,SAAS,WAAW,cAA4B,GAAU,GAAU,SAA0B;CAC7F,MAAM,QAAQ,YAAY,cAAc,GAAG,EAAE,UAAU;CACvD,OACC,KAAK,EAAE,SAAS,GAAG,MAAM,SAAS,GAAG,OAAO,KAAK,EAAE,WAAW,OAAO,GAAG,MAAM,KAAK,GAAG,MAAM,WAAW,IAAI,OAAO,CAAC;AAErH;;;;;AAMA,SAAgB,aAAa,GAAG,QAAoE;CACnG,MAAM,OAAO,eAAe,MAAM;CAClC,QAAQ,GAAG,GAAG,UAAU,SAAS,WAAW,MAAM,GAAG,GAAG,OAAO;AAChE"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { Color, Space } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* A contrast measure: `(fg, bg) => number`, plus what a caller needs to use it
|
|
4
|
+
* without knowing the algorithm. Any plain `(fg, bg) => number` also works
|
|
5
|
+
* wherever a `ContrastFn` is taken; the extra fields are optional.
|
|
6
|
+
*/
|
|
7
|
+
export interface ContrastFn {
|
|
8
|
+
(fg: Color, bg: Color): number;
|
|
9
|
+
/** A label for messages and output, for example `wcag2`. */
|
|
10
|
+
readonly algorithm?: string;
|
|
11
|
+
/** Named targets, for example `{ AA: 4.5 }`, which `meets` accepts as a level. */
|
|
12
|
+
readonly thresholds?: Readonly<Record<string, number>>;
|
|
13
|
+
/** `symmetric` when swapping fg and bg gives the same number; `signed` when the sign says which is lighter. */
|
|
14
|
+
readonly polarity?: 'symmetric' | 'signed';
|
|
15
|
+
}
|
|
16
|
+
/** The contrast algorithms built in. */
|
|
17
|
+
export type ContrastAlgorithm = 'wcag2';
|
|
18
|
+
/** Either a built-in name or a {@link ContrastFn}. */
|
|
19
|
+
export type ContrastOption = ContrastAlgorithm | ContrastFn;
|
|
20
|
+
type ColorInput = Color | string;
|
|
21
|
+
/**
|
|
22
|
+
* WCAG 2 contrast ratio, 1 to 21. A translucent `fg` is laid over `bg` first;
|
|
23
|
+
* `bg` is taken as opaque.
|
|
24
|
+
*/
|
|
25
|
+
export declare const wcag2: ContrastFn;
|
|
26
|
+
/**
|
|
27
|
+
* Contrast of `fg` on `bg`. Colors may be CSS strings. The default is WCAG 2;
|
|
28
|
+
* pass a {@link ContrastFn} to use another measure (see the APCA recipe).
|
|
29
|
+
*/
|
|
30
|
+
export declare function contrast(fg: ColorInput, bg: ColorInput, algo?: ContrastOption): number;
|
|
31
|
+
/**
|
|
32
|
+
* Builds {@link contrast} over the spaces given, plus `srgb` and `xyz-d65`,
|
|
33
|
+
* which WCAG 2 works in, so a bundle contains only those. It takes color
|
|
34
|
+
* objects, not strings, so it does not carry `parse`. A color in a space not
|
|
35
|
+
* given throws, naming that space.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createContrast(...spaces: Space[]): (fg: Color, bg: Color, algo?: ContrastOption) => number;
|
|
38
|
+
/**
|
|
39
|
+
* True when `fg` on `bg` reaches `level`: a number, or a name from the
|
|
40
|
+
* algorithm's thresholds (WCAG 2: `AA`, `AA-large`, `AAA`, `AAA-large`,
|
|
41
|
+
* `non-text`). A signed measure such as APCA is compared by magnitude.
|
|
42
|
+
*/
|
|
43
|
+
export declare function meets(fg: ColorInput, bg: ColorInput, level: number | string, algo?: ContrastOption): boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Builds {@link meets} over the spaces given, plus `srgb` and `xyz-d65`, which
|
|
46
|
+
* WCAG 2 works in, so a bundle contains only those. It takes color objects,
|
|
47
|
+
* not strings, so it does not carry `parse`. A color in a space not given
|
|
48
|
+
* throws, naming that space.
|
|
49
|
+
*/
|
|
50
|
+
export declare function createMeets(...spaces: Space[]): (fg: Color, bg: Color, level: number | string, algo?: ContrastOption) => boolean;
|
|
51
|
+
/** Options for `findContrast` and the function `createFindContrast` returns. */
|
|
52
|
+
export interface FindContrastOptions {
|
|
53
|
+
/** Hue of the result, in OKLCH degrees. Default: the hue of `bg`. */
|
|
54
|
+
hue?: number;
|
|
55
|
+
/** Chroma of the result, in OKLCH. Default 0. The result is mapped into the gamut. */
|
|
56
|
+
chroma?: number;
|
|
57
|
+
/** Which way to look first from `bg`. Default: darker on a light `bg`, lighter on a dark one. */
|
|
58
|
+
direction?: 'lighter' | 'darker';
|
|
59
|
+
/** Default `wcag2`. */
|
|
60
|
+
contrast?: ContrastOption;
|
|
61
|
+
}
|
|
62
|
+
/** What `findContrast` returns: the color it found and whether it reached the target. */
|
|
63
|
+
export interface FoundContrast {
|
|
64
|
+
/** The color, in `srgb` and inside the gamut. */
|
|
65
|
+
color: Color;
|
|
66
|
+
/** The contrast it achieves against `bg`, as the algorithm reports it. */
|
|
67
|
+
contrast: number;
|
|
68
|
+
/** False when no lightness reaches `target`; `color` is then the best one found. */
|
|
69
|
+
met: boolean;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Finds the color closest in lightness to `bg` (OKLCH L, at the given hue and
|
|
73
|
+
* chroma) that reaches `target` contrast against it, by binary search. It looks
|
|
74
|
+
* the preferred way first, then the other. The returned `contrast` is measured
|
|
75
|
+
* on the final, gamut-mapped color, and `met` says whether it reaches `target`.
|
|
76
|
+
*/
|
|
77
|
+
export declare function findContrast(bg: ColorInput, target: number, options?: FindContrastOptions): FoundContrast;
|
|
78
|
+
/**
|
|
79
|
+
* Builds {@link findContrast} over the spaces given, plus `srgb`, `xyz-d65`,
|
|
80
|
+
* `oklch` and `oklab`, which WCAG 2 and gamut mapping work in, so a bundle
|
|
81
|
+
* contains only those. It takes a color object, not a string, so it does not
|
|
82
|
+
* carry `parse`. A color in a space not given throws, naming that space.
|
|
83
|
+
*/
|
|
84
|
+
export declare function createFindContrast(...spaces: Space[]): (bg: Color, target: number, options?: FindContrastOptions) => FoundContrast;
|
|
85
|
+
/** Options for `onColor` and the function `createOnColor` returns. */
|
|
86
|
+
export interface OnColorOptions {
|
|
87
|
+
/** The contrast the text needs. Default 4.5 for WCAG 2. */
|
|
88
|
+
target?: number;
|
|
89
|
+
/** How much of the background's hue the dark text carries, as OKLCH chroma. Default 0.03. */
|
|
90
|
+
chroma?: number;
|
|
91
|
+
/** Default `wcag2`. */
|
|
92
|
+
contrast?: ContrastOption;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* The text color for `bg`, by the Radix rule: white, unless white's contrast
|
|
96
|
+
* on `bg` is under `target`, then a near-black tinted with the hue of `bg`
|
|
97
|
+
* (OKLCH lightness 0.2). When neither reaches `target`, whichever contrasts more.
|
|
98
|
+
*/
|
|
99
|
+
export declare function onColor(bg: ColorInput, options?: OnColorOptions): Color;
|
|
100
|
+
/**
|
|
101
|
+
* Builds {@link onColor} over the spaces given, plus `srgb`, `xyz-d65`,
|
|
102
|
+
* `oklch` and `oklab`, which WCAG 2 and gamut mapping work in, so a bundle
|
|
103
|
+
* contains only those. It takes a color object, not a string, so it does not
|
|
104
|
+
* carry `parse`. A color in a space not given throws, naming that space.
|
|
105
|
+
*/
|
|
106
|
+
export declare function createOnColor(...spaces: Space[]): (bg: Color, options?: OnColorOptions) => Color;
|
|
107
|
+
//# sourceMappingURL=contrast.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.d.ts","names":[],"sources":["../../src/color/contrast.ts"],"mappings":";;;;;;iBAgBiB;GACf,IAAI,OAAO,IAAI;;WAEP;;WAEA,aAAa,SAAS;;WAEtB;;;YAIE;;YAGA,iBAAiB,oBAAoB;KAE5C,aAAa;;;;;qBAoBL,OAAO;;;;;wBA8CJ,SAAS,IAAI,YAAY,IAAI,YAAY,OAAM;;;;;;;wBAU/C,kBAAkB,QAAQ,WAAW,IAAI,OAAO,IAAI,OAAO,OAAO;;;;;;wBAUlE,MAAM,IAAI,YAAY,IAAI,YAAY,wBAAwB,OAAM;;;;;;;wBAYpE,eACZ,QAAQ,WACR,IAAI,OAAO,IAAI,OAAO,wBAAwB,OAAO;;iBAkBxC;;EAEhB;;EAEA;;EAEA;;EAEA,WAAW;;;iBAIK;;EAEhB,OAAO;;EAEP;;EAEA;;;;;;;;wBAWe,aAAa,IAAI,YAAY,gBAAgB,UAAS,sBAA2B;;;;;;;wBAUjF,sBACZ,QAAQ,WACR,IAAI,OAAO,gBAAgB,UAAU,wBAAwB;;iBA+ChD;;EAEhB;;EAEA;;EAEA,WAAW;;;;;;;wBAQI,QAAQ,IAAI,YAAY,UAAS,iBAAsB;;;;;;;wBAUvD,iBAAiB,QAAQ,WAAW,IAAI,OAAO,UAAU,mBAAmB"}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { componentsOver } from "./convert.js";
|
|
2
|
+
import { toGamutWith } from "./gamut.js";
|
|
3
|
+
import { oklab } from "./oklab.js";
|
|
4
|
+
import { oklch } from "./oklch.js";
|
|
5
|
+
import { toColor } from "./parse.js";
|
|
6
|
+
import { componentsIn } from "./spaces.js";
|
|
7
|
+
import { srgb } from "./srgb.js";
|
|
8
|
+
import { unknownName } from "./unknown-name.js";
|
|
9
|
+
import { xyzD65 } from "./xyz-d65.js";
|
|
10
|
+
// src/color/contrast.ts
|
|
11
|
+
/** Relative luminance (WCAG 2): the Y of the color in XYZ-D65, with white at 1. */
|
|
12
|
+
function luminance(componentsIn, color) {
|
|
13
|
+
return Math.max(0, componentsIn(color, "xyz-d65")[1]);
|
|
14
|
+
}
|
|
15
|
+
/** Lays a translucent color over `bg`, in `srgb`, the way a browser paints it. */
|
|
16
|
+
function flatten(componentsIn, fg, bg) {
|
|
17
|
+
const alpha = fg.alpha === null ? 0 : fg.alpha ?? 1;
|
|
18
|
+
if (alpha >= 1) return fg;
|
|
19
|
+
const f = componentsIn(fg, "srgb");
|
|
20
|
+
const b = componentsIn(bg, "srgb");
|
|
21
|
+
return {
|
|
22
|
+
colorSpace: "srgb",
|
|
23
|
+
components: f.map((x, i) => x * alpha + b[i] * (1 - alpha))
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* WCAG 2 contrast ratio, 1 to 21. A translucent `fg` is laid over `bg` first;
|
|
28
|
+
* `bg` is taken as opaque.
|
|
29
|
+
*/
|
|
30
|
+
const wcag2 = /* @__PURE__ */ wcag2Over(componentsIn);
|
|
31
|
+
/** WCAG 2 over the spaces `componentsIn` knows, which must include `srgb` and `xyz-d65`. */
|
|
32
|
+
function wcag2Over(componentsIn) {
|
|
33
|
+
return Object.assign((fg, bg) => {
|
|
34
|
+
const a = luminance(componentsIn, flatten(componentsIn, fg, bg)) + .05;
|
|
35
|
+
const b = luminance(componentsIn, bg) + .05;
|
|
36
|
+
return a > b ? a / b : b / a;
|
|
37
|
+
}, {
|
|
38
|
+
algorithm: "wcag2",
|
|
39
|
+
thresholds: {
|
|
40
|
+
AA: 4.5,
|
|
41
|
+
"AA-large": 3,
|
|
42
|
+
AAA: 7,
|
|
43
|
+
"AAA-large": 4.5,
|
|
44
|
+
"non-text": 3
|
|
45
|
+
},
|
|
46
|
+
polarity: "symmetric"
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/** The measure `algo` names: `wcag2` or a function; anything else is an unknown name. */
|
|
50
|
+
function pick(algo) {
|
|
51
|
+
return pickFrom(wcag2, algo);
|
|
52
|
+
}
|
|
53
|
+
/** {@link pick}, with `builtin` as what `wcag2` names. */
|
|
54
|
+
function pickFrom(builtin, algo) {
|
|
55
|
+
if (algo === "wcag2") return builtin;
|
|
56
|
+
if (typeof algo !== "function") throw unknownName("contrast algorithm", algo, ["wcag2"]);
|
|
57
|
+
return algo;
|
|
58
|
+
}
|
|
59
|
+
/** A {@link ContrastKit} over `needed` and `spaces`. */
|
|
60
|
+
function contrastKit(needed, spaces) {
|
|
61
|
+
const over = componentsOver([...needed, ...spaces]);
|
|
62
|
+
return {
|
|
63
|
+
componentsIn: over,
|
|
64
|
+
wcag2: wcag2Over(over)
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Contrast of `fg` on `bg`. Colors may be CSS strings. The default is WCAG 2;
|
|
69
|
+
* pass a {@link ContrastFn} to use another measure (see the APCA recipe).
|
|
70
|
+
*/
|
|
71
|
+
function contrast(fg, bg, algo = "wcag2") {
|
|
72
|
+
return pick(algo)(toColor(fg), toColor(bg));
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Builds {@link contrast} over the spaces given, plus `srgb` and `xyz-d65`,
|
|
76
|
+
* which WCAG 2 works in, so a bundle contains only those. It takes color
|
|
77
|
+
* objects, not strings, so it does not carry `parse`. A color in a space not
|
|
78
|
+
* given throws, naming that space.
|
|
79
|
+
*/
|
|
80
|
+
function createContrast(...spaces) {
|
|
81
|
+
const kit = contrastKit([srgb, xyzD65], spaces);
|
|
82
|
+
return (fg, bg, algo = "wcag2") => pickFrom(kit.wcag2, algo)(fg, bg);
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* True when `fg` on `bg` reaches `level`: a number, or a name from the
|
|
86
|
+
* algorithm's thresholds (WCAG 2: `AA`, `AA-large`, `AAA`, `AAA-large`,
|
|
87
|
+
* `non-text`). A signed measure such as APCA is compared by magnitude.
|
|
88
|
+
*/
|
|
89
|
+
function meets(fg, bg, level, algo = "wcag2") {
|
|
90
|
+
const fn = pick(algo);
|
|
91
|
+
const target = levelOf(fn, level);
|
|
92
|
+
return Math.abs(fn(toColor(fg), toColor(bg))) >= target;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Builds {@link meets} over the spaces given, plus `srgb` and `xyz-d65`, which
|
|
96
|
+
* WCAG 2 works in, so a bundle contains only those. It takes color objects,
|
|
97
|
+
* not strings, so it does not carry `parse`. A color in a space not given
|
|
98
|
+
* throws, naming that space.
|
|
99
|
+
*/
|
|
100
|
+
function createMeets(...spaces) {
|
|
101
|
+
const kit = contrastKit([srgb, xyzD65], spaces);
|
|
102
|
+
return (fg, bg, level, algo = "wcag2") => {
|
|
103
|
+
const fn = pickFrom(kit.wcag2, algo);
|
|
104
|
+
return Math.abs(fn(fg, bg)) >= levelOf(fn, level);
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/** The contrast `level` stands for under `fn`: the number itself, or one of its thresholds by name. */
|
|
108
|
+
function levelOf(fn, level) {
|
|
109
|
+
const named = typeof level === "number" ? level : fn.thresholds?.[level];
|
|
110
|
+
const target = typeof named === "number" ? named : void 0;
|
|
111
|
+
if (target === void 0) throw unknownName("contrast level", level, Object.keys(fn.thresholds ?? {}));
|
|
112
|
+
return target;
|
|
113
|
+
}
|
|
114
|
+
const STEPS = 32;
|
|
115
|
+
/**
|
|
116
|
+
* Finds the color closest in lightness to `bg` (OKLCH L, at the given hue and
|
|
117
|
+
* chroma) that reaches `target` contrast against it, by binary search. It looks
|
|
118
|
+
* the preferred way first, then the other. The returned `contrast` is measured
|
|
119
|
+
* on the final, gamut-mapped color, and `met` says whether it reaches `target`.
|
|
120
|
+
*/
|
|
121
|
+
function findContrast(bg, target, options = {}) {
|
|
122
|
+
return findContrastWith({
|
|
123
|
+
componentsIn,
|
|
124
|
+
wcag2
|
|
125
|
+
}, toColor(bg), target, options);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Builds {@link findContrast} over the spaces given, plus `srgb`, `xyz-d65`,
|
|
129
|
+
* `oklch` and `oklab`, which WCAG 2 and gamut mapping work in, so a bundle
|
|
130
|
+
* contains only those. It takes a color object, not a string, so it does not
|
|
131
|
+
* carry `parse`. A color in a space not given throws, naming that space.
|
|
132
|
+
*/
|
|
133
|
+
function createFindContrast(...spaces) {
|
|
134
|
+
const kit = contrastKit([
|
|
135
|
+
srgb,
|
|
136
|
+
xyzD65,
|
|
137
|
+
oklch,
|
|
138
|
+
oklab
|
|
139
|
+
], spaces);
|
|
140
|
+
return (bg, target, options = {}) => findContrastWith(kit, bg, target, options);
|
|
141
|
+
}
|
|
142
|
+
function findContrastWith({ componentsIn, wcag2 }, back, target, options) {
|
|
143
|
+
const fn = pickFrom(wcag2, options.contrast ?? "wcag2");
|
|
144
|
+
const lch = componentsIn(back, "oklch");
|
|
145
|
+
const l0 = lch[0];
|
|
146
|
+
const hue = options.hue ?? lch[2];
|
|
147
|
+
const chroma = options.chroma ?? 0;
|
|
148
|
+
const at = (l) => {
|
|
149
|
+
const color = toGamutWith(componentsIn, {
|
|
150
|
+
colorSpace: "oklch",
|
|
151
|
+
components: [
|
|
152
|
+
l,
|
|
153
|
+
chroma,
|
|
154
|
+
hue
|
|
155
|
+
]
|
|
156
|
+
}, {});
|
|
157
|
+
const value = fn(color, back);
|
|
158
|
+
return {
|
|
159
|
+
color,
|
|
160
|
+
contrast: value,
|
|
161
|
+
met: Math.abs(value) >= target
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
const first = options.direction ?? (l0 > .5 ? "darker" : "lighter");
|
|
165
|
+
let best;
|
|
166
|
+
for (const side of [first, first === "darker" ? "lighter" : "darker"]) {
|
|
167
|
+
const far = side === "darker" ? 0 : 1;
|
|
168
|
+
const end = at(far);
|
|
169
|
+
if (!end.met) {
|
|
170
|
+
if (!best || Math.abs(end.contrast) > Math.abs(best.contrast)) best = end;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
let [lo, hi] = [l0, far];
|
|
174
|
+
let found = end;
|
|
175
|
+
for (let i = 0; i < STEPS; i++) {
|
|
176
|
+
const mid = (lo + hi) / 2;
|
|
177
|
+
const candidate = at(mid);
|
|
178
|
+
if (candidate.met) {
|
|
179
|
+
hi = mid;
|
|
180
|
+
found = candidate;
|
|
181
|
+
} else lo = mid;
|
|
182
|
+
}
|
|
183
|
+
return found;
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
...best,
|
|
187
|
+
met: false
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* The text color for `bg`, by the Radix rule: white, unless white's contrast
|
|
192
|
+
* on `bg` is under `target`, then a near-black tinted with the hue of `bg`
|
|
193
|
+
* (OKLCH lightness 0.2). When neither reaches `target`, whichever contrasts more.
|
|
194
|
+
*/
|
|
195
|
+
function onColor(bg, options = {}) {
|
|
196
|
+
return onColorWith({
|
|
197
|
+
componentsIn,
|
|
198
|
+
wcag2
|
|
199
|
+
}, toColor(bg), options);
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Builds {@link onColor} over the spaces given, plus `srgb`, `xyz-d65`,
|
|
203
|
+
* `oklch` and `oklab`, which WCAG 2 and gamut mapping work in, so a bundle
|
|
204
|
+
* contains only those. It takes a color object, not a string, so it does not
|
|
205
|
+
* carry `parse`. A color in a space not given throws, naming that space.
|
|
206
|
+
*/
|
|
207
|
+
function createOnColor(...spaces) {
|
|
208
|
+
const kit = contrastKit([
|
|
209
|
+
srgb,
|
|
210
|
+
xyzD65,
|
|
211
|
+
oklch,
|
|
212
|
+
oklab
|
|
213
|
+
], spaces);
|
|
214
|
+
return (bg, options = {}) => onColorWith(kit, bg, options);
|
|
215
|
+
}
|
|
216
|
+
function onColorWith({ componentsIn, wcag2 }, back, options) {
|
|
217
|
+
const fn = pickFrom(wcag2, options.contrast ?? "wcag2");
|
|
218
|
+
const target = options.target ?? 4.5;
|
|
219
|
+
const white = {
|
|
220
|
+
colorSpace: "srgb",
|
|
221
|
+
components: [
|
|
222
|
+
1,
|
|
223
|
+
1,
|
|
224
|
+
1
|
|
225
|
+
]
|
|
226
|
+
};
|
|
227
|
+
const whiteScore = Math.abs(fn(white, back));
|
|
228
|
+
if (whiteScore >= target) return white;
|
|
229
|
+
const hue = componentsIn(back, "oklch")[2];
|
|
230
|
+
const dark = toGamutWith(componentsIn, {
|
|
231
|
+
colorSpace: "oklch",
|
|
232
|
+
components: [
|
|
233
|
+
.2,
|
|
234
|
+
options.chroma ?? .03,
|
|
235
|
+
hue
|
|
236
|
+
]
|
|
237
|
+
}, {});
|
|
238
|
+
return Math.abs(fn(dark, back)) >= whiteScore ? dark : white;
|
|
239
|
+
}
|
|
240
|
+
export { contrast, createContrast, createFindContrast, createMeets, createOnColor, findContrast, meets, onColor, pick, wcag2 };
|
|
241
|
+
|
|
242
|
+
//# sourceMappingURL=contrast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"contrast.js","names":[],"sources":["../../src/color/contrast.ts"],"sourcesContent":["import { type ComponentsIn, componentsOver } from './convert.js'\nimport { toGamutWith } from './gamut.js'\nimport { oklab } from './oklab.js'\nimport { oklch } from './oklch.js'\nimport { toColor } from './parse.js'\nimport { componentsIn } from './spaces.js'\nimport { srgb } from './srgb.js'\nimport type { Color, Space } from './types.js'\nimport { unknownName } from './unknown-name.js'\nimport { xyzD65 } from './xyz-d65.js'\n\n/**\n * A contrast measure: `(fg, bg) => number`, plus what a caller needs to use it\n * without knowing the algorithm. Any plain `(fg, bg) => number` also works\n * wherever a `ContrastFn` is taken; the extra fields are optional.\n */\nexport interface ContrastFn {\n\t(fg: Color, bg: Color): number\n\t/** A label for messages and output, for example `wcag2`. */\n\treadonly algorithm?: string\n\t/** Named targets, for example `{ AA: 4.5 }`, which `meets` accepts as a level. */\n\treadonly thresholds?: Readonly<Record<string, number>>\n\t/** `symmetric` when swapping fg and bg gives the same number; `signed` when the sign says which is lighter. */\n\treadonly polarity?: 'symmetric' | 'signed'\n}\n\n/** The contrast algorithms built in. */\nexport type ContrastAlgorithm = 'wcag2'\n\n/** Either a built-in name or a {@link ContrastFn}. */\nexport type ContrastOption = ContrastAlgorithm | ContrastFn\n\ntype ColorInput = Color | string\n\n/** Relative luminance (WCAG 2): the Y of the color in XYZ-D65, with white at 1. */\nfunction luminance(componentsIn: ComponentsIn, color: Color): number {\n\treturn Math.max(0, componentsIn(color, 'xyz-d65')[1] as number)\n}\n\n/** Lays a translucent color over `bg`, in `srgb`, the way a browser paints it. */\nfunction flatten(componentsIn: ComponentsIn, fg: Color, bg: Color): Color {\n\tconst alpha = fg.alpha === null ? 0 : (fg.alpha ?? 1)\n\tif (alpha >= 1) return fg\n\tconst f = componentsIn(fg, 'srgb')\n\tconst b = componentsIn(bg, 'srgb')\n\treturn { colorSpace: 'srgb', components: f.map((x, i) => (x as number) * alpha + (b[i] as number) * (1 - alpha)) }\n}\n\n/**\n * WCAG 2 contrast ratio, 1 to 21. A translucent `fg` is laid over `bg` first;\n * `bg` is taken as opaque.\n */\nexport const wcag2: ContrastFn = /* @__PURE__ */ wcag2Over(componentsIn)\n\n/** WCAG 2 over the spaces `componentsIn` knows, which must include `srgb` and `xyz-d65`. */\nfunction wcag2Over(componentsIn: ComponentsIn): ContrastFn {\n\treturn Object.assign(\n\t\t(fg: Color, bg: Color): number => {\n\t\t\tconst a = luminance(componentsIn, flatten(componentsIn, fg, bg)) + 0.05\n\t\t\tconst b = luminance(componentsIn, bg) + 0.05\n\t\t\treturn a > b ? a / b : b / a\n\t\t},\n\t\t{\n\t\t\talgorithm: 'wcag2',\n\t\t\tthresholds: { AA: 4.5, 'AA-large': 3, AAA: 7, 'AAA-large': 4.5, 'non-text': 3 },\n\t\t\tpolarity: 'symmetric'\n\t\t} as const\n\t)\n}\n\n/** The measure `algo` names: `wcag2` or a function; anything else is an unknown name. */\nexport function pick(algo: ContrastOption): ContrastFn {\n\treturn pickFrom(wcag2, algo)\n}\n\n/** {@link pick}, with `builtin` as what `wcag2` names. */\nfunction pickFrom(builtin: ContrastFn, algo: ContrastOption): ContrastFn {\n\tif (algo === 'wcag2') return builtin\n\tif (typeof algo !== 'function') throw unknownName('contrast algorithm', algo, ['wcag2'])\n\treturn algo\n}\n\n/** What a narrow contrast helper works over: its spaces and its own `wcag2`. */\ninterface ContrastKit {\n\tcomponentsIn: ComponentsIn\n\twcag2: ContrastFn\n}\n\n/** A {@link ContrastKit} over `needed` and `spaces`. */\nfunction contrastKit(needed: Space[], spaces: Space[]): ContrastKit {\n\tconst over = componentsOver([...needed, ...spaces])\n\treturn { componentsIn: over, wcag2: wcag2Over(over) }\n}\n\n/**\n * Contrast of `fg` on `bg`. Colors may be CSS strings. The default is WCAG 2;\n * pass a {@link ContrastFn} to use another measure (see the APCA recipe).\n */\nexport function contrast(fg: ColorInput, bg: ColorInput, algo: ContrastOption = 'wcag2'): number {\n\treturn pick(algo)(toColor(fg), toColor(bg))\n}\n\n/**\n * Builds {@link contrast} over the spaces given, plus `srgb` and `xyz-d65`,\n * which WCAG 2 works in, so a bundle contains only those. It takes color\n * objects, not strings, so it does not carry `parse`. A color in a space not\n * given throws, naming that space.\n */\nexport function createContrast(...spaces: Space[]): (fg: Color, bg: Color, algo?: ContrastOption) => number {\n\tconst kit = contrastKit([srgb, xyzD65], spaces)\n\treturn (fg, bg, algo = 'wcag2') => pickFrom(kit.wcag2, algo)(fg, bg)\n}\n\n/**\n * True when `fg` on `bg` reaches `level`: a number, or a name from the\n * algorithm's thresholds (WCAG 2: `AA`, `AA-large`, `AAA`, `AAA-large`,\n * `non-text`). A signed measure such as APCA is compared by magnitude.\n */\nexport function meets(fg: ColorInput, bg: ColorInput, level: number | string, algo: ContrastOption = 'wcag2'): boolean {\n\tconst fn = pick(algo)\n\tconst target = levelOf(fn, level)\n\treturn Math.abs(fn(toColor(fg), toColor(bg))) >= target\n}\n\n/**\n * Builds {@link meets} over the spaces given, plus `srgb` and `xyz-d65`, which\n * WCAG 2 works in, so a bundle contains only those. It takes color objects,\n * not strings, so it does not carry `parse`. A color in a space not given\n * throws, naming that space.\n */\nexport function createMeets(\n\t...spaces: Space[]\n): (fg: Color, bg: Color, level: number | string, algo?: ContrastOption) => boolean {\n\tconst kit = contrastKit([srgb, xyzD65], spaces)\n\treturn (fg, bg, level, algo = 'wcag2') => {\n\t\tconst fn = pickFrom(kit.wcag2, algo)\n\t\treturn Math.abs(fn(fg, bg)) >= levelOf(fn, level)\n\t}\n}\n\n/** The contrast `level` stands for under `fn`: the number itself, or one of its thresholds by name. */\nfunction levelOf(fn: ContrastFn, level: number | string): number {\n\tconst named = typeof level === 'number' ? level : fn.thresholds?.[level]\n\t// A name that is not the algorithm's own (`constructor`) finds something that is not a number.\n\tconst target = typeof named === 'number' ? named : undefined\n\tif (target === undefined) throw unknownName('contrast level', level, Object.keys(fn.thresholds ?? {}))\n\treturn target\n}\n\n/** Options for `findContrast` and the function `createFindContrast` returns. */\nexport interface FindContrastOptions {\n\t/** Hue of the result, in OKLCH degrees. Default: the hue of `bg`. */\n\thue?: number\n\t/** Chroma of the result, in OKLCH. Default 0. The result is mapped into the gamut. */\n\tchroma?: number\n\t/** Which way to look first from `bg`. Default: darker on a light `bg`, lighter on a dark one. */\n\tdirection?: 'lighter' | 'darker'\n\t/** Default `wcag2`. */\n\tcontrast?: ContrastOption\n}\n\n/** What `findContrast` returns: the color it found and whether it reached the target. */\nexport interface FoundContrast {\n\t/** The color, in `srgb` and inside the gamut. */\n\tcolor: Color\n\t/** The contrast it achieves against `bg`, as the algorithm reports it. */\n\tcontrast: number\n\t/** False when no lightness reaches `target`; `color` is then the best one found. */\n\tmet: boolean\n}\n\nconst STEPS = 32\n\n/**\n * Finds the color closest in lightness to `bg` (OKLCH L, at the given hue and\n * chroma) that reaches `target` contrast against it, by binary search. It looks\n * the preferred way first, then the other. The returned `contrast` is measured\n * on the final, gamut-mapped color, and `met` says whether it reaches `target`.\n */\nexport function findContrast(bg: ColorInput, target: number, options: FindContrastOptions = {}): FoundContrast {\n\treturn findContrastWith({ componentsIn, wcag2 }, toColor(bg), target, options)\n}\n\n/**\n * Builds {@link findContrast} over the spaces given, plus `srgb`, `xyz-d65`,\n * `oklch` and `oklab`, which WCAG 2 and gamut mapping work in, so a bundle\n * contains only those. It takes a color object, not a string, so it does not\n * carry `parse`. A color in a space not given throws, naming that space.\n */\nexport function createFindContrast(\n\t...spaces: Space[]\n): (bg: Color, target: number, options?: FindContrastOptions) => FoundContrast {\n\tconst kit = contrastKit([srgb, xyzD65, oklch, oklab], spaces)\n\treturn (bg, target, options = {}) => findContrastWith(kit, bg, target, options)\n}\n\nfunction findContrastWith(\n\t{ componentsIn, wcag2 }: ContrastKit,\n\tback: Color,\n\ttarget: number,\n\toptions: FindContrastOptions\n): FoundContrast {\n\tconst fn = pickFrom(wcag2, options.contrast ?? 'wcag2')\n\tconst lch = componentsIn(back, 'oklch')\n\tconst l0 = lch[0] as number\n\tconst hue = options.hue ?? (lch[2] as number)\n\tconst chroma = options.chroma ?? 0\n\tconst at = (l: number): FoundContrast => {\n\t\tconst color = toGamutWith(componentsIn, { colorSpace: 'oklch', components: [l, chroma, hue] }, {})\n\t\tconst value = fn(color, back)\n\t\treturn { color, contrast: value, met: Math.abs(value) >= target }\n\t}\n\tconst first = options.direction ?? (l0 > 0.5 ? 'darker' : 'lighter')\n\tlet best: FoundContrast | undefined\n\tfor (const side of [first, first === 'darker' ? 'lighter' : 'darker']) {\n\t\tconst far = side === 'darker' ? 0 : 1\n\t\tconst end = at(far)\n\t\tif (!end.met) {\n\t\t\tif (!best || Math.abs(end.contrast) > Math.abs(best.contrast)) best = end\n\t\t\tcontinue\n\t\t}\n\t\t// `lo` is on the bg side and fails or is the bg itself; `hi` always passes.\n\t\tlet [lo, hi] = [l0, far]\n\t\tlet found = end\n\t\tfor (let i = 0; i < STEPS; i++) {\n\t\t\tconst mid = (lo + hi) / 2\n\t\t\tconst candidate = at(mid)\n\t\t\tif (candidate.met) {\n\t\t\t\thi = mid\n\t\t\t\tfound = candidate\n\t\t\t} else lo = mid\n\t\t}\n\t\treturn found\n\t}\n\treturn { ...(best as FoundContrast), met: false }\n}\n\n/** Options for `onColor` and the function `createOnColor` returns. */\nexport interface OnColorOptions {\n\t/** The contrast the text needs. Default 4.5 for WCAG 2. */\n\ttarget?: number\n\t/** How much of the background's hue the dark text carries, as OKLCH chroma. Default 0.03. */\n\tchroma?: number\n\t/** Default `wcag2`. */\n\tcontrast?: ContrastOption\n}\n\n/**\n * The text color for `bg`, by the Radix rule: white, unless white's contrast\n * on `bg` is under `target`, then a near-black tinted with the hue of `bg`\n * (OKLCH lightness 0.2). When neither reaches `target`, whichever contrasts more.\n */\nexport function onColor(bg: ColorInput, options: OnColorOptions = {}): Color {\n\treturn onColorWith({ componentsIn, wcag2 }, toColor(bg), options)\n}\n\n/**\n * Builds {@link onColor} over the spaces given, plus `srgb`, `xyz-d65`,\n * `oklch` and `oklab`, which WCAG 2 and gamut mapping work in, so a bundle\n * contains only those. It takes a color object, not a string, so it does not\n * carry `parse`. A color in a space not given throws, naming that space.\n */\nexport function createOnColor(...spaces: Space[]): (bg: Color, options?: OnColorOptions) => Color {\n\tconst kit = contrastKit([srgb, xyzD65, oklch, oklab], spaces)\n\treturn (bg, options = {}) => onColorWith(kit, bg, options)\n}\n\nfunction onColorWith({ componentsIn, wcag2 }: ContrastKit, back: Color, options: OnColorOptions): Color {\n\tconst fn = pickFrom(wcag2, options.contrast ?? 'wcag2')\n\tconst target = options.target ?? 4.5\n\tconst white: Color = { colorSpace: 'srgb', components: [1, 1, 1] }\n\tconst whiteScore = Math.abs(fn(white, back))\n\tif (whiteScore >= target) return white\n\tconst hue = componentsIn(back, 'oklch')[2] as number\n\tconst dark = toGamutWith(componentsIn, { colorSpace: 'oklch', components: [0.2, options.chroma ?? 0.03, hue] }, {})\n\treturn Math.abs(fn(dark, back)) >= whiteScore ? dark : white\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Color, ColorSpaceId, Space, Vec3 } from "./types.js";
|
|
2
|
+
/** Converts bare components between two spaces, through the XYZ-D65 hub. */
|
|
3
|
+
export declare function convertComponents(from: Space, to: Space, c: Vec3): Vec3;
|
|
4
|
+
/**
|
|
5
|
+
* Builds `convert` over just the spaces given, so a bundle contains only those.
|
|
6
|
+
* `none` components become 0 when the space changes.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createConverter(...spaces: Space[]): (color: Color, to: ColorSpaceId) => Color;
|
|
9
|
+
//# sourceMappingURL=convert.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.d.ts","names":[],"sources":["../../src/color/convert.ts"],"mappings":";;wBAIgB,kBAAkB,MAAM,OAAO,IAAI,OAAO,GAAG,OAAO;;;;;wBAmCpD,mBAAmB,QAAQ,WAAW,OAAO,OAAO,IAAI,iBAAiB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { unknownName } from "./unknown-name.js";
|
|
2
|
+
// src/color/convert.ts
|
|
3
|
+
/** Converts bare components between two spaces, through the XYZ-D65 hub. */
|
|
4
|
+
function convertComponents(from, to, c) {
|
|
5
|
+
return from === to ? c : to.fromXyz(from.toXyz(c));
|
|
6
|
+
}
|
|
7
|
+
/** {@link ComponentsIn} over the spaces given. */
|
|
8
|
+
function componentsOver(spaces) {
|
|
9
|
+
const byId = new Map(spaces.map((s) => [s.id, s]));
|
|
10
|
+
const find = (id) => {
|
|
11
|
+
const space = byId.get(id);
|
|
12
|
+
if (!space) throw unknownName("color space", id, [...byId.keys()]);
|
|
13
|
+
return space;
|
|
14
|
+
};
|
|
15
|
+
return (color, to) => {
|
|
16
|
+
if (color.colorSpace === to) return [...color.components];
|
|
17
|
+
const from = find(color.colorSpace);
|
|
18
|
+
const c = color.components;
|
|
19
|
+
return find(to).fromXyz(from.toXyz([
|
|
20
|
+
c[0] ?? 0,
|
|
21
|
+
c[1] ?? 0,
|
|
22
|
+
c[2] ?? 0
|
|
23
|
+
]));
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/** `convert` over the spaces `componentsIn` knows: the color in `to`, keeping its alpha. */
|
|
27
|
+
function convertWith(componentsIn, color, to) {
|
|
28
|
+
const result = {
|
|
29
|
+
colorSpace: to,
|
|
30
|
+
components: componentsIn(color, to)
|
|
31
|
+
};
|
|
32
|
+
if (color.alpha !== void 0) result.alpha = color.alpha;
|
|
33
|
+
return result;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Builds `convert` over just the spaces given, so a bundle contains only those.
|
|
37
|
+
* `none` components become 0 when the space changes.
|
|
38
|
+
*/
|
|
39
|
+
function createConverter(...spaces) {
|
|
40
|
+
const componentsIn = componentsOver(spaces);
|
|
41
|
+
return (color, to) => convertWith(componentsIn, color, to);
|
|
42
|
+
}
|
|
43
|
+
export { componentsOver, convertComponents, convertWith, createConverter };
|
|
44
|
+
|
|
45
|
+
//# sourceMappingURL=convert.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"convert.js","names":[],"sources":["../../src/color/convert.ts"],"sourcesContent":["import type { Color, ColorSpaceId, Component, Space, Vec3 } from './types.js'\nimport { unknownName } from './unknown-name.js'\n\n/** Converts bare components between two spaces, through the XYZ-D65 hub. */\nexport function convertComponents(from: Space, to: Space, c: Vec3): Vec3 {\n\treturn from === to ? c : to.fromXyz(from.toXyz(c))\n}\n\n/** The components of a color in another space: a copy in the same space, else `none` becomes 0. */\nexport type ComponentsIn = (color: Color, to: ColorSpaceId) => Component[]\n\n/** {@link ComponentsIn} over the spaces given. */\nexport function componentsOver(spaces: Space[]): ComponentsIn {\n\tconst byId = new Map(spaces.map((s) => [s.id, s]))\n\tconst find = (id: ColorSpaceId): Space => {\n\t\tconst space = byId.get(id)\n\t\tif (!space) throw unknownName('color space', id, [...byId.keys()])\n\t\treturn space\n\t}\n\treturn (color, to) => {\n\t\tif (color.colorSpace === to) return [...color.components]\n\t\tconst from = find(color.colorSpace)\n\t\tconst c = color.components\n\t\t// Different ids, so different spaces: straight through the hub, `none` as 0.\n\t\treturn find(to).fromXyz(from.toXyz([c[0] ?? 0, c[1] ?? 0, c[2] ?? 0]))\n\t}\n}\n\n/** `convert` over the spaces `componentsIn` knows: the color in `to`, keeping its alpha. */\nexport function convertWith(componentsIn: ComponentsIn, color: Color, to: ColorSpaceId): Color {\n\tconst result: Color = { colorSpace: to, components: componentsIn(color, to) }\n\tif (color.alpha !== undefined) result.alpha = color.alpha\n\treturn result\n}\n\n/**\n * Builds `convert` over just the spaces given, so a bundle contains only those.\n * `none` components become 0 when the space changes.\n */\nexport function createConverter(...spaces: Space[]): (color: Color, to: ColorSpaceId) => Color {\n\tconst componentsIn = componentsOver(spaces)\n\treturn (color, to) => convertWith(componentsIn, color, to)\n}\n"],"mappings":";;;AAIA,SAAgB,kBAAkB,MAAa,IAAW,GAAe;CACxE,OAAO,SAAS,KAAK,IAAI,GAAG,QAAQ,KAAK,MAAM,CAAC,CAAC;AAClD;;AAMA,SAAgB,eAAe,QAA+B;CAC7D,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACjD,MAAM,QAAQ,OAA4B;EACzC,MAAM,QAAQ,KAAK,IAAI,EAAE;EACzB,IAAI,CAAC,OAAO,MAAM,YAAY,eAAe,IAAI,CAAC,GAAG,KAAK,KAAK,CAAC,CAAC;EACjE,OAAO;CACR;CACA,QAAQ,OAAO,OAAO;EACrB,IAAI,MAAM,eAAe,IAAI,OAAO,CAAC,GAAG,MAAM,UAAU;EACxD,MAAM,OAAO,KAAK,MAAM,UAAU;EAClC,MAAM,IAAI,MAAM;EAEhB,OAAO,KAAK,EAAE,CAAC,CAAC,QAAQ,KAAK,MAAM;GAAC,EAAE,MAAM;GAAG,EAAE,MAAM;GAAG,EAAE,MAAM;EAAC,CAAC,CAAC;CACtE;AACD;;AAGA,SAAgB,YAAY,cAA4B,OAAc,IAAyB;CAC9F,MAAM,SAAgB;EAAE,YAAY;EAAI,YAAY,aAAa,OAAO,EAAE;CAAE;CAC5E,IAAI,MAAM,UAAU,KAAA,GAAW,OAAO,QAAQ,MAAM;CACpD,OAAO;AACR;;;;;AAMA,SAAgB,gBAAgB,GAAG,QAA4D;CAC9F,MAAM,eAAe,eAAe,MAAM;CAC1C,QAAQ,OAAO,OAAO,YAAY,cAAc,OAAO,EAAE;AAC1D"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Space } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The CSS `display-p3-linear` space: Display P3 primaries without a transfer curve. Components are red, green and blue in 0 to 1.
|
|
4
|
+
* Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const displayP3Linear: Space;
|
|
7
|
+
//# sourceMappingURL=display-p3-linear.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-p3-linear.d.ts","names":["_$__types_js0.Space"],"sources":["../../src/color/display-p3-linear.ts"],"mappings":";;;;;qBAQa,iBAAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { p3ToXyz, xyzToP3 } from "./matrices.js";
|
|
2
|
+
import { rgbSpace } from "./rgb.js";
|
|
3
|
+
import { identity } from "./transfer.js";
|
|
4
|
+
// src/color/display-p3-linear.ts
|
|
5
|
+
/**
|
|
6
|
+
* The CSS `display-p3-linear` space: Display P3 primaries without a transfer curve. Components are red, green and blue in 0 to 1.
|
|
7
|
+
* Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
|
|
8
|
+
*/
|
|
9
|
+
const displayP3Linear = rgbSpace("display-p3-linear", identity, identity, p3ToXyz, xyzToP3);
|
|
10
|
+
export { displayP3Linear };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=display-p3-linear.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-p3-linear.js","names":[],"sources":["../../src/color/display-p3-linear.ts"],"sourcesContent":["import { p3ToXyz, xyzToP3 } from './matrices.js'\nimport { rgbSpace } from './rgb.js'\nimport { identity } from './transfer.js'\n\n/**\n * The CSS `display-p3-linear` space: Display P3 primaries without a transfer curve. Components are red, green and blue in 0 to 1.\n * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.\n */\nexport const displayP3Linear = rgbSpace('display-p3-linear', identity, identity, p3ToXyz, xyzToP3)\n"],"mappings":";;;;;;;;AAQA,MAAa,kBAAkB,SAAS,qBAAqB,UAAU,UAAU,SAAS,OAAO"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { Space } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The CSS `display-p3` space. Components are red, green and blue in 0 to 1, with the sRGB transfer curve.
|
|
4
|
+
* Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
|
|
5
|
+
*/
|
|
6
|
+
export declare const displayP3: Space;
|
|
7
|
+
//# sourceMappingURL=display-p3.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-p3.d.ts","names":["_$__types_js0.Space"],"sources":["../../src/color/display-p3.ts"],"mappings":";;;;;qBAQa,WAAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { p3ToXyz, xyzToP3 } from "./matrices.js";
|
|
2
|
+
import { rgbSpace } from "./rgb.js";
|
|
3
|
+
import { srgbToGamma, srgbToLinear } from "./transfer.js";
|
|
4
|
+
// src/color/display-p3.ts
|
|
5
|
+
/**
|
|
6
|
+
* The CSS `display-p3` space. Components are red, green and blue in 0 to 1, with the sRGB transfer curve.
|
|
7
|
+
* Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.
|
|
8
|
+
*/
|
|
9
|
+
const displayP3 = rgbSpace("display-p3", srgbToLinear, srgbToGamma, p3ToXyz, xyzToP3);
|
|
10
|
+
export { displayP3 };
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=display-p3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"display-p3.js","names":[],"sources":["../../src/color/display-p3.ts"],"sourcesContent":["import { p3ToXyz, xyzToP3 } from './matrices.js'\nimport { rgbSpace } from './rgb.js'\nimport { srgbToGamma, srgbToLinear } from './transfer.js'\n\n/**\n * The CSS `display-p3` space. Components are red, green and blue in 0 to 1, with the sRGB transfer curve.\n * Pass it to `createConverter` and to the narrow helpers such as `createMix` and `createToGamut`.\n */\nexport const displayP3 = rgbSpace('display-p3', srgbToLinear, srgbToGamma, p3ToXyz, xyzToP3)\n"],"mappings":";;;;;;;;AAQA,MAAa,YAAY,SAAS,cAAc,cAAc,aAAa,SAAS,OAAO"}
|