@clhaas/palette-kit 0.3.0 → 0.4.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/CHANGELOG.md +25 -0
- package/README.md +80 -87
- package/dist/contrast/contrast.d.ts +16 -0
- package/dist/contrast/contrast.js +102 -0
- package/dist/core/intent-registry.d.ts +11 -0
- package/dist/core/intent-registry.js +70 -0
- package/dist/core/oklch.d.ts +16 -0
- package/dist/core/oklch.js +56 -0
- package/dist/create-palette-kit.d.ts +9 -0
- package/dist/create-palette-kit.js +67 -0
- package/dist/engine/context/context.d.ts +13 -0
- package/dist/engine/context/context.js +37 -0
- package/dist/engine/level/curves.d.ts +17 -0
- package/dist/engine/level/curves.js +49 -0
- package/dist/engine/level/level.d.ts +4 -0
- package/dist/engine/level/level.js +13 -0
- package/dist/engine/relation/relation.d.ts +105 -0
- package/dist/engine/relation/relation.js +137 -0
- package/dist/engine/resolve/resolve.d.ts +36 -0
- package/dist/engine/resolve/resolve.js +116 -0
- package/dist/engine/state/state.d.ts +46 -0
- package/dist/engine/state/state.js +68 -0
- package/dist/engine/usage/fill.d.ts +9 -0
- package/dist/engine/usage/fill.js +9 -0
- package/dist/engine/usage/lines.d.ts +9 -0
- package/dist/engine/usage/lines.js +9 -0
- package/dist/engine/usage/overlays.d.ts +9 -0
- package/dist/engine/usage/overlays.js +9 -0
- package/dist/engine/usage/strategy.d.ts +56 -0
- package/dist/engine/usage/strategy.js +30 -0
- package/dist/engine/usage/visualVocabulary.d.ts +9 -0
- package/dist/engine/usage/visualVocabulary.js +9 -0
- package/dist/export/serialize.d.ts +14 -0
- package/dist/export/serialize.js +89 -0
- package/dist/export/types.d.ts +37 -0
- package/dist/export/types.js +31 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/operators/convert.d.ts +32 -0
- package/dist/operators/convert.js +80 -0
- package/dist/presets/presets.d.ts +95 -0
- package/dist/presets/presets.js +308 -0
- package/dist/types/index.d.ts +111 -187
- package/dist/utils/errors/errors.d.ts +17 -0
- package/dist/utils/errors/errors.js +22 -0
- package/docs/API.md +167 -0
- package/docs/Alpha.md +14 -0
- package/docs/Architecture.md +56 -0
- package/docs/CLI.md +22 -0
- package/docs/Concepts.md +73 -0
- package/docs/Config.md +144 -0
- package/docs/Diagnostics.md +22 -0
- package/docs/Exporters.md +33 -0
- package/docs/FAQ.md +59 -0
- package/docs/Migration.md +61 -0
- package/docs/Overlays.md +33 -0
- package/docs/README.md +60 -0
- package/docs/Text.md +41 -0
- package/docs/Tokens.md +42 -0
- package/docs/Usage-JSON.md +39 -0
- package/docs/Usage-ReactNative.md +63 -0
- package/docs/Usage-Web.md +66 -0
- package/docs/Validation.md +97 -0
- package/docs/Why.md +37 -0
- package/docs/_api-surface.md +53 -0
- package/docs/snippets/serialize-oklch.md +9 -0
- package/docs/spec.md +98 -0
- package/package.json +74 -59
- package/.codex/skills/color-pipeline-implementer/SKILL.md +0 -23
- package/.codex/skills/commit-message-crafter/SKILL.md +0 -63
- package/.codex/skills/commit-message-crafter/references/benchmarks.md +0 -20
- package/.codex/skills/contrast-solver-helper/SKILL.md +0 -20
- package/.codex/skills/exporters-builder/SKILL.md +0 -20
- package/.codex/skills/markdownlint-writer/SKILL.md +0 -32
- package/.codex/skills/phase-implementation-runbook/SKILL.md +0 -92
- package/.codex/skills/type-contract-auditor/SKILL.md +0 -21
- package/.github/skills/review-guide/SKILL.md +0 -23
- package/.github/skills/review-guide/references/review-guide-v0.3.md +0 -629
- package/.markdownlint.json +0 -4
- package/AGENTS.md +0 -16
- package/biome.json +0 -43
- package/dist/cli/args.d.ts +0 -12
- package/dist/cli/args.js +0 -56
- package/dist/cli/args.test.d.ts +0 -1
- package/dist/cli/args.test.js +0 -22
- package/dist/cli/codegen/__snapshots__/tokens.test.js.snap +0 -87
- package/dist/cli/codegen/tokens.d.ts +0 -12
- package/dist/cli/codegen/tokens.js +0 -139
- package/dist/cli/codegen/tokens.test.d.ts +0 -1
- package/dist/cli/codegen/tokens.test.js +0 -51
- package/dist/cli/config.d.ts +0 -40
- package/dist/cli/config.js +0 -34
- package/dist/cli/validate.d.ts +0 -2
- package/dist/cli/validate.js +0 -33
- package/dist/cli/validate.test.d.ts +0 -1
- package/dist/cli/validate.test.js +0 -40
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -148
- package/dist/contrast/apca.d.ts +0 -2
- package/dist/contrast/apca.js +0 -15
- package/dist/contrast/apca.test.d.ts +0 -1
- package/dist/contrast/apca.test.js +0 -16
- package/dist/contrast/index.d.ts +0 -4
- package/dist/contrast/index.js +0 -4
- package/dist/contrast/scoring.d.ts +0 -4
- package/dist/contrast/scoring.js +0 -31
- package/dist/contrast/scoring.test.d.ts +0 -1
- package/dist/contrast/scoring.test.js +0 -148
- package/dist/contrast/solver.d.ts +0 -13
- package/dist/contrast/solver.js +0 -170
- package/dist/contrast/solver.test.d.ts +0 -1
- package/dist/contrast/solver.test.js +0 -75
- package/dist/contrast/types.d.ts +0 -17
- package/dist/contrast/types.js +0 -1
- package/dist/contrast/utils.d.ts +0 -4
- package/dist/contrast/utils.js +0 -18
- package/dist/contrast/wcag2.d.ts +0 -3
- package/dist/contrast/wcag2.js +0 -19
- package/dist/contrast/wcag2.test.d.ts +0 -1
- package/dist/contrast/wcag2.test.js +0 -17
- package/dist/core/createTheme.d.ts +0 -35
- package/dist/core/createTheme.js +0 -24
- package/dist/core/dx-helpers.test.d.ts +0 -1
- package/dist/core/dx-helpers.test.js +0 -61
- package/dist/core/index.d.ts +0 -2
- package/dist/core/index.js +0 -2
- package/dist/core/onSolid.test.d.ts +0 -1
- package/dist/core/onSolid.test.js +0 -118
- package/dist/core/qa.v1.test.d.ts +0 -1
- package/dist/core/qa.v1.test.js +0 -112
- package/dist/core/resolve.d.ts +0 -3
- package/dist/core/resolve.js +0 -8
- package/dist/core/resolve.test.d.ts +0 -1
- package/dist/core/resolve.test.js +0 -89
- package/dist/core/resolveMany.d.ts +0 -8
- package/dist/core/resolveMany.js +0 -17
- package/dist/core/tokenRegistry.d.ts +0 -23
- package/dist/core/tokenRegistry.js +0 -83
- package/dist/core/tokenRegistry.test.d.ts +0 -1
- package/dist/core/tokenRegistry.test.js +0 -133
- package/dist/engine/applyOperators.d.ts +0 -3
- package/dist/engine/applyOperators.js +0 -23
- package/dist/engine/context.d.ts +0 -4
- package/dist/engine/context.js +0 -1
- package/dist/engine/gamut.d.ts +0 -13
- package/dist/engine/gamut.js +0 -101
- package/dist/engine/gamut.test.d.ts +0 -1
- package/dist/engine/gamut.test.js +0 -23
- package/dist/engine/generateScale.d.ts +0 -15
- package/dist/engine/generateScale.js +0 -29
- package/dist/engine/generateScale.test.d.ts +0 -1
- package/dist/engine/generateScale.test.js +0 -32
- package/dist/engine/index.d.ts +0 -8
- package/dist/engine/index.js +0 -4
- package/dist/engine/normalize.d.ts +0 -43
- package/dist/engine/normalize.js +0 -403
- package/dist/engine/normalize.test.d.ts +0 -1
- package/dist/engine/normalize.test.js +0 -136
- package/dist/engine/onSolid.d.ts +0 -3
- package/dist/engine/onSolid.js +0 -110
- package/dist/engine/resolveBaseColor.d.ts +0 -25
- package/dist/engine/resolveBaseColor.js +0 -127
- package/dist/engine/resolveBaseColor.test.d.ts +0 -1
- package/dist/engine/resolveBaseColor.test.js +0 -97
- package/dist/export/__snapshots__/exportTheme.test.js.snap +0 -74
- package/dist/export/exportTheme.d.ts +0 -47
- package/dist/export/exportTheme.js +0 -170
- package/dist/export/exportTheme.test.d.ts +0 -1
- package/dist/export/exportTheme.test.js +0 -118
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/serializeColor.d.ts +0 -1
- package/dist/export/serializeColor.js +0 -1
- package/dist/export/serializeColor.test.d.ts +0 -1
- package/dist/export/serializeColor.test.js +0 -54
- package/dist/export.d.ts +0 -1
- package/dist/export.js +0 -1
- package/dist/operators/emphasis.d.ts +0 -3
- package/dist/operators/emphasis.js +0 -113
- package/dist/operators/emphasis.test.d.ts +0 -1
- package/dist/operators/emphasis.test.js +0 -69
- package/dist/operators/index.d.ts +0 -3
- package/dist/operators/index.js +0 -2
- package/dist/operators/state.d.ts +0 -3
- package/dist/operators/state.js +0 -102
- package/dist/operators/state.test.d.ts +0 -1
- package/dist/operators/state.test.js +0 -48
- package/dist/operators/types.d.ts +0 -13
- package/dist/operators/types.js +0 -1
- package/dist/operators/utils.d.ts +0 -16
- package/dist/operators/utils.js +0 -23
- package/dist/presets/curves.d.ts +0 -28
- package/dist/presets/curves.js +0 -145
- package/dist/presets/index.d.ts +0 -2
- package/dist/presets/index.js +0 -1
- package/dist/presets/tokens/index.d.ts +0 -3
- package/dist/presets/tokens/index.js +0 -3
- package/dist/presets/tokens/minimal-ui.d.ts +0 -6
- package/dist/presets/tokens/minimal-ui.js +0 -53
- package/dist/presets/tokens/modern-ui.d.ts +0 -5
- package/dist/presets/tokens/modern-ui.js +0 -83
- package/dist/presets/tokens/presets.test.d.ts +0 -1
- package/dist/presets/tokens/presets.test.js +0 -31
- package/dist/presets/tokens/radixLike-ui.d.ts +0 -6
- package/dist/presets/tokens/radixLike-ui.js +0 -77
- package/dist/serialize/index.d.ts +0 -1
- package/dist/serialize/index.js +0 -1
- package/dist/serialize/normalizeOutput.d.ts +0 -6
- package/dist/serialize/normalizeOutput.js +0 -45
- package/dist/serialize/serializeColor.d.ts +0 -21
- package/dist/serialize/serializeColor.js +0 -178
- package/dist/serialize/serializeResolved.test.d.ts +0 -1
- package/dist/serialize/serializeResolved.test.js +0 -45
- package/dist/serialize.d.ts +0 -1
- package/dist/serialize.js +0 -1
- package/dist/utils/clamp.d.ts +0 -1
- package/dist/utils/clamp.js +0 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/lerp.d.ts +0 -1
- package/dist/utils/lerp.js +0 -1
- package/dist/utils/parseColor.d.ts +0 -6
- package/dist/utils/parseColor.js +0 -67
- package/dist/utils/parseColor.test.d.ts +0 -1
- package/dist/utils/parseColor.test.js +0 -51
- package/dist/utils/smoothstep.d.ts +0 -1
- package/dist/utils/smoothstep.js +0 -5
- package/planning/phase-10-review.md +0 -550
- package/planning/phase-7-review.md +0 -411
- package/planning/phase-8-review.md +0 -669
- package/planning/phase-9-review.md +0 -564
- package/planning/roadmap-v0.3.md +0 -284
- package/planning/spec-serializer-v0.3.md +0 -324
- package/planning/spec-v0.3.md +0 -305
- package/src/cli/args.test.ts +0 -28
- package/src/cli/args.ts +0 -66
- package/src/cli/codegen/__snapshots__/tokens.test.ts.snap +0 -87
- package/src/cli/codegen/tokens.test.ts +0 -61
- package/src/cli/codegen/tokens.ts +0 -191
- package/src/cli/config.ts +0 -71
- package/src/cli/validate.test.ts +0 -49
- package/src/cli/validate.ts +0 -38
- package/src/cli.ts +0 -183
- package/src/contrast/apca.test.ts +0 -20
- package/src/contrast/apca.ts +0 -26
- package/src/contrast/index.ts +0 -4
- package/src/contrast/scoring.test.ts +0 -188
- package/src/contrast/scoring.ts +0 -48
- package/src/contrast/solver.test.ts +0 -147
- package/src/contrast/solver.ts +0 -235
- package/src/contrast/types.ts +0 -20
- package/src/contrast/utils.ts +0 -28
- package/src/contrast/wcag2.test.ts +0 -21
- package/src/contrast/wcag2.ts +0 -24
- package/src/core/createTheme.ts +0 -78
- package/src/core/dx-helpers.test.ts +0 -82
- package/src/core/index.ts +0 -7
- package/src/core/onSolid.test.ts +0 -146
- package/src/core/qa.v1.test.ts +0 -149
- package/src/core/resolve.test.ts +0 -99
- package/src/core/resolve.ts +0 -11
- package/src/core/resolveMany.ts +0 -22
- package/src/core/tokenRegistry.test.ts +0 -153
- package/src/core/tokenRegistry.ts +0 -114
- package/src/engine/applyOperators.ts +0 -32
- package/src/engine/context.ts +0 -8
- package/src/engine/gamut.test.ts +0 -30
- package/src/engine/gamut.ts +0 -144
- package/src/engine/generateScale.test.ts +0 -46
- package/src/engine/generateScale.ts +0 -48
- package/src/engine/index.ts +0 -8
- package/src/engine/normalize.test.ts +0 -222
- package/src/engine/normalize.ts +0 -550
- package/src/engine/onSolid.ts +0 -178
- package/src/engine/resolveBaseColor.test.ts +0 -117
- package/src/engine/resolveBaseColor.ts +0 -203
- package/src/export/__snapshots__/exportTheme.test.ts.snap +0 -74
- package/src/export/exportTheme.test.ts +0 -144
- package/src/export/exportTheme.ts +0 -251
- package/src/export/index.ts +0 -1
- package/src/export/serializeColor.test.ts +0 -73
- package/src/export/serializeColor.ts +0 -1
- package/src/export.ts +0 -1
- package/src/index.ts +0 -3
- package/src/operators/emphasis.test.ts +0 -85
- package/src/operators/emphasis.ts +0 -132
- package/src/operators/index.ts +0 -3
- package/src/operators/state.test.ts +0 -66
- package/src/operators/state.ts +0 -122
- package/src/operators/types.ts +0 -14
- package/src/operators/utils.ts +0 -44
- package/src/presets/curves.ts +0 -168
- package/src/presets/index.ts +0 -2
- package/src/presets/tokens/index.ts +0 -3
- package/src/presets/tokens/minimal-ui.ts +0 -55
- package/src/presets/tokens/modern-ui.ts +0 -85
- package/src/presets/tokens/presets.test.ts +0 -46
- package/src/presets/tokens/radixLike-ui.ts +0 -79
- package/src/serialize/index.ts +0 -1
- package/src/serialize/normalizeOutput.ts +0 -63
- package/src/serialize/serializeColor.ts +0 -260
- package/src/serialize/serializeResolved.test.ts +0 -57
- package/src/serialize.ts +0 -1
- package/src/types/index.ts +0 -207
- package/src/utils/clamp.ts +0 -2
- package/src/utils/index.ts +0 -1
- package/src/utils/lerp.ts +0 -1
- package/src/utils/parseColor.test.ts +0 -66
- package/src/utils/parseColor.ts +0 -87
- package/src/utils/smoothstep.ts +0 -6
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -15
package/dist/cli.js
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
|
|
3
|
-
import { basename, join, resolve } from "node:path";
|
|
4
|
-
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { CliUsageError, COMMANDS, HELP_TEXT, parseArgs } from "./cli/args.js";
|
|
6
|
-
import { generateTokenArtifacts } from "./cli/codegen/tokens.js";
|
|
7
|
-
import { buildConfigTemplate } from "./cli/config.js";
|
|
8
|
-
import { validateConfig } from "./cli/validate.js";
|
|
9
|
-
import { createTheme } from "./core/createTheme.js";
|
|
10
|
-
import { validateTokenRegistry } from "./core/tokenRegistry.js";
|
|
11
|
-
import { exportThemeCss, exportThemeJson } from "./export/exportTheme.js";
|
|
12
|
-
import { minimalUiTokens, modernUiTokens, radixLikeUiTokens } from "./presets/tokens/index.js";
|
|
13
|
-
const readPackageJson = async () => {
|
|
14
|
-
const url = new URL("../package.json", import.meta.url);
|
|
15
|
-
const content = await readFile(url, "utf8");
|
|
16
|
-
return JSON.parse(content);
|
|
17
|
-
};
|
|
18
|
-
const printHelp = () => {
|
|
19
|
-
console.log(HELP_TEXT);
|
|
20
|
-
};
|
|
21
|
-
const printVersion = async () => {
|
|
22
|
-
const pkg = await readPackageJson();
|
|
23
|
-
console.log(pkg.version);
|
|
24
|
-
};
|
|
25
|
-
const ensureDir = async (dir) => {
|
|
26
|
-
await mkdir(dir, { recursive: true });
|
|
27
|
-
};
|
|
28
|
-
const exists = async (filePath) => {
|
|
29
|
-
try {
|
|
30
|
-
await stat(filePath);
|
|
31
|
-
return true;
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
return false;
|
|
35
|
-
}
|
|
36
|
-
};
|
|
37
|
-
const loadConfig = async (configPath) => {
|
|
38
|
-
const resolved = resolve(configPath);
|
|
39
|
-
try {
|
|
40
|
-
const module = await import(pathToFileURL(resolved).href);
|
|
41
|
-
const config = (module.default ?? module);
|
|
42
|
-
return config;
|
|
43
|
-
}
|
|
44
|
-
catch (error) {
|
|
45
|
-
const suffix = resolved.endsWith(".ts")
|
|
46
|
-
? "\nTip: Use a .mjs config or run Node with a TS loader (e.g. tsx)."
|
|
47
|
-
: "";
|
|
48
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
49
|
-
throw new Error(`Unable to load config at ${resolved}. ${message}${suffix}`);
|
|
50
|
-
}
|
|
51
|
-
};
|
|
52
|
-
const tokenPresetMap = {
|
|
53
|
-
"minimal-ui": minimalUiTokens,
|
|
54
|
-
"radixLike-ui": radixLikeUiTokens,
|
|
55
|
-
"modern-ui": modernUiTokens,
|
|
56
|
-
};
|
|
57
|
-
const toThemeTokens = (registry) => Object.fromEntries(Object.entries(registry.tokens).map(([name, token]) => [name, token.query]));
|
|
58
|
-
const writeTokensCodegen = async (outDir, registry) => {
|
|
59
|
-
const generated = generateTokenArtifacts(registry);
|
|
60
|
-
await writeFile(join(outDir, "tokens.ts"), generated.tokensTs, "utf8");
|
|
61
|
-
await writeFile(join(outDir, "tokens.d.ts"), generated.tokensDts, "utf8");
|
|
62
|
-
return generated.tokenNames;
|
|
63
|
-
};
|
|
64
|
-
const writeReport = async (outDir, config, tokenCount, outputFiles) => {
|
|
65
|
-
const lines = [
|
|
66
|
-
"# Palette Kit Report",
|
|
67
|
-
"",
|
|
68
|
-
`- preset: ${config.tokens.preset}`,
|
|
69
|
-
`- tokens: ${tokenCount}`,
|
|
70
|
-
`- output: preferSpace=${config.output?.preferSpace ?? "oklch"}`,
|
|
71
|
-
`- includeSpaces: ${(config.output?.includeSpaces ?? []).join(", ") || "(none)"}`,
|
|
72
|
-
`- files: ${outputFiles.join(", ")}`,
|
|
73
|
-
];
|
|
74
|
-
await writeFile(join(outDir, "report.md"), `${lines.join("\n")}\n`, "utf8");
|
|
75
|
-
};
|
|
76
|
-
const runInit = async (flags) => {
|
|
77
|
-
const targetPath = typeof flags.path === "string" ? flags.path : ".";
|
|
78
|
-
const force = Boolean(flags.force);
|
|
79
|
-
const outDir = resolve(targetPath);
|
|
80
|
-
await ensureDir(outDir);
|
|
81
|
-
const filePath = join(outDir, "palette.config.ts");
|
|
82
|
-
if (!force && (await exists(filePath))) {
|
|
83
|
-
throw new Error(`Config already exists at ${filePath}. Use --force to overwrite`);
|
|
84
|
-
}
|
|
85
|
-
const pkg = await readPackageJson();
|
|
86
|
-
await writeFile(filePath, buildConfigTemplate(pkg.name), "utf8");
|
|
87
|
-
console.log(`Created ${filePath}`);
|
|
88
|
-
};
|
|
89
|
-
const runBuild = async (flags) => {
|
|
90
|
-
const configPath = typeof flags.config === "string" ? flags.config : "palette.config.ts";
|
|
91
|
-
const outDir = resolve(typeof flags.outDir === "string" ? flags.outDir : "dist/palette");
|
|
92
|
-
const report = Boolean(flags.report);
|
|
93
|
-
const config = await loadConfig(configPath);
|
|
94
|
-
validateConfig(config);
|
|
95
|
-
const registry = tokenPresetMap[config.tokens.preset];
|
|
96
|
-
validateTokenRegistry(registry);
|
|
97
|
-
const theme = createTheme(config.theme);
|
|
98
|
-
const tokens = toThemeTokens(registry);
|
|
99
|
-
const css = exportThemeCss(theme, tokens, config.output).css;
|
|
100
|
-
const json = exportThemeJson(theme, tokens, config.output);
|
|
101
|
-
await ensureDir(outDir);
|
|
102
|
-
const tokenNames = await writeTokensCodegen(outDir, registry);
|
|
103
|
-
await writeFile(join(outDir, "tokens.css"), css, "utf8");
|
|
104
|
-
await writeFile(join(outDir, "tokens.json"), `${JSON.stringify(json, null, 2)}\n`, "utf8");
|
|
105
|
-
if (report) {
|
|
106
|
-
await writeReport(outDir, config, tokenNames.length, [
|
|
107
|
-
"tokens.css",
|
|
108
|
-
"tokens.json",
|
|
109
|
-
"tokens.ts",
|
|
110
|
-
"tokens.d.ts",
|
|
111
|
-
]);
|
|
112
|
-
}
|
|
113
|
-
console.log(`Wrote ${basename(outDir)}/tokens.css, tokens.json, tokens.ts, tokens.d.ts`);
|
|
114
|
-
};
|
|
115
|
-
const main = async () => {
|
|
116
|
-
try {
|
|
117
|
-
const parsed = parseArgs(process.argv.slice(2));
|
|
118
|
-
if (parsed.version) {
|
|
119
|
-
await printVersion();
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (parsed.help || !parsed.command) {
|
|
123
|
-
printHelp();
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
if (!COMMANDS.includes(parsed.command)) {
|
|
127
|
-
throw new CliUsageError(`Unknown command: ${parsed.command}`);
|
|
128
|
-
}
|
|
129
|
-
if (parsed.command === "init") {
|
|
130
|
-
await runInit(parsed.flags);
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
if (parsed.command === "build") {
|
|
134
|
-
await runBuild(parsed.flags);
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
catch (error) {
|
|
139
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
140
|
-
console.error(message);
|
|
141
|
-
if (error instanceof CliUsageError) {
|
|
142
|
-
console.error("");
|
|
143
|
-
printHelp();
|
|
144
|
-
}
|
|
145
|
-
process.exitCode = 1;
|
|
146
|
-
}
|
|
147
|
-
};
|
|
148
|
-
void main();
|
package/dist/contrast/apca.d.ts
DELETED
package/dist/contrast/apca.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { APCAcontrast, sRGBtoY } from "apca-w3";
|
|
2
|
-
const clamp01 = (value) => Math.min(1, Math.max(0, value));
|
|
3
|
-
const toApcaInput = (color) => [
|
|
4
|
-
clamp01(color.r) * 255,
|
|
5
|
-
clamp01(color.g) * 255,
|
|
6
|
-
clamp01(color.b) * 255,
|
|
7
|
-
];
|
|
8
|
-
export function computeApcaLc(fg, bg) {
|
|
9
|
-
const fgY = sRGBtoY(toApcaInput(fg));
|
|
10
|
-
const bgY = sRGBtoY(toApcaInput(bg));
|
|
11
|
-
const raw = APCAcontrast(fgY, bgY);
|
|
12
|
-
const value = typeof raw === "number" ? raw : Number(raw);
|
|
13
|
-
// Fail-soft to avoid propagating NaN into solver scoring.
|
|
14
|
-
return Number.isFinite(value) ? value : 0;
|
|
15
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { computeApcaLc } from "./apca.js";
|
|
3
|
-
describe("computeApcaLc", () => {
|
|
4
|
-
it("reports high magnitude for black on white", () => {
|
|
5
|
-
const lc = computeApcaLc({ r: 0, g: 0, b: 0 }, { r: 1, g: 1, b: 1 });
|
|
6
|
-
expect(Math.abs(lc)).toBeGreaterThan(60);
|
|
7
|
-
});
|
|
8
|
-
it("reports high magnitude for white on black", () => {
|
|
9
|
-
const lc = computeApcaLc({ r: 1, g: 1, b: 1 }, { r: 0, g: 0, b: 0 });
|
|
10
|
-
expect(Math.abs(lc)).toBeGreaterThan(60);
|
|
11
|
-
});
|
|
12
|
-
it("does not return NaN", () => {
|
|
13
|
-
const lc = computeApcaLc({ r: 0.2, g: 0.4, b: 0.6 }, { r: 0.1, g: 0.1, b: 0.1 });
|
|
14
|
-
expect(Number.isNaN(lc)).toBe(false);
|
|
15
|
-
});
|
|
16
|
-
});
|
package/dist/contrast/index.d.ts
DELETED
package/dist/contrast/index.js
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import type { ContrastRequirement } from "../types/index.js";
|
|
2
|
-
import type { ContrastCheckResult } from "./types.js";
|
|
3
|
-
export declare const scoreApca: (value: number, target: number, min: number, max: number, hasMax: boolean) => number;
|
|
4
|
-
export declare const scoreContrast: (result: ContrastCheckResult, req: ContrastRequirement) => number;
|
package/dist/contrast/scoring.js
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export const scoreApca = (value, target, min, max, hasMax) => {
|
|
2
|
-
if (Number.isNaN(value)) {
|
|
3
|
-
return Number.NEGATIVE_INFINITY;
|
|
4
|
-
}
|
|
5
|
-
if (hasMax) {
|
|
6
|
-
if (value >= min && value <= max) {
|
|
7
|
-
return 1000 - Math.abs(value - target);
|
|
8
|
-
}
|
|
9
|
-
if (value < min) {
|
|
10
|
-
return -(min - value);
|
|
11
|
-
}
|
|
12
|
-
return -(value - max);
|
|
13
|
-
}
|
|
14
|
-
if (value < min) {
|
|
15
|
-
const distance = min - value;
|
|
16
|
-
return -distance * 10 - Math.abs(value - target);
|
|
17
|
-
}
|
|
18
|
-
return -Math.abs(value - target);
|
|
19
|
-
};
|
|
20
|
-
export const scoreContrast = (result, req) => {
|
|
21
|
-
if (!Number.isFinite(result.value)) {
|
|
22
|
-
return Number.NEGATIVE_INFINITY;
|
|
23
|
-
}
|
|
24
|
-
if (req.model === "apca") {
|
|
25
|
-
const min = req.minLc ?? req.targetLc;
|
|
26
|
-
const hasMax = req.maxLc !== undefined;
|
|
27
|
-
const max = req.maxLc ?? Number.POSITIVE_INFINITY;
|
|
28
|
-
return scoreApca(result.value, req.targetLc, min, max, hasMax);
|
|
29
|
-
}
|
|
30
|
-
return result.value;
|
|
31
|
-
};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { scoreApca, scoreContrast } from "./scoring.js";
|
|
3
|
-
describe("scoreApca", () => {
|
|
4
|
-
it("prefers values closer to target within range", () => {
|
|
5
|
-
const target = 60;
|
|
6
|
-
const min = 50;
|
|
7
|
-
const max = 80;
|
|
8
|
-
const hasMax = true;
|
|
9
|
-
expect(scoreApca(60, target, min, max, hasMax)).toBeGreaterThan(scoreApca(80, target, min, max, hasMax));
|
|
10
|
-
});
|
|
11
|
-
it("penalizes values outside the range", () => {
|
|
12
|
-
const target = 60;
|
|
13
|
-
const min = 50;
|
|
14
|
-
const max = 80;
|
|
15
|
-
const hasMax = true;
|
|
16
|
-
expect(scoreApca(40, target, min, max, hasMax)).toBeLessThan(0);
|
|
17
|
-
expect(scoreApca(90, target, min, max, hasMax)).toBeLessThan(0);
|
|
18
|
-
});
|
|
19
|
-
it("penalizes below min and prefers target when no max", () => {
|
|
20
|
-
const target = 60;
|
|
21
|
-
const min = 50;
|
|
22
|
-
const max = Number.POSITIVE_INFINITY;
|
|
23
|
-
const hasMax = false;
|
|
24
|
-
expect(scoreApca(40, target, min, max, hasMax)).toBeLessThan(scoreApca(55, target, min, max, hasMax));
|
|
25
|
-
expect(scoreApca(60, target, min, max, hasMax)).toBeGreaterThan(scoreApca(70, target, min, max, hasMax));
|
|
26
|
-
});
|
|
27
|
-
it("returns NEGATIVE_INFINITY for NaN values", () => {
|
|
28
|
-
const target = 60;
|
|
29
|
-
const min = 50;
|
|
30
|
-
const max = 80;
|
|
31
|
-
const hasMax = true;
|
|
32
|
-
expect(scoreApca(Number.NaN, target, min, max, hasMax)).toBe(Number.NEGATIVE_INFINITY);
|
|
33
|
-
});
|
|
34
|
-
});
|
|
35
|
-
describe("scoreContrast", () => {
|
|
36
|
-
describe("with APCA contrast model", () => {
|
|
37
|
-
it("delegates to scoreApca with correct parameters when maxLc is defined", () => {
|
|
38
|
-
const result = {
|
|
39
|
-
model: "apca",
|
|
40
|
-
target: 60,
|
|
41
|
-
value: 65,
|
|
42
|
-
pass: true,
|
|
43
|
-
};
|
|
44
|
-
const req = {
|
|
45
|
-
model: "apca",
|
|
46
|
-
targetLc: 60,
|
|
47
|
-
minLc: 50,
|
|
48
|
-
maxLc: 80,
|
|
49
|
-
};
|
|
50
|
-
const score = scoreContrast(result, req);
|
|
51
|
-
const expectedScore = scoreApca(65, 60, 50, 80, true);
|
|
52
|
-
expect(score).toBe(expectedScore);
|
|
53
|
-
});
|
|
54
|
-
it("delegates to scoreApca without max when maxLc is undefined", () => {
|
|
55
|
-
const result = {
|
|
56
|
-
model: "apca",
|
|
57
|
-
target: 60,
|
|
58
|
-
value: 65,
|
|
59
|
-
pass: true,
|
|
60
|
-
};
|
|
61
|
-
const req = {
|
|
62
|
-
model: "apca",
|
|
63
|
-
targetLc: 60,
|
|
64
|
-
minLc: 50,
|
|
65
|
-
};
|
|
66
|
-
const score = scoreContrast(result, req);
|
|
67
|
-
const expectedScore = scoreApca(65, 60, 50, Number.POSITIVE_INFINITY, false);
|
|
68
|
-
expect(score).toBe(expectedScore);
|
|
69
|
-
});
|
|
70
|
-
it("uses targetLc as min when minLc is undefined", () => {
|
|
71
|
-
const result = {
|
|
72
|
-
model: "apca",
|
|
73
|
-
target: 60,
|
|
74
|
-
value: 65,
|
|
75
|
-
pass: true,
|
|
76
|
-
};
|
|
77
|
-
const req = {
|
|
78
|
-
model: "apca",
|
|
79
|
-
targetLc: 60,
|
|
80
|
-
};
|
|
81
|
-
const score = scoreContrast(result, req);
|
|
82
|
-
const expectedScore = scoreApca(65, 60, 60, Number.POSITIVE_INFINITY, false);
|
|
83
|
-
expect(score).toBe(expectedScore);
|
|
84
|
-
});
|
|
85
|
-
});
|
|
86
|
-
describe("with WCAG2 contrast model", () => {
|
|
87
|
-
it("returns the raw contrast value", () => {
|
|
88
|
-
const result = {
|
|
89
|
-
model: "wcag2",
|
|
90
|
-
target: 4.5,
|
|
91
|
-
value: 7.2,
|
|
92
|
-
pass: true,
|
|
93
|
-
};
|
|
94
|
-
const req = {
|
|
95
|
-
model: "wcag2",
|
|
96
|
-
minRatio: 4.5,
|
|
97
|
-
};
|
|
98
|
-
const score = scoreContrast(result, req);
|
|
99
|
-
expect(score).toBe(7.2);
|
|
100
|
-
});
|
|
101
|
-
});
|
|
102
|
-
describe("with NaN or non-finite values", () => {
|
|
103
|
-
it("returns NEGATIVE_INFINITY for NaN", () => {
|
|
104
|
-
const result = {
|
|
105
|
-
model: "apca",
|
|
106
|
-
target: 60,
|
|
107
|
-
value: Number.NaN,
|
|
108
|
-
pass: false,
|
|
109
|
-
};
|
|
110
|
-
const req = {
|
|
111
|
-
model: "apca",
|
|
112
|
-
targetLc: 60,
|
|
113
|
-
minLc: 50,
|
|
114
|
-
};
|
|
115
|
-
const score = scoreContrast(result, req);
|
|
116
|
-
expect(score).toBe(Number.NEGATIVE_INFINITY);
|
|
117
|
-
});
|
|
118
|
-
it("returns NEGATIVE_INFINITY for positive infinity", () => {
|
|
119
|
-
const result = {
|
|
120
|
-
model: "apca",
|
|
121
|
-
target: 60,
|
|
122
|
-
value: Number.POSITIVE_INFINITY,
|
|
123
|
-
pass: false,
|
|
124
|
-
};
|
|
125
|
-
const req = {
|
|
126
|
-
model: "apca",
|
|
127
|
-
targetLc: 60,
|
|
128
|
-
minLc: 50,
|
|
129
|
-
};
|
|
130
|
-
const score = scoreContrast(result, req);
|
|
131
|
-
expect(score).toBe(Number.NEGATIVE_INFINITY);
|
|
132
|
-
});
|
|
133
|
-
it("returns NEGATIVE_INFINITY for negative infinity", () => {
|
|
134
|
-
const result = {
|
|
135
|
-
model: "wcag2",
|
|
136
|
-
target: 4.5,
|
|
137
|
-
value: Number.NEGATIVE_INFINITY,
|
|
138
|
-
pass: false,
|
|
139
|
-
};
|
|
140
|
-
const req = {
|
|
141
|
-
model: "wcag2",
|
|
142
|
-
minRatio: 4.5,
|
|
143
|
-
};
|
|
144
|
-
const score = scoreContrast(result, req);
|
|
145
|
-
expect(score).toBe(Number.NEGATIVE_INFINITY);
|
|
146
|
-
});
|
|
147
|
-
});
|
|
148
|
-
});
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { OkLchColor } from "../engine/generateScale.js";
|
|
2
|
-
import type { CurvePresetName } from "../presets/index.js";
|
|
3
|
-
import type { ContrastRequirement, SurfaceIntent } from "../types/index.js";
|
|
4
|
-
import type { ContrastCheckResult, SolveOptions } from "./types.js";
|
|
5
|
-
export declare function solveContrast(fg: OkLchColor, bg: OkLchColor | undefined, req: ContrastRequirement, ctx: {
|
|
6
|
-
preset?: CurvePresetName;
|
|
7
|
-
surface: SurfaceIntent;
|
|
8
|
-
context: "light" | "dark";
|
|
9
|
-
}, opts?: SolveOptions): {
|
|
10
|
-
color: OkLchColor;
|
|
11
|
-
result: ContrastCheckResult;
|
|
12
|
-
iterations: number;
|
|
13
|
-
};
|
package/dist/contrast/solver.js
DELETED
|
@@ -1,170 +0,0 @@
|
|
|
1
|
-
import { converter } from "culori";
|
|
2
|
-
import { getSurfaceRange } from "../operators/utils.js";
|
|
3
|
-
import { computeApcaLc } from "./apca.js";
|
|
4
|
-
import { scoreContrast } from "./scoring.js";
|
|
5
|
-
import { contrastRatio } from "./wcag2.js";
|
|
6
|
-
const toSrgb = converter("rgb");
|
|
7
|
-
const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
|
|
8
|
-
const clampOkLch = (color, cMax) => ({
|
|
9
|
-
l: clamp(color.l, 0, 100),
|
|
10
|
-
c: clamp(color.c, 0, cMax),
|
|
11
|
-
h: color.h,
|
|
12
|
-
alpha: color.alpha,
|
|
13
|
-
});
|
|
14
|
-
const clampOkLchLoose = (color) => ({
|
|
15
|
-
l: clamp(color.l, 0, 100),
|
|
16
|
-
c: Math.max(0, color.c),
|
|
17
|
-
h: color.h,
|
|
18
|
-
alpha: color.alpha,
|
|
19
|
-
});
|
|
20
|
-
const toSrgbColor = (color) => {
|
|
21
|
-
const rgb = toSrgb({ mode: "oklch", l: clamp(color.l, 0, 100) / 100, c: color.c, h: color.h });
|
|
22
|
-
if (!rgb) {
|
|
23
|
-
return null;
|
|
24
|
-
}
|
|
25
|
-
const r = typeof rgb.r === "number" && Number.isFinite(rgb.r) ? clamp(rgb.r, 0, 1) : 0;
|
|
26
|
-
const g = typeof rgb.g === "number" && Number.isFinite(rgb.g) ? clamp(rgb.g, 0, 1) : 0;
|
|
27
|
-
const b = typeof rgb.b === "number" && Number.isFinite(rgb.b) ? clamp(rgb.b, 0, 1) : 0;
|
|
28
|
-
return { r, g, b };
|
|
29
|
-
};
|
|
30
|
-
const getTarget = (req) => {
|
|
31
|
-
if (req.model === "apca") {
|
|
32
|
-
return req.targetLc;
|
|
33
|
-
}
|
|
34
|
-
if (req.model === "wcag2") {
|
|
35
|
-
return req.minRatio;
|
|
36
|
-
}
|
|
37
|
-
return 0;
|
|
38
|
-
};
|
|
39
|
-
const checkContrast = (fg, bg, req, epsilon) => {
|
|
40
|
-
if (req.model === "none") {
|
|
41
|
-
return { model: "none", target: 0, value: 0, pass: true };
|
|
42
|
-
}
|
|
43
|
-
const fgSrgb = toSrgbColor(fg);
|
|
44
|
-
const bgSrgb = toSrgbColor(bg);
|
|
45
|
-
const target = getTarget(req);
|
|
46
|
-
if (!fgSrgb || !bgSrgb) {
|
|
47
|
-
return { model: req.model, target, value: Number.NaN, pass: false };
|
|
48
|
-
}
|
|
49
|
-
if (req.model === "apca") {
|
|
50
|
-
const value = Math.abs(computeApcaLc(fgSrgb, bgSrgb));
|
|
51
|
-
const minTarget = req.minLc ?? req.targetLc;
|
|
52
|
-
const maxTarget = req.maxLc ?? Number.POSITIVE_INFINITY;
|
|
53
|
-
const pass = Number.isFinite(value) && value >= minTarget - epsilon && value <= maxTarget + epsilon;
|
|
54
|
-
return { model: "apca", target, value, pass };
|
|
55
|
-
}
|
|
56
|
-
const value = contrastRatio(fgSrgb, bgSrgb);
|
|
57
|
-
const pass = Number.isFinite(value) && value + epsilon >= target;
|
|
58
|
-
return { model: "wcag2", target, value, pass };
|
|
59
|
-
};
|
|
60
|
-
const pickBetter = (current, candidate, req) => {
|
|
61
|
-
const currentScore = scoreContrast(current.result, req);
|
|
62
|
-
const candidateScore = scoreContrast(candidate.result, req);
|
|
63
|
-
return candidateScore > currentScore ? candidate : current;
|
|
64
|
-
};
|
|
65
|
-
export function solveContrast(fg, bg, req, ctx, opts) {
|
|
66
|
-
const options = {
|
|
67
|
-
strict: false,
|
|
68
|
-
maxIterations: 24,
|
|
69
|
-
epsilon: 0.01,
|
|
70
|
-
...opts,
|
|
71
|
-
};
|
|
72
|
-
if (req.model === "none") {
|
|
73
|
-
return {
|
|
74
|
-
color: fg,
|
|
75
|
-
result: { model: "none", target: 0, value: 0, pass: true },
|
|
76
|
-
iterations: 0,
|
|
77
|
-
};
|
|
78
|
-
}
|
|
79
|
-
if (!bg) {
|
|
80
|
-
if (options.strict) {
|
|
81
|
-
throw new Error("Contrast solver requires background");
|
|
82
|
-
}
|
|
83
|
-
return {
|
|
84
|
-
color: fg,
|
|
85
|
-
result: { model: req.model, target: getTarget(req), value: Number.NaN, pass: false },
|
|
86
|
-
iterations: 0,
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
const range = getSurfaceRange(ctx.preset, ctx.surface, ctx.context);
|
|
90
|
-
const clamped = clampOkLch(fg, range.cMax);
|
|
91
|
-
const background = clampOkLchLoose(bg);
|
|
92
|
-
let iterations = 0;
|
|
93
|
-
const evaluate = (color) => {
|
|
94
|
-
const result = checkContrast(color, background, req, options.epsilon);
|
|
95
|
-
iterations += 1;
|
|
96
|
-
return result;
|
|
97
|
-
};
|
|
98
|
-
let best = { color: clamped, result: evaluate(clamped) };
|
|
99
|
-
if (best.result.pass) {
|
|
100
|
-
return { ...best, iterations };
|
|
101
|
-
}
|
|
102
|
-
const lMin = 0;
|
|
103
|
-
const lMax = 100;
|
|
104
|
-
const sampleT = 0.25;
|
|
105
|
-
const sampleDown = clamp(clamped.l + (lMin - clamped.l) * sampleT, lMin, lMax);
|
|
106
|
-
const sampleUp = clamp(clamped.l + (lMax - clamped.l) * sampleT, lMin, lMax);
|
|
107
|
-
let preferredBound = lMin;
|
|
108
|
-
if (iterations < options.maxIterations) {
|
|
109
|
-
const downCandidate = {
|
|
110
|
-
color: { ...clamped, l: sampleDown },
|
|
111
|
-
result: evaluate({ ...clamped, l: sampleDown }),
|
|
112
|
-
};
|
|
113
|
-
best = pickBetter(best, downCandidate, req);
|
|
114
|
-
if (best.result.pass) {
|
|
115
|
-
return { ...best, iterations };
|
|
116
|
-
}
|
|
117
|
-
if (iterations < options.maxIterations) {
|
|
118
|
-
const upCandidate = {
|
|
119
|
-
color: { ...clamped, l: sampleUp },
|
|
120
|
-
result: evaluate({ ...clamped, l: sampleUp }),
|
|
121
|
-
};
|
|
122
|
-
best = pickBetter(best, upCandidate, req);
|
|
123
|
-
if (best.result.pass) {
|
|
124
|
-
return { ...best, iterations };
|
|
125
|
-
}
|
|
126
|
-
preferredBound =
|
|
127
|
-
scoreContrast(upCandidate.result, req) > scoreContrast(downCandidate.result, req)
|
|
128
|
-
? lMax
|
|
129
|
-
: lMin;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const remainingAfterSamples = Math.max(0, options.maxIterations - iterations);
|
|
133
|
-
const lSteps = Math.max(4, remainingAfterSamples);
|
|
134
|
-
for (let step = 1; step <= lSteps && iterations < options.maxIterations; step += 1) {
|
|
135
|
-
const t = step / lSteps;
|
|
136
|
-
const l = clamp(clamped.l + (preferredBound - clamped.l) * t, lMin, lMax);
|
|
137
|
-
const candidateColor = { ...clamped, l };
|
|
138
|
-
const result = evaluate(candidateColor);
|
|
139
|
-
const candidate = { color: candidateColor, result };
|
|
140
|
-
best = pickBetter(best, candidate, req);
|
|
141
|
-
if (result.pass) {
|
|
142
|
-
return { color: candidateColor, result, iterations };
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
let current = { ...best.color };
|
|
146
|
-
while (iterations < options.maxIterations && current.c > 0) {
|
|
147
|
-
const nextC = clamp(current.c * 0.9, 0, range.cMax);
|
|
148
|
-
current = { ...current, c: nextC };
|
|
149
|
-
const result = evaluate(current);
|
|
150
|
-
best = pickBetter(best, { color: current, result }, req);
|
|
151
|
-
if (result.pass) {
|
|
152
|
-
return { color: current, result, iterations };
|
|
153
|
-
}
|
|
154
|
-
const sweepSteps = Math.min(3, options.maxIterations - iterations);
|
|
155
|
-
for (let step = 1; step <= sweepSteps && iterations < options.maxIterations; step += 1) {
|
|
156
|
-
const t = step / sweepSteps;
|
|
157
|
-
const l = clamp(current.l + (preferredBound - current.l) * t, lMin, lMax);
|
|
158
|
-
const candidateColor = { ...current, l };
|
|
159
|
-
const candidateResult = evaluate(candidateColor);
|
|
160
|
-
best = pickBetter(best, { color: candidateColor, result: candidateResult }, req);
|
|
161
|
-
if (candidateResult.pass) {
|
|
162
|
-
return { color: candidateColor, result: candidateResult, iterations };
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
if (options.strict) {
|
|
167
|
-
throw new Error(`Contrast solver failed (${best.result.model}) target=${best.result.target} value=${best.result.value} iterations=${iterations}`);
|
|
168
|
-
}
|
|
169
|
-
return { color: best.color, result: best.result, iterations };
|
|
170
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,75 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { parseColor } from "../utils/parseColor.js";
|
|
3
|
-
import { solveContrast } from "./solver.js";
|
|
4
|
-
const toOkLch = (hex) => {
|
|
5
|
-
const parsed = parseColor(hex);
|
|
6
|
-
return {
|
|
7
|
-
l: parsed.okLch.channels[0] * 100,
|
|
8
|
-
c: parsed.okLch.channels[1],
|
|
9
|
-
h: parsed.okLch.channels[2],
|
|
10
|
-
alpha: parsed.okLch.alpha,
|
|
11
|
-
};
|
|
12
|
-
};
|
|
13
|
-
describe("solveContrast", () => {
|
|
14
|
-
it("raises contrast on light backgrounds", () => {
|
|
15
|
-
const fg = toOkLch("#777777");
|
|
16
|
-
const bg = toOkLch("#ffffff");
|
|
17
|
-
const result = solveContrast(fg, bg, { model: "wcag2", minRatio: 4.5 }, { surface: "surface", context: "light" });
|
|
18
|
-
expect(result.result.pass).toBe(true);
|
|
19
|
-
expect(result.result.value).toBeGreaterThanOrEqual(4.5);
|
|
20
|
-
expect(result.color.l).toBeGreaterThanOrEqual(0);
|
|
21
|
-
expect(result.color.l).toBeLessThanOrEqual(100);
|
|
22
|
-
expect(result.color.c).toBeGreaterThanOrEqual(0);
|
|
23
|
-
});
|
|
24
|
-
it("returns original color when contrast model is none", () => {
|
|
25
|
-
const fg = toOkLch("#777777");
|
|
26
|
-
const bg = toOkLch("#ffffff");
|
|
27
|
-
const result = solveContrast(fg, bg, { model: "none" }, { surface: "surface", context: "light" });
|
|
28
|
-
expect(result.result.pass).toBe(true);
|
|
29
|
-
expect(result.iterations).toBe(0);
|
|
30
|
-
expect(result.color).toEqual(fg);
|
|
31
|
-
});
|
|
32
|
-
it("skips when background is missing and strict is false", () => {
|
|
33
|
-
const fg = toOkLch("#777777");
|
|
34
|
-
const result = solveContrast(fg, undefined, { model: "wcag2", minRatio: 4.5 }, { surface: "surface", context: "light" }, { strict: false });
|
|
35
|
-
expect(result.result.pass).toBe(false);
|
|
36
|
-
expect(Number.isNaN(result.result.value)).toBe(true);
|
|
37
|
-
});
|
|
38
|
-
it("throws when background is missing and strict is true", () => {
|
|
39
|
-
const fg = toOkLch("#777777");
|
|
40
|
-
expect(() => solveContrast(fg, undefined, { model: "wcag2", minRatio: 4.5 }, { surface: "surface", context: "light" }, { strict: true })).toThrowError(/requires background/i);
|
|
41
|
-
});
|
|
42
|
-
it("raises contrast on dark backgrounds", () => {
|
|
43
|
-
const fg = toOkLch("#777777");
|
|
44
|
-
const bg = toOkLch("#111111");
|
|
45
|
-
const result = solveContrast(fg, bg, { model: "wcag2", minRatio: 4.5 }, { surface: "surface", context: "dark" });
|
|
46
|
-
expect(result.result.pass).toBe(true);
|
|
47
|
-
expect(result.result.value).toBeGreaterThanOrEqual(4.5);
|
|
48
|
-
});
|
|
49
|
-
it("keeps hue stable", () => {
|
|
50
|
-
const fg = toOkLch("#3366ff");
|
|
51
|
-
const bg = toOkLch("#ffffff");
|
|
52
|
-
const result = solveContrast(fg, bg, { model: "wcag2", minRatio: 4.5 }, { surface: "surface", context: "light" });
|
|
53
|
-
expect(result.color.h).toBeCloseTo(fg.h, 6);
|
|
54
|
-
});
|
|
55
|
-
it("throws in strict mode when target is unattainable", () => {
|
|
56
|
-
const fg = toOkLch("#ffffff");
|
|
57
|
-
const bg = toOkLch("#ffffff");
|
|
58
|
-
expect(() => solveContrast(fg, bg, { model: "wcag2", minRatio: 30 }, { surface: "surface", context: "light" }, { strict: true })).toThrowError(/contrast solver failed/i);
|
|
59
|
-
});
|
|
60
|
-
it("prefers APCA values closer to target within the allowed range", () => {
|
|
61
|
-
const fg = toOkLch("#777777");
|
|
62
|
-
const bg = toOkLch("#ffffff");
|
|
63
|
-
const minLc = 50;
|
|
64
|
-
const maxLc = 80;
|
|
65
|
-
const targetLc = 60;
|
|
66
|
-
const result = solveContrast(fg, bg, { model: "apca", targetLc, minLc, maxLc }, { surface: "surface", context: "light" });
|
|
67
|
-
const value = result.result.value;
|
|
68
|
-
const targetDistance = Math.abs(value - targetLc);
|
|
69
|
-
const maxDistance = Math.abs(maxLc - targetLc);
|
|
70
|
-
expect(result.result.pass).toBe(true);
|
|
71
|
-
expect(value).toBeGreaterThanOrEqual(minLc);
|
|
72
|
-
expect(value).toBeLessThanOrEqual(maxLc);
|
|
73
|
-
expect(targetDistance).toBeLessThanOrEqual(maxDistance);
|
|
74
|
-
});
|
|
75
|
-
});
|
package/dist/contrast/types.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type ContrastModel = "apca" | "wcag2" | "none";
|
|
2
|
-
export type ContrastCheckResult = {
|
|
3
|
-
model: ContrastModel;
|
|
4
|
-
target: number;
|
|
5
|
-
value: number;
|
|
6
|
-
pass: boolean;
|
|
7
|
-
};
|
|
8
|
-
export type SolveOptions = {
|
|
9
|
-
strict?: boolean;
|
|
10
|
-
maxIterations?: number;
|
|
11
|
-
epsilon?: number;
|
|
12
|
-
};
|
|
13
|
-
export type SrgbColor = {
|
|
14
|
-
r: number;
|
|
15
|
-
g: number;
|
|
16
|
-
b: number;
|
|
17
|
-
};
|
package/dist/contrast/types.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|