@clhaas/palette-kit 0.1.8 → 0.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/.codex/skills/color-pipeline-implementer/SKILL.md +23 -0
- package/.codex/skills/commit-message-crafter/SKILL.md +63 -0
- package/.codex/skills/commit-message-crafter/references/benchmarks.md +20 -0
- package/.codex/skills/contrast-solver-helper/SKILL.md +20 -0
- package/.codex/skills/exporters-builder/SKILL.md +20 -0
- package/.codex/skills/markdownlint-writer/SKILL.md +32 -0
- package/.codex/skills/phase-implementation-runbook/SKILL.md +92 -0
- package/.codex/skills/type-contract-auditor/SKILL.md +21 -0
- package/.github/skills/review-guide/SKILL.md +23 -0
- package/.github/skills/review-guide/references/review-guide-v0.3.md +629 -0
- package/.markdownlint.json +4 -0
- package/AGENTS.md +16 -0
- package/CHANGELOG.md +34 -0
- package/README.md +79 -169
- package/biome.json +43 -0
- package/dist/cli/args.d.ts +12 -0
- package/dist/cli/args.js +56 -0
- package/dist/cli/args.test.js +22 -0
- package/dist/cli/codegen/__snapshots__/tokens.test.js.snap +87 -0
- package/dist/cli/codegen/tokens.d.ts +12 -0
- package/dist/cli/codegen/tokens.js +139 -0
- package/dist/cli/codegen/tokens.test.d.ts +1 -0
- package/dist/cli/codegen/tokens.test.js +51 -0
- package/dist/cli/config.d.ts +40 -0
- package/dist/cli/config.js +34 -0
- package/dist/cli/validate.d.ts +2 -0
- package/dist/cli/validate.js +33 -0
- package/dist/cli/validate.test.d.ts +1 -0
- package/dist/cli/validate.test.js +40 -0
- package/dist/cli.js +138 -140
- package/dist/contrast/apca.d.ts +2 -2
- package/dist/contrast/apca.js +14 -4
- package/dist/contrast/apca.test.d.ts +1 -0
- package/dist/contrast/apca.test.js +16 -0
- package/dist/contrast/index.d.ts +4 -0
- package/dist/contrast/index.js +4 -0
- package/dist/contrast/scoring.d.ts +4 -0
- package/dist/contrast/scoring.js +31 -0
- package/dist/contrast/scoring.test.d.ts +1 -0
- package/dist/contrast/scoring.test.js +148 -0
- package/dist/contrast/solver.d.ts +13 -0
- package/dist/contrast/solver.js +170 -0
- package/dist/contrast/solver.test.d.ts +1 -0
- package/dist/contrast/solver.test.js +75 -0
- package/dist/contrast/types.d.ts +17 -0
- package/dist/contrast/types.js +1 -0
- package/dist/contrast/utils.d.ts +4 -0
- package/dist/contrast/utils.js +18 -0
- package/dist/contrast/wcag2.d.ts +3 -0
- package/dist/contrast/wcag2.js +19 -0
- package/dist/contrast/wcag2.test.d.ts +1 -0
- package/dist/contrast/wcag2.test.js +17 -0
- package/dist/core/createTheme.d.ts +35 -0
- package/dist/core/createTheme.js +24 -0
- package/dist/core/dx-helpers.test.d.ts +1 -0
- package/dist/core/dx-helpers.test.js +61 -0
- package/dist/core/index.d.ts +2 -0
- package/dist/core/index.js +2 -0
- package/dist/core/onSolid.test.d.ts +1 -0
- package/dist/core/onSolid.test.js +118 -0
- package/dist/core/qa.v1.test.d.ts +1 -0
- package/dist/core/qa.v1.test.js +112 -0
- package/dist/core/resolve.d.ts +3 -0
- package/dist/core/resolve.js +8 -0
- package/dist/core/resolve.test.d.ts +1 -0
- package/dist/core/resolve.test.js +89 -0
- package/dist/core/resolveMany.d.ts +8 -0
- package/dist/core/resolveMany.js +17 -0
- package/dist/core/tokenRegistry.d.ts +23 -0
- package/dist/core/tokenRegistry.js +83 -0
- package/dist/core/tokenRegistry.test.d.ts +1 -0
- package/dist/core/tokenRegistry.test.js +133 -0
- package/dist/engine/applyOperators.d.ts +3 -0
- package/dist/engine/applyOperators.js +23 -0
- package/dist/engine/context.d.ts +4 -0
- package/dist/engine/context.js +1 -0
- package/dist/engine/gamut.d.ts +13 -0
- package/dist/engine/gamut.js +101 -0
- package/dist/engine/gamut.test.d.ts +1 -0
- package/dist/engine/gamut.test.js +23 -0
- package/dist/engine/generateScale.d.ts +15 -0
- package/dist/engine/generateScale.js +29 -0
- package/dist/engine/generateScale.test.d.ts +1 -0
- package/dist/engine/generateScale.test.js +32 -0
- package/dist/engine/index.d.ts +8 -0
- package/dist/engine/index.js +4 -0
- package/dist/engine/normalize.d.ts +43 -0
- package/dist/engine/normalize.js +403 -0
- package/dist/engine/normalize.test.d.ts +1 -0
- package/dist/engine/normalize.test.js +136 -0
- package/dist/engine/onSolid.d.ts +3 -0
- package/dist/engine/onSolid.js +110 -0
- package/dist/engine/resolveBaseColor.d.ts +25 -0
- package/dist/engine/resolveBaseColor.js +127 -0
- package/dist/engine/resolveBaseColor.test.d.ts +1 -0
- package/dist/engine/resolveBaseColor.test.js +97 -0
- package/dist/export/__snapshots__/exportTheme.test.js.snap +74 -0
- package/dist/export/exportTheme.d.ts +47 -0
- package/dist/export/exportTheme.js +170 -0
- package/dist/export/exportTheme.test.d.ts +1 -0
- package/dist/export/exportTheme.test.js +118 -0
- package/dist/export/index.d.ts +1 -0
- package/dist/export/index.js +1 -0
- package/dist/export/serializeColor.d.ts +1 -0
- package/dist/export/serializeColor.js +1 -0
- package/dist/export/serializeColor.test.d.ts +1 -0
- package/dist/export/serializeColor.test.js +54 -0
- package/dist/export.d.ts +1 -0
- package/dist/export.js +1 -0
- package/dist/index.d.ts +3 -22
- package/dist/index.js +2 -18
- package/dist/operators/emphasis.d.ts +3 -0
- package/dist/operators/emphasis.js +113 -0
- package/dist/operators/emphasis.test.d.ts +1 -0
- package/dist/operators/emphasis.test.js +69 -0
- package/dist/operators/index.d.ts +3 -0
- package/dist/operators/index.js +2 -0
- package/dist/operators/state.d.ts +3 -0
- package/dist/operators/state.js +102 -0
- package/dist/operators/state.test.d.ts +1 -0
- package/dist/operators/state.test.js +48 -0
- package/dist/operators/types.d.ts +13 -0
- package/dist/operators/types.js +1 -0
- package/dist/operators/utils.d.ts +16 -0
- package/dist/operators/utils.js +23 -0
- package/dist/presets/curves.d.ts +28 -0
- package/dist/presets/curves.js +145 -0
- package/dist/presets/index.d.ts +2 -0
- package/dist/presets/index.js +1 -0
- package/dist/presets/tokens/index.d.ts +3 -0
- package/dist/presets/tokens/index.js +3 -0
- package/dist/presets/tokens/minimal-ui.d.ts +6 -0
- package/dist/presets/tokens/minimal-ui.js +53 -0
- package/dist/presets/tokens/modern-ui.d.ts +5 -0
- package/dist/presets/tokens/modern-ui.js +83 -0
- package/dist/presets/tokens/presets.test.d.ts +1 -0
- package/dist/presets/tokens/presets.test.js +31 -0
- package/dist/presets/tokens/radixLike-ui.d.ts +6 -0
- package/dist/presets/tokens/radixLike-ui.js +77 -0
- package/dist/serialize/index.d.ts +1 -0
- package/dist/serialize/index.js +1 -0
- package/dist/serialize/normalizeOutput.d.ts +6 -0
- package/dist/serialize/normalizeOutput.js +45 -0
- package/dist/serialize/serializeColor.d.ts +21 -0
- package/dist/serialize/serializeColor.js +178 -0
- package/dist/serialize/serializeResolved.test.d.ts +1 -0
- package/dist/serialize/serializeResolved.test.js +45 -0
- package/dist/serialize.d.ts +1 -0
- package/dist/serialize.js +1 -0
- package/dist/types/index.d.ts +187 -0
- package/dist/types/index.js +1 -0
- package/dist/utils/clamp.d.ts +1 -0
- package/dist/utils/clamp.js +1 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/lerp.d.ts +1 -0
- package/dist/utils/lerp.js +1 -0
- package/dist/utils/parseColor.d.ts +6 -0
- package/dist/utils/parseColor.js +67 -0
- package/dist/utils/parseColor.test.d.ts +1 -0
- package/dist/utils/parseColor.test.js +51 -0
- package/dist/utils/smoothstep.d.ts +1 -0
- package/dist/utils/smoothstep.js +5 -0
- package/package.json +19 -12
- package/planning/phase-10-review.md +550 -0
- package/planning/phase-7-review.md +411 -0
- package/planning/phase-8-review.md +669 -0
- package/planning/phase-9-review.md +564 -0
- package/planning/roadmap-v0.3.md +284 -0
- package/planning/spec-serializer-v0.3.md +324 -0
- package/planning/spec-v0.3.md +305 -0
- package/src/cli/args.test.ts +28 -0
- package/src/cli/args.ts +66 -0
- package/src/cli/codegen/__snapshots__/tokens.test.ts.snap +87 -0
- package/src/cli/codegen/tokens.test.ts +61 -0
- package/src/cli/codegen/tokens.ts +191 -0
- package/src/cli/config.ts +71 -0
- package/src/cli/validate.test.ts +49 -0
- package/src/cli/validate.ts +38 -0
- package/src/cli.ts +183 -0
- package/src/contrast/apca.test.ts +20 -0
- package/src/contrast/apca.ts +26 -0
- package/src/contrast/index.ts +4 -0
- package/src/contrast/scoring.test.ts +188 -0
- package/src/contrast/scoring.ts +48 -0
- package/src/contrast/solver.test.ts +147 -0
- package/src/contrast/solver.ts +235 -0
- package/src/contrast/types.ts +20 -0
- package/src/contrast/utils.ts +28 -0
- package/src/contrast/wcag2.test.ts +21 -0
- package/src/contrast/wcag2.ts +24 -0
- package/src/core/createTheme.ts +78 -0
- package/src/core/dx-helpers.test.ts +82 -0
- package/src/core/index.ts +7 -0
- package/src/core/onSolid.test.ts +146 -0
- package/src/core/qa.v1.test.ts +149 -0
- package/src/core/resolve.test.ts +99 -0
- package/src/core/resolve.ts +11 -0
- package/src/core/resolveMany.ts +22 -0
- package/src/core/tokenRegistry.test.ts +153 -0
- package/src/core/tokenRegistry.ts +114 -0
- package/src/engine/applyOperators.ts +32 -0
- package/src/engine/context.ts +8 -0
- package/src/engine/gamut.test.ts +30 -0
- package/src/engine/gamut.ts +144 -0
- package/src/engine/generateScale.test.ts +46 -0
- package/src/engine/generateScale.ts +48 -0
- package/src/engine/index.ts +8 -0
- package/src/engine/normalize.test.ts +222 -0
- package/src/engine/normalize.ts +550 -0
- package/src/engine/onSolid.ts +178 -0
- package/src/engine/resolveBaseColor.test.ts +117 -0
- package/src/engine/resolveBaseColor.ts +203 -0
- package/src/export/__snapshots__/exportTheme.test.ts.snap +74 -0
- package/src/export/exportTheme.test.ts +144 -0
- package/src/export/exportTheme.ts +251 -0
- package/src/export/index.ts +1 -0
- package/src/export/serializeColor.test.ts +73 -0
- package/src/export/serializeColor.ts +1 -0
- package/src/export.ts +1 -0
- package/src/index.ts +3 -0
- package/src/operators/emphasis.test.ts +85 -0
- package/src/operators/emphasis.ts +132 -0
- package/src/operators/index.ts +3 -0
- package/src/operators/state.test.ts +66 -0
- package/src/operators/state.ts +122 -0
- package/src/operators/types.ts +14 -0
- package/src/operators/utils.ts +44 -0
- package/src/presets/curves.ts +168 -0
- package/src/presets/index.ts +2 -0
- package/src/presets/tokens/index.ts +3 -0
- package/src/presets/tokens/minimal-ui.ts +55 -0
- package/src/presets/tokens/modern-ui.ts +85 -0
- package/src/presets/tokens/presets.test.ts +46 -0
- package/src/presets/tokens/radixLike-ui.ts +79 -0
- package/src/serialize/index.ts +1 -0
- package/src/serialize/normalizeOutput.ts +63 -0
- package/src/serialize/serializeColor.ts +260 -0
- package/src/serialize/serializeResolved.test.ts +57 -0
- package/src/serialize.ts +1 -0
- package/src/types/index.ts +207 -0
- package/src/utils/clamp.ts +2 -0
- package/src/utils/index.ts +1 -0
- package/src/utils/lerp.ts +1 -0
- package/src/utils/parseColor.test.ts +66 -0
- package/src/utils/parseColor.ts +87 -0
- package/src/utils/smoothstep.ts +6 -0
- package/tsconfig.build.json +11 -0
- package/tsconfig.json +15 -0
- package/dist/alpha/generateAlphaScale.d.ts +0 -5
- package/dist/alpha/generateAlphaScale.js +0 -34
- package/dist/contrast/onSolid.d.ts +0 -6
- package/dist/contrast/onSolid.js +0 -28
- package/dist/contrast/solveText.d.ts +0 -2
- package/dist/contrast/solveText.js +0 -31
- package/dist/createTheme.d.ts +0 -38
- package/dist/createTheme.js +0 -148
- package/dist/data/radixSeeds.d.ts +0 -3
- package/dist/data/radixSeeds.js +0 -34
- package/dist/diagnostics/analyzeScale.d.ts +0 -2
- package/dist/diagnostics/analyzeScale.js +0 -7
- package/dist/diagnostics/analyzeTheme.d.ts +0 -2
- package/dist/diagnostics/analyzeTheme.js +0 -35
- package/dist/diagnostics/warnings.d.ts +0 -2
- package/dist/diagnostics/warnings.js +0 -20
- package/dist/engine/curves.d.ts +0 -9
- package/dist/engine/curves.js +0 -48
- package/dist/engine/oklch.d.ts +0 -8
- package/dist/engine/oklch.js +0 -40
- package/dist/engine/templates.d.ts +0 -14
- package/dist/engine/templates.js +0 -45
- package/dist/exporters/selectColorMode.d.ts +0 -2
- package/dist/exporters/selectColorMode.js +0 -19
- package/dist/exporters/toCssVars.d.ts +0 -13
- package/dist/exporters/toCssVars.js +0 -108
- package/dist/exporters/toJson.d.ts +0 -3
- package/dist/exporters/toJson.js +0 -25
- package/dist/exporters/toReactNative.d.ts +0 -54
- package/dist/exporters/toReactNative.js +0 -33
- package/dist/exporters/toTailwind.d.ts +0 -17
- package/dist/exporters/toTailwind.js +0 -111
- package/dist/exporters/toTs.d.ts +0 -3
- package/dist/exporters/toTs.js +0 -43
- package/dist/generateScale.d.ts +0 -48
- package/dist/generateScale.js +0 -274
- package/dist/overlays/generateOverlayScale.d.ts +0 -2
- package/dist/overlays/generateOverlayScale.js +0 -34
- package/dist/text/generateTextScale.d.ts +0 -8
- package/dist/text/generateTextScale.js +0 -18
- package/dist/text/resolveOnBgText.d.ts +0 -9
- package/dist/text/resolveOnBgText.js +0 -28
- package/dist/tokens/presetRadixLikeUi.d.ts +0 -5
- package/dist/tokens/presetRadixLikeUi.js +0 -55
- package/dist/types.d.ts +0 -69
- /package/dist/{types.js → cli/args.test.d.ts} +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { TokenRegistry } from "../../types/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Radix-like preset aligned with common component library semantics.
|
|
5
|
+
* Expands coverage for subtle/surface/solid layers and semantic text.
|
|
6
|
+
*/
|
|
7
|
+
export const radixLikeUiTokens: TokenRegistry = {
|
|
8
|
+
tokens: {
|
|
9
|
+
"bg.app": {
|
|
10
|
+
name: "bg.app",
|
|
11
|
+
description: "Application background for the overall canvas.",
|
|
12
|
+
category: "background",
|
|
13
|
+
query: { role: "bg.app", usage: "bg", surface: "app" },
|
|
14
|
+
},
|
|
15
|
+
"bg.surface": {
|
|
16
|
+
name: "bg.surface",
|
|
17
|
+
description: "Base surface background for cards and panels.",
|
|
18
|
+
category: "background",
|
|
19
|
+
query: { role: "bg.surface", usage: "bg", surface: "surface" },
|
|
20
|
+
},
|
|
21
|
+
"bg.subtle": {
|
|
22
|
+
name: "bg.subtle",
|
|
23
|
+
description: "Subtle background for secondary sections.",
|
|
24
|
+
category: "background",
|
|
25
|
+
query: { role: "bg.subtle", usage: "bg", surface: "subtle" },
|
|
26
|
+
},
|
|
27
|
+
"bg.solid": {
|
|
28
|
+
name: "bg.solid",
|
|
29
|
+
description: "Solid background for high-emphasis elements.",
|
|
30
|
+
category: "background",
|
|
31
|
+
query: { role: "bg.solid", usage: "bg", surface: "solid" },
|
|
32
|
+
states: { hover: true, active: true },
|
|
33
|
+
},
|
|
34
|
+
"text.primary": {
|
|
35
|
+
name: "text.primary",
|
|
36
|
+
description: "Primary text on standard surfaces.",
|
|
37
|
+
category: "text",
|
|
38
|
+
query: { role: "text.primary", usage: "text", surface: "surface" },
|
|
39
|
+
},
|
|
40
|
+
"text.secondary": {
|
|
41
|
+
name: "text.secondary",
|
|
42
|
+
description: "Secondary text for supporting content.",
|
|
43
|
+
category: "text",
|
|
44
|
+
query: { role: "text.secondary", usage: "text", surface: "surface", emphasis: "muted" },
|
|
45
|
+
},
|
|
46
|
+
"text.inverse": {
|
|
47
|
+
name: "text.inverse",
|
|
48
|
+
description: "Inverse text for solid backgrounds.",
|
|
49
|
+
category: "text",
|
|
50
|
+
query: { role: "text.inverse", usage: "text", surface: "solid", emphasis: "inverted" },
|
|
51
|
+
},
|
|
52
|
+
"border.default": {
|
|
53
|
+
name: "border.default",
|
|
54
|
+
description: "Default border for layout and surfaces.",
|
|
55
|
+
category: "border",
|
|
56
|
+
query: { role: "border.default", usage: "border", surface: "surface" },
|
|
57
|
+
},
|
|
58
|
+
"border.subtle": {
|
|
59
|
+
name: "border.subtle",
|
|
60
|
+
description: "Subtle border for separators.",
|
|
61
|
+
category: "border",
|
|
62
|
+
query: { role: "border.subtle", usage: "border", surface: "subtle" },
|
|
63
|
+
},
|
|
64
|
+
"icon.default": {
|
|
65
|
+
name: "icon.default",
|
|
66
|
+
description: "Default icon color on surfaces.",
|
|
67
|
+
category: "icon",
|
|
68
|
+
query: { role: "icon.default", usage: "icon", surface: "surface" },
|
|
69
|
+
states: { hover: true },
|
|
70
|
+
},
|
|
71
|
+
"ring.default": {
|
|
72
|
+
name: "ring.default",
|
|
73
|
+
description: "Base ring color (derive focus via state operator).",
|
|
74
|
+
category: "ring",
|
|
75
|
+
query: { role: "ring.default", usage: "ring", surface: "surface", emphasis: "strong" },
|
|
76
|
+
states: { focus: true },
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { serializeColor, serializeResolved } from "./serializeColor.js";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { OutputOptions } from "../types/index.js";
|
|
2
|
+
|
|
3
|
+
const gamutMappings: NonNullable<OutputOptions["gamutMapping"]>[] = [
|
|
4
|
+
"clip",
|
|
5
|
+
"compressChroma",
|
|
6
|
+
"preferP3ThenCompress",
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
const srgbFormats: NonNullable<OutputOptions["srgbFormat"]>[] = ["hex", "rgb", "rgba"];
|
|
10
|
+
|
|
11
|
+
const formatString = (value: string | undefined) => (value ? value.trim() : undefined);
|
|
12
|
+
|
|
13
|
+
const assertOneOf = <T extends string>(value: string, options: readonly T[], label: string): T => {
|
|
14
|
+
if (!options.includes(value as T)) {
|
|
15
|
+
throw new Error(`${label} must be one of: ${options.join(", ")} (received "${value}")`);
|
|
16
|
+
}
|
|
17
|
+
return value as T;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type NormalizedOutput = Required<
|
|
21
|
+
Pick<OutputOptions, "preferSpace" | "includeSpaces" | "gamutMapping" | "strict" | "srgbFormat">
|
|
22
|
+
> & {
|
|
23
|
+
precision: Required<NonNullable<OutputOptions["precision"]>>;
|
|
24
|
+
includeMeta: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const normalizeOutput = (output?: OutputOptions): NormalizedOutput => {
|
|
28
|
+
const preferSpaceValue = formatString(output?.preferSpace);
|
|
29
|
+
const gamutMappingValue = formatString(output?.gamutMapping);
|
|
30
|
+
const srgbFormatValue = formatString(output?.srgbFormat);
|
|
31
|
+
|
|
32
|
+
const preferSpace = preferSpaceValue
|
|
33
|
+
? assertOneOf(preferSpaceValue, ["oklch", "srgb", "p3"], "output preferSpace")
|
|
34
|
+
: "oklch";
|
|
35
|
+
|
|
36
|
+
const gamutMapping = gamutMappingValue
|
|
37
|
+
? assertOneOf(gamutMappingValue, gamutMappings, "output gamutMapping")
|
|
38
|
+
: "preferP3ThenCompress";
|
|
39
|
+
|
|
40
|
+
const srgbFormat = srgbFormatValue
|
|
41
|
+
? assertOneOf(srgbFormatValue, srgbFormats, "output srgbFormat")
|
|
42
|
+
: "hex";
|
|
43
|
+
|
|
44
|
+
if (output?.strict !== undefined && typeof output.strict !== "boolean") {
|
|
45
|
+
throw new Error("Output strict must be a boolean");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
preferSpace,
|
|
50
|
+
includeSpaces: output?.includeSpaces ?? [],
|
|
51
|
+
gamutMapping,
|
|
52
|
+
precision: {
|
|
53
|
+
l: 1,
|
|
54
|
+
c: 3,
|
|
55
|
+
h: 1,
|
|
56
|
+
alpha: 2,
|
|
57
|
+
...output?.precision,
|
|
58
|
+
},
|
|
59
|
+
strict: output?.strict ?? false,
|
|
60
|
+
includeMeta: output?.includeMeta ?? false,
|
|
61
|
+
srgbFormat,
|
|
62
|
+
};
|
|
63
|
+
};
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
import { formatHex, formatHex8 } from "culori";
|
|
2
|
+
import { isInGamut, mapToGamut, toGamutRgb } from "../engine/gamut.js";
|
|
3
|
+
import type { OkLchColor } from "../engine/generateScale.js";
|
|
4
|
+
import type { BaseResolvedColor } from "../engine/resolveBaseColor.js";
|
|
5
|
+
import type { ColorMeta, OutputOptions, ResolvedColor } from "../types/index.js";
|
|
6
|
+
import { clamp } from "../utils/clamp.js";
|
|
7
|
+
import { type NormalizedOutput, normalizeOutput } from "./normalizeOutput.js";
|
|
8
|
+
|
|
9
|
+
type SerializedColorJson = ResolvedColor;
|
|
10
|
+
|
|
11
|
+
type SerializationDiagnostics = {
|
|
12
|
+
spaceUsed: NonNullable<OutputOptions["preferSpace"]>;
|
|
13
|
+
clipped?: boolean;
|
|
14
|
+
compressed?: boolean;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const roundTo = (value: number, digits: number) => {
|
|
18
|
+
const factor = 10 ** digits;
|
|
19
|
+
return Math.round(value * factor) / factor;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const formatNumber = (value: number, digits: number) => {
|
|
23
|
+
const rounded = roundTo(value, digits);
|
|
24
|
+
if (digits === 0) {
|
|
25
|
+
return String(Math.round(rounded));
|
|
26
|
+
}
|
|
27
|
+
const fixed = rounded.toFixed(digits);
|
|
28
|
+
return fixed.replace(/\.?0+$/, "");
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const normalizeHue = (hue: number) => ((hue % 360) + 360) % 360;
|
|
32
|
+
|
|
33
|
+
const formatOklch = (color: OkLchColor, precision: NormalizedOutput["precision"]) => {
|
|
34
|
+
const alpha = clamp(color.alpha ?? 1, 0, 1);
|
|
35
|
+
const hasAlpha = alpha < 1;
|
|
36
|
+
const l = formatNumber(clamp(color.l, 0, 100), precision.l);
|
|
37
|
+
const c = formatNumber(Math.max(0, color.c), precision.c);
|
|
38
|
+
const h = formatNumber(normalizeHue(color.h), precision.h);
|
|
39
|
+
const alphaPart = hasAlpha ? ` / ${formatNumber(alpha, precision.alpha)}` : "";
|
|
40
|
+
return `oklch(${l}% ${c} ${h}${alphaPart})`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const formatDisplayP3 = (
|
|
44
|
+
rgb: { r: number; g: number; b: number },
|
|
45
|
+
alpha: number,
|
|
46
|
+
precision: NormalizedOutput["precision"],
|
|
47
|
+
) => {
|
|
48
|
+
const r = formatNumber(clamp(rgb.r, 0, 1), precision.c);
|
|
49
|
+
const g = formatNumber(clamp(rgb.g, 0, 1), precision.c);
|
|
50
|
+
const b = formatNumber(clamp(rgb.b, 0, 1), precision.c);
|
|
51
|
+
const alphaPart = alpha < 1 ? ` / ${formatNumber(alpha, precision.alpha)}` : "";
|
|
52
|
+
return `color(display-p3 ${r} ${g} ${b}${alphaPart})`;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const formatSrgbHex = (rgb: { r: number; g: number; b: number }, alpha: number) => {
|
|
56
|
+
const rgbColor = {
|
|
57
|
+
mode: "rgb" as const,
|
|
58
|
+
r: clamp(rgb.r, 0, 1),
|
|
59
|
+
g: clamp(rgb.g, 0, 1),
|
|
60
|
+
b: clamp(rgb.b, 0, 1),
|
|
61
|
+
alpha,
|
|
62
|
+
};
|
|
63
|
+
return alpha < 1 ? formatHex8(rgbColor) : formatHex(rgbColor);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const formatSrgbFunction = (
|
|
67
|
+
rgb: { r: number; g: number; b: number },
|
|
68
|
+
alpha: number,
|
|
69
|
+
precision: NormalizedOutput["precision"],
|
|
70
|
+
forceAlpha: boolean,
|
|
71
|
+
) => {
|
|
72
|
+
const toChannel = (value: number) => String(Math.round(clamp(value, 0, 1) * 255));
|
|
73
|
+
const r = toChannel(rgb.r);
|
|
74
|
+
const g = toChannel(rgb.g);
|
|
75
|
+
const b = toChannel(rgb.b);
|
|
76
|
+
|
|
77
|
+
if (forceAlpha || alpha < 1) {
|
|
78
|
+
return `rgba(${r} ${g} ${b} / ${formatNumber(alpha, precision.alpha)})`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return `rgb(${r} ${g} ${b})`;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const formatSrgb = (
|
|
85
|
+
rgb: { r: number; g: number; b: number },
|
|
86
|
+
alpha: number,
|
|
87
|
+
output: NormalizedOutput,
|
|
88
|
+
) => {
|
|
89
|
+
switch (output.srgbFormat) {
|
|
90
|
+
case "rgb":
|
|
91
|
+
return formatSrgbFunction(rgb, alpha, output.precision, false);
|
|
92
|
+
case "rgba":
|
|
93
|
+
return formatSrgbFunction(rgb, alpha, output.precision, true);
|
|
94
|
+
default:
|
|
95
|
+
return formatSrgbHex(rgb, alpha);
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
const buildDiagnostics = (
|
|
100
|
+
spaceUsed: SerializationDiagnostics["spaceUsed"],
|
|
101
|
+
clipped?: boolean,
|
|
102
|
+
compressed?: boolean,
|
|
103
|
+
): SerializationDiagnostics => ({
|
|
104
|
+
spaceUsed,
|
|
105
|
+
...(clipped ? { clipped } : {}),
|
|
106
|
+
...(compressed ? { compressed } : {}),
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const buildMeta = (
|
|
110
|
+
meta: ColorMeta | undefined,
|
|
111
|
+
output: NormalizedOutput,
|
|
112
|
+
diagnostics: SerializationDiagnostics,
|
|
113
|
+
): ColorMeta | undefined =>
|
|
114
|
+
output.includeMeta
|
|
115
|
+
? {
|
|
116
|
+
...meta,
|
|
117
|
+
spaceUsed: diagnostics.spaceUsed,
|
|
118
|
+
gamutMapping: output.gamutMapping,
|
|
119
|
+
...(diagnostics.clipped ? { clipped: diagnostics.clipped } : {}),
|
|
120
|
+
...(diagnostics.compressed ? { compressed: diagnostics.compressed } : {}),
|
|
121
|
+
}
|
|
122
|
+
: undefined;
|
|
123
|
+
|
|
124
|
+
const resolveAlpha = (baseAlpha: number, mapped?: OkLchColor) =>
|
|
125
|
+
clamp(mapped?.alpha ?? baseAlpha, 0, 1);
|
|
126
|
+
|
|
127
|
+
const requirePreferredSpace = (
|
|
128
|
+
prefer: NormalizedOutput["preferSpace"],
|
|
129
|
+
strict: boolean,
|
|
130
|
+
value: string | undefined,
|
|
131
|
+
fallback: string,
|
|
132
|
+
) => {
|
|
133
|
+
if (!strict) return value ?? fallback;
|
|
134
|
+
// In strict mode, do not silently fall back for explicit non-oklch preferences.
|
|
135
|
+
if (prefer !== "oklch" && !value) {
|
|
136
|
+
throw new Error(`Unable to serialize preferred space: ${prefer}`);
|
|
137
|
+
}
|
|
138
|
+
return value ?? fallback;
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const resolveUsedSpace = (
|
|
142
|
+
prefer: NormalizedOutput["preferSpace"],
|
|
143
|
+
preferredValue: string | undefined,
|
|
144
|
+
): NormalizedOutput["preferSpace"] => {
|
|
145
|
+
if (prefer === "oklch") {
|
|
146
|
+
return "oklch";
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return preferredValue ? prefer : "oklch";
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Serialize raw OKLCH channels into CSS-ready strings for the requested spaces.
|
|
154
|
+
*
|
|
155
|
+
* @param color - OKLCH channels resolved by the core resolver.
|
|
156
|
+
* @param output - Serialization options for precision, gamut mapping, and output spaces.
|
|
157
|
+
* @param meta - Optional metadata to include when `includeMeta` is enabled.
|
|
158
|
+
*/
|
|
159
|
+
export const serializeColor = (
|
|
160
|
+
color: OkLchColor,
|
|
161
|
+
output?: OutputOptions,
|
|
162
|
+
meta?: ColorMeta,
|
|
163
|
+
): ResolvedColor => {
|
|
164
|
+
const normalized = normalizeOutput(output);
|
|
165
|
+
const alpha = clamp(color.alpha ?? 1, 0, 1);
|
|
166
|
+
const spaces = new Set([normalized.preferSpace, ...normalized.includeSpaces]);
|
|
167
|
+
|
|
168
|
+
const oklchText = formatOklch(color, normalized.precision);
|
|
169
|
+
|
|
170
|
+
const needsSrgb = spaces.has("srgb") || normalized.preferSpace === "srgb";
|
|
171
|
+
const srgbInGamut = needsSrgb ? isInGamut(color, "srgb") : false;
|
|
172
|
+
const srgbColor = needsSrgb
|
|
173
|
+
? mapToGamut(color, "srgb", normalized.gamutMapping, normalized.strict)
|
|
174
|
+
: undefined;
|
|
175
|
+
const srgbRgb = srgbColor ? toGamutRgb(srgbColor, "srgb") : null;
|
|
176
|
+
const srgbAlpha = resolveAlpha(alpha, srgbColor);
|
|
177
|
+
const srgbText = srgbRgb ? formatSrgb(srgbRgb, srgbAlpha, normalized) : undefined;
|
|
178
|
+
const srgbClipped = needsSrgb && normalized.gamutMapping === "clip" && !srgbInGamut;
|
|
179
|
+
const srgbCompressed =
|
|
180
|
+
needsSrgb && normalized.gamutMapping !== "clip" && !srgbInGamut ? true : undefined;
|
|
181
|
+
|
|
182
|
+
const needsP3 = spaces.has("p3") || normalized.preferSpace === "p3";
|
|
183
|
+
const p3InGamut = needsP3 ? isInGamut(color, "p3") : false;
|
|
184
|
+
const p3Color = needsP3
|
|
185
|
+
? normalized.gamutMapping === "preferP3ThenCompress" && p3InGamut
|
|
186
|
+
? color
|
|
187
|
+
: mapToGamut(color, "p3", normalized.gamutMapping, normalized.strict)
|
|
188
|
+
: undefined;
|
|
189
|
+
const p3Rgb = p3Color ? toGamutRgb(p3Color, "p3") : null;
|
|
190
|
+
const p3Alpha = resolveAlpha(alpha, p3Color);
|
|
191
|
+
const p3Text = p3Rgb ? formatDisplayP3(p3Rgb, p3Alpha, normalized.precision) : undefined;
|
|
192
|
+
const p3Clipped = needsP3 && normalized.gamutMapping === "clip" && !p3InGamut;
|
|
193
|
+
const p3Compressed =
|
|
194
|
+
needsP3 && normalized.gamutMapping !== "clip" && !p3InGamut ? true : undefined;
|
|
195
|
+
|
|
196
|
+
const preferredValue =
|
|
197
|
+
normalized.preferSpace === "srgb"
|
|
198
|
+
? srgbText
|
|
199
|
+
: normalized.preferSpace === "p3"
|
|
200
|
+
? p3Text
|
|
201
|
+
: oklchText;
|
|
202
|
+
|
|
203
|
+
const value = (() => {
|
|
204
|
+
if (normalized.preferSpace === "srgb") {
|
|
205
|
+
return requirePreferredSpace("srgb", normalized.strict, srgbText, oklchText);
|
|
206
|
+
}
|
|
207
|
+
if (normalized.preferSpace === "p3") {
|
|
208
|
+
return requirePreferredSpace("p3", normalized.strict, p3Text, oklchText);
|
|
209
|
+
}
|
|
210
|
+
return oklchText;
|
|
211
|
+
})();
|
|
212
|
+
|
|
213
|
+
const spaceUsed = resolveUsedSpace(normalized.preferSpace, preferredValue);
|
|
214
|
+
const diagnostics = (() => {
|
|
215
|
+
if (spaceUsed === "srgb") {
|
|
216
|
+
return buildDiagnostics(spaceUsed, srgbClipped, srgbCompressed);
|
|
217
|
+
}
|
|
218
|
+
if (spaceUsed === "p3") {
|
|
219
|
+
return buildDiagnostics(spaceUsed, p3Clipped, p3Compressed);
|
|
220
|
+
}
|
|
221
|
+
return buildDiagnostics(spaceUsed);
|
|
222
|
+
})();
|
|
223
|
+
|
|
224
|
+
return {
|
|
225
|
+
value,
|
|
226
|
+
srgb: spaces.has("srgb") ? srgbText : undefined,
|
|
227
|
+
p3: spaces.has("p3") ? p3Text : undefined,
|
|
228
|
+
oklch: spaces.has("oklch") ? oklchText : undefined,
|
|
229
|
+
alpha,
|
|
230
|
+
meta: buildMeta(meta, normalized, diagnostics),
|
|
231
|
+
};
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Serialize a resolver output while preserving resolver metadata.
|
|
236
|
+
*
|
|
237
|
+
* @param color - Result of the core resolver.
|
|
238
|
+
* @param output - Serialization options for precision, gamut mapping, and output spaces.
|
|
239
|
+
*/
|
|
240
|
+
export const serializeResolved = (
|
|
241
|
+
color: BaseResolvedColor,
|
|
242
|
+
output?: OutputOptions,
|
|
243
|
+
): ResolvedColor => {
|
|
244
|
+
const meta: ColorMeta = {
|
|
245
|
+
step: color.step,
|
|
246
|
+
variantUsed: color.variantUsed,
|
|
247
|
+
seedUsed: color.seedUsed,
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
return serializeColor(color.oklch, output, meta);
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
export const serializeColorJson = (
|
|
254
|
+
color: OkLchColor,
|
|
255
|
+
output?: OutputOptions,
|
|
256
|
+
meta?: ColorMeta,
|
|
257
|
+
): SerializedColorJson => {
|
|
258
|
+
// JSON export is string-based and mirrors CSS serialization intentionally.
|
|
259
|
+
return serializeColor(color, output, meta);
|
|
260
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createTheme } from "../core/createTheme.js";
|
|
4
|
+
import { serializeColor, serializeResolved } from "./serializeColor.js";
|
|
5
|
+
|
|
6
|
+
describe("serializeResolved", () => {
|
|
7
|
+
it("includes resolver metadata when includeMeta is true", () => {
|
|
8
|
+
const resolved = {
|
|
9
|
+
oklch: { l: 60, c: 0.2, h: 40, alpha: 0.5 },
|
|
10
|
+
step: 7,
|
|
11
|
+
variantUsed: "accent",
|
|
12
|
+
seedUsed: "#123456",
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const result = serializeResolved(resolved, { includeMeta: true, preferSpace: "oklch" });
|
|
16
|
+
|
|
17
|
+
expect(result.meta?.step).toBe(7);
|
|
18
|
+
expect(result.meta?.variantUsed).toBe("accent");
|
|
19
|
+
expect(result.meta?.seedUsed).toBe("#123456");
|
|
20
|
+
expect(result.meta?.spaceUsed).toBe("oklch");
|
|
21
|
+
expect(result.meta?.gamutMapping).toBe("preferP3ThenCompress");
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
describe("serializeColor", () => {
|
|
26
|
+
it("uses rgba output when explicitly requested", () => {
|
|
27
|
+
const color = { l: 60, c: 0.2, h: 40, alpha: 1 };
|
|
28
|
+
const result = serializeColor(color, { preferSpace: "srgb", srgbFormat: "rgba" });
|
|
29
|
+
|
|
30
|
+
expect(result.value.startsWith("rgba(")).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("theme.serialize", () => {
|
|
35
|
+
it("serializes a resolved query using the theme", () => {
|
|
36
|
+
const theme = createTheme({
|
|
37
|
+
seeds: {
|
|
38
|
+
light: { neutral: "#8B8D98", accent: "#3D63DD" },
|
|
39
|
+
dark: { neutral: "#8B8D98", accent: "#3D63DD" },
|
|
40
|
+
},
|
|
41
|
+
preset: "modern",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const result = theme.serialize(
|
|
45
|
+
{
|
|
46
|
+
role: "bg.solid",
|
|
47
|
+
usage: "bg",
|
|
48
|
+
surface: "solid",
|
|
49
|
+
context: "light",
|
|
50
|
+
},
|
|
51
|
+
{ preferSpace: "oklch" },
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(result.value.startsWith("oklch(")).toBe(true);
|
|
55
|
+
expect(result.alpha).toBeGreaterThanOrEqual(0);
|
|
56
|
+
});
|
|
57
|
+
});
|
package/src/serialize.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./serialize/index.js";
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
export type CssColorString = string;
|
|
2
|
+
|
|
3
|
+
export type ColorSpace = "srgb" | "p3" | "oklch";
|
|
4
|
+
|
|
5
|
+
export type ColorContext = "light" | "dark" | "highContrast" | "dimmed";
|
|
6
|
+
|
|
7
|
+
export type SurfaceIntent =
|
|
8
|
+
| "app"
|
|
9
|
+
| "surface"
|
|
10
|
+
| "subtle"
|
|
11
|
+
| "solid"
|
|
12
|
+
| "overlay"
|
|
13
|
+
| "data"
|
|
14
|
+
| "transparent";
|
|
15
|
+
|
|
16
|
+
export type ColorState = "default" | "hover" | "active" | "selected" | "focus" | "disabled";
|
|
17
|
+
|
|
18
|
+
export type ColorEmphasis = "muted" | "subtle" | "default" | "strong" | "inverted";
|
|
19
|
+
|
|
20
|
+
export type SemanticVariant =
|
|
21
|
+
| "neutral"
|
|
22
|
+
| "accent"
|
|
23
|
+
| "success"
|
|
24
|
+
| "warning"
|
|
25
|
+
| "danger"
|
|
26
|
+
| "info"
|
|
27
|
+
| "highlight"
|
|
28
|
+
| "premium"
|
|
29
|
+
| `category:${string}`
|
|
30
|
+
| `chart:${string}`;
|
|
31
|
+
|
|
32
|
+
export type ColorRole = string;
|
|
33
|
+
|
|
34
|
+
export type ColorUsage = "bg" | "border" | "text" | "icon" | "ring" | "shadow" | "stroke" | "fill";
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Token-safe background hints.
|
|
38
|
+
* Tokens must not embed literal colors.
|
|
39
|
+
*/
|
|
40
|
+
export type TokenBackgroundHint = { kind: "auto" } | { kind: "role"; role: ColorRole };
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Token-safe query shape (Phase 3).
|
|
44
|
+
* - `output` is forbidden (export/serializer concern)
|
|
45
|
+
* - `on.kind: "color"` is forbidden (no embedded colors)
|
|
46
|
+
* - `state` must not be encoded (states are operators; declare via TokenDefinition.states)
|
|
47
|
+
*/
|
|
48
|
+
export type TokenQuery = Omit<ColorQuery, "output" | "on" | "state"> & {
|
|
49
|
+
output?: never;
|
|
50
|
+
on?: TokenBackgroundHint;
|
|
51
|
+
state?: never;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Token-supported interactive states.
|
|
56
|
+
*
|
|
57
|
+
* Note: `"default"` is the base token, so it is intentionally excluded here.
|
|
58
|
+
*/
|
|
59
|
+
export type TokenState = Exclude<ColorState, "default">;
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Declarative set of supported states for a token.
|
|
63
|
+
* Use `true` to mark a state as supported.
|
|
64
|
+
*/
|
|
65
|
+
export type TokenStates = Partial<Record<TokenState, true>>;
|
|
66
|
+
|
|
67
|
+
export type BackgroundHint =
|
|
68
|
+
| { kind: "auto" }
|
|
69
|
+
| { kind: "role"; role: ColorRole }
|
|
70
|
+
| { kind: "color"; value: CssColorString };
|
|
71
|
+
|
|
72
|
+
export type ContrastRequirement =
|
|
73
|
+
| { model: "apca"; targetLc: number; minLc?: number; maxLc?: number }
|
|
74
|
+
| { model: "wcag2"; minRatio: number }
|
|
75
|
+
| { model: "none" };
|
|
76
|
+
|
|
77
|
+
export type AlphaStrategy =
|
|
78
|
+
| { mode: "none" }
|
|
79
|
+
| { mode: "fixed"; alpha: number }
|
|
80
|
+
| { mode: "solveOnBackground" };
|
|
81
|
+
|
|
82
|
+
export interface OutputOptions {
|
|
83
|
+
preferSpace?: ColorSpace;
|
|
84
|
+
includeSpaces?: ColorSpace[];
|
|
85
|
+
gamutMapping?: "clip" | "compressChroma" | "preferP3ThenCompress";
|
|
86
|
+
strict?: boolean;
|
|
87
|
+
precision?: {
|
|
88
|
+
l?: number;
|
|
89
|
+
c?: number;
|
|
90
|
+
h?: number;
|
|
91
|
+
alpha?: number;
|
|
92
|
+
};
|
|
93
|
+
includeMeta?: boolean;
|
|
94
|
+
srgbFormat?: "hex" | "rgb" | "rgba";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface RawColor {
|
|
98
|
+
space: ColorSpace;
|
|
99
|
+
channels: number[];
|
|
100
|
+
alpha: number;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface ColorMeta {
|
|
104
|
+
role?: ColorRole;
|
|
105
|
+
variant?: SemanticVariant;
|
|
106
|
+
usage?: ColorUsage;
|
|
107
|
+
context?: ColorContext;
|
|
108
|
+
surface?: SurfaceIntent;
|
|
109
|
+
state?: ColorState;
|
|
110
|
+
emphasis?: ColorEmphasis;
|
|
111
|
+
on?: BackgroundHint;
|
|
112
|
+
contrast?: ContrastRequirement;
|
|
113
|
+
step?: number;
|
|
114
|
+
variantUsed?: string;
|
|
115
|
+
seedUsed?: CssColorString;
|
|
116
|
+
gamutMapping?: OutputOptions["gamutMapping"];
|
|
117
|
+
spaceUsed?: ColorSpace;
|
|
118
|
+
clipped?: boolean;
|
|
119
|
+
compressed?: boolean;
|
|
120
|
+
provenance?: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Declarative token definition consumed by registries, exporters, CLI and codegen.
|
|
125
|
+
*
|
|
126
|
+
* Rules:
|
|
127
|
+
* - Tokens never carry actual color values.
|
|
128
|
+
* - `query.output` is forbidden; output formatting is decided by serializers/exporters.
|
|
129
|
+
* - Do not encode interactive state in `query.state`; declare supported states via `states`.
|
|
130
|
+
* - Do not embed literal background colors via `query.on: { kind: "color" }`.
|
|
131
|
+
*/
|
|
132
|
+
export interface TokenDefinition {
|
|
133
|
+
name: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
query: TokenQuery;
|
|
136
|
+
category?: string;
|
|
137
|
+
states?: TokenStates;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Collection of base token definitions keyed by token name.
|
|
142
|
+
*/
|
|
143
|
+
export interface TokenRegistry {
|
|
144
|
+
tokens: Record<string, TokenDefinition>;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface ResolvedColor {
|
|
148
|
+
/**
|
|
149
|
+
* Serialized string corresponding to `preferSpace`.
|
|
150
|
+
* Always present.
|
|
151
|
+
*/
|
|
152
|
+
value: CssColorString;
|
|
153
|
+
/**
|
|
154
|
+
* Auxiliary sRGB representation.
|
|
155
|
+
* Only present if included via `includeSpaces`.
|
|
156
|
+
*/
|
|
157
|
+
srgb?: CssColorString;
|
|
158
|
+
/**
|
|
159
|
+
* Auxiliary Display-P3 representation.
|
|
160
|
+
* Only present if included via `includeSpaces`.
|
|
161
|
+
*/
|
|
162
|
+
p3?: CssColorString;
|
|
163
|
+
/**
|
|
164
|
+
* Auxiliary OKLCH representation.
|
|
165
|
+
* Only present if included via `includeSpaces`.
|
|
166
|
+
*/
|
|
167
|
+
oklch?: CssColorString;
|
|
168
|
+
alpha: number;
|
|
169
|
+
meta?: ColorMeta;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export interface ColorQuery {
|
|
173
|
+
role: ColorRole;
|
|
174
|
+
variant?: SemanticVariant;
|
|
175
|
+
usage?: ColorUsage;
|
|
176
|
+
context?: ColorContext;
|
|
177
|
+
surface?: SurfaceIntent;
|
|
178
|
+
state?: ColorState;
|
|
179
|
+
emphasis?: ColorEmphasis;
|
|
180
|
+
on?: BackgroundHint;
|
|
181
|
+
contrast?: ContrastRequirement;
|
|
182
|
+
alpha?: AlphaStrategy;
|
|
183
|
+
output?: OutputOptions;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export interface OnSolidQuery {
|
|
187
|
+
bgRole: ColorRole;
|
|
188
|
+
usage: "text" | "icon";
|
|
189
|
+
context?: ColorContext;
|
|
190
|
+
state?: ColorState;
|
|
191
|
+
emphasis?: ColorEmphasis;
|
|
192
|
+
alpha?: AlphaStrategy;
|
|
193
|
+
contrast?: ContrastRequirement;
|
|
194
|
+
output?: OutputOptions;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export interface SemanticColorTheme {
|
|
198
|
+
resolve(query: ColorQuery): ResolvedColor;
|
|
199
|
+
resolveMany(queries: ColorQuery[]): ResolvedColor[];
|
|
200
|
+
color(role: ColorRole, options?: Omit<ColorQuery, "role">): ResolvedColor;
|
|
201
|
+
onSolid(query: OnSolidQuery): ResolvedColor;
|
|
202
|
+
withContext(context: ColorContext): SemanticColorTheme;
|
|
203
|
+
export: {
|
|
204
|
+
cssVars(): string;
|
|
205
|
+
json(): unknown;
|
|
206
|
+
};
|
|
207
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { parseColor } from "./parseColor.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const lerp = (start: number, end: number, t: number) => start + (end - start) * t;
|