@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.
Files changed (312) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +80 -87
  3. package/dist/contrast/contrast.d.ts +16 -0
  4. package/dist/contrast/contrast.js +102 -0
  5. package/dist/core/intent-registry.d.ts +11 -0
  6. package/dist/core/intent-registry.js +70 -0
  7. package/dist/core/oklch.d.ts +16 -0
  8. package/dist/core/oklch.js +56 -0
  9. package/dist/create-palette-kit.d.ts +9 -0
  10. package/dist/create-palette-kit.js +67 -0
  11. package/dist/engine/context/context.d.ts +13 -0
  12. package/dist/engine/context/context.js +37 -0
  13. package/dist/engine/level/curves.d.ts +17 -0
  14. package/dist/engine/level/curves.js +49 -0
  15. package/dist/engine/level/level.d.ts +4 -0
  16. package/dist/engine/level/level.js +13 -0
  17. package/dist/engine/relation/relation.d.ts +105 -0
  18. package/dist/engine/relation/relation.js +137 -0
  19. package/dist/engine/resolve/resolve.d.ts +36 -0
  20. package/dist/engine/resolve/resolve.js +116 -0
  21. package/dist/engine/state/state.d.ts +46 -0
  22. package/dist/engine/state/state.js +68 -0
  23. package/dist/engine/usage/fill.d.ts +9 -0
  24. package/dist/engine/usage/fill.js +9 -0
  25. package/dist/engine/usage/lines.d.ts +9 -0
  26. package/dist/engine/usage/lines.js +9 -0
  27. package/dist/engine/usage/overlays.d.ts +9 -0
  28. package/dist/engine/usage/overlays.js +9 -0
  29. package/dist/engine/usage/strategy.d.ts +56 -0
  30. package/dist/engine/usage/strategy.js +30 -0
  31. package/dist/engine/usage/visualVocabulary.d.ts +9 -0
  32. package/dist/engine/usage/visualVocabulary.js +9 -0
  33. package/dist/export/serialize.d.ts +14 -0
  34. package/dist/export/serialize.js +89 -0
  35. package/dist/export/types.d.ts +37 -0
  36. package/dist/export/types.js +31 -0
  37. package/dist/index.d.ts +3 -3
  38. package/dist/index.js +2 -2
  39. package/dist/operators/convert.d.ts +32 -0
  40. package/dist/operators/convert.js +80 -0
  41. package/dist/presets/presets.d.ts +95 -0
  42. package/dist/presets/presets.js +308 -0
  43. package/dist/types/index.d.ts +111 -187
  44. package/dist/utils/errors/errors.d.ts +17 -0
  45. package/dist/utils/errors/errors.js +22 -0
  46. package/docs/API.md +167 -0
  47. package/docs/Alpha.md +14 -0
  48. package/docs/Architecture.md +56 -0
  49. package/docs/CLI.md +22 -0
  50. package/docs/Concepts.md +73 -0
  51. package/docs/Config.md +144 -0
  52. package/docs/Diagnostics.md +22 -0
  53. package/docs/Exporters.md +33 -0
  54. package/docs/FAQ.md +59 -0
  55. package/docs/Migration.md +61 -0
  56. package/docs/Overlays.md +33 -0
  57. package/docs/README.md +60 -0
  58. package/docs/Text.md +41 -0
  59. package/docs/Tokens.md +42 -0
  60. package/docs/Usage-JSON.md +39 -0
  61. package/docs/Usage-ReactNative.md +63 -0
  62. package/docs/Usage-Web.md +66 -0
  63. package/docs/Validation.md +97 -0
  64. package/docs/Why.md +37 -0
  65. package/docs/_api-surface.md +53 -0
  66. package/docs/snippets/serialize-oklch.md +9 -0
  67. package/docs/spec.md +98 -0
  68. package/package.json +74 -59
  69. package/.codex/skills/color-pipeline-implementer/SKILL.md +0 -23
  70. package/.codex/skills/commit-message-crafter/SKILL.md +0 -63
  71. package/.codex/skills/commit-message-crafter/references/benchmarks.md +0 -20
  72. package/.codex/skills/contrast-solver-helper/SKILL.md +0 -20
  73. package/.codex/skills/exporters-builder/SKILL.md +0 -20
  74. package/.codex/skills/markdownlint-writer/SKILL.md +0 -32
  75. package/.codex/skills/phase-implementation-runbook/SKILL.md +0 -92
  76. package/.codex/skills/type-contract-auditor/SKILL.md +0 -21
  77. package/.github/skills/review-guide/SKILL.md +0 -23
  78. package/.github/skills/review-guide/references/review-guide-v0.3.md +0 -629
  79. package/.markdownlint.json +0 -4
  80. package/AGENTS.md +0 -16
  81. package/biome.json +0 -43
  82. package/dist/cli/args.d.ts +0 -12
  83. package/dist/cli/args.js +0 -56
  84. package/dist/cli/args.test.d.ts +0 -1
  85. package/dist/cli/args.test.js +0 -22
  86. package/dist/cli/codegen/__snapshots__/tokens.test.js.snap +0 -87
  87. package/dist/cli/codegen/tokens.d.ts +0 -12
  88. package/dist/cli/codegen/tokens.js +0 -139
  89. package/dist/cli/codegen/tokens.test.d.ts +0 -1
  90. package/dist/cli/codegen/tokens.test.js +0 -51
  91. package/dist/cli/config.d.ts +0 -40
  92. package/dist/cli/config.js +0 -34
  93. package/dist/cli/validate.d.ts +0 -2
  94. package/dist/cli/validate.js +0 -33
  95. package/dist/cli/validate.test.d.ts +0 -1
  96. package/dist/cli/validate.test.js +0 -40
  97. package/dist/cli.d.ts +0 -2
  98. package/dist/cli.js +0 -148
  99. package/dist/contrast/apca.d.ts +0 -2
  100. package/dist/contrast/apca.js +0 -15
  101. package/dist/contrast/apca.test.d.ts +0 -1
  102. package/dist/contrast/apca.test.js +0 -16
  103. package/dist/contrast/index.d.ts +0 -4
  104. package/dist/contrast/index.js +0 -4
  105. package/dist/contrast/scoring.d.ts +0 -4
  106. package/dist/contrast/scoring.js +0 -31
  107. package/dist/contrast/scoring.test.d.ts +0 -1
  108. package/dist/contrast/scoring.test.js +0 -148
  109. package/dist/contrast/solver.d.ts +0 -13
  110. package/dist/contrast/solver.js +0 -170
  111. package/dist/contrast/solver.test.d.ts +0 -1
  112. package/dist/contrast/solver.test.js +0 -75
  113. package/dist/contrast/types.d.ts +0 -17
  114. package/dist/contrast/types.js +0 -1
  115. package/dist/contrast/utils.d.ts +0 -4
  116. package/dist/contrast/utils.js +0 -18
  117. package/dist/contrast/wcag2.d.ts +0 -3
  118. package/dist/contrast/wcag2.js +0 -19
  119. package/dist/contrast/wcag2.test.d.ts +0 -1
  120. package/dist/contrast/wcag2.test.js +0 -17
  121. package/dist/core/createTheme.d.ts +0 -35
  122. package/dist/core/createTheme.js +0 -24
  123. package/dist/core/dx-helpers.test.d.ts +0 -1
  124. package/dist/core/dx-helpers.test.js +0 -61
  125. package/dist/core/index.d.ts +0 -2
  126. package/dist/core/index.js +0 -2
  127. package/dist/core/onSolid.test.d.ts +0 -1
  128. package/dist/core/onSolid.test.js +0 -118
  129. package/dist/core/qa.v1.test.d.ts +0 -1
  130. package/dist/core/qa.v1.test.js +0 -112
  131. package/dist/core/resolve.d.ts +0 -3
  132. package/dist/core/resolve.js +0 -8
  133. package/dist/core/resolve.test.d.ts +0 -1
  134. package/dist/core/resolve.test.js +0 -89
  135. package/dist/core/resolveMany.d.ts +0 -8
  136. package/dist/core/resolveMany.js +0 -17
  137. package/dist/core/tokenRegistry.d.ts +0 -23
  138. package/dist/core/tokenRegistry.js +0 -83
  139. package/dist/core/tokenRegistry.test.d.ts +0 -1
  140. package/dist/core/tokenRegistry.test.js +0 -133
  141. package/dist/engine/applyOperators.d.ts +0 -3
  142. package/dist/engine/applyOperators.js +0 -23
  143. package/dist/engine/context.d.ts +0 -4
  144. package/dist/engine/context.js +0 -1
  145. package/dist/engine/gamut.d.ts +0 -13
  146. package/dist/engine/gamut.js +0 -101
  147. package/dist/engine/gamut.test.d.ts +0 -1
  148. package/dist/engine/gamut.test.js +0 -23
  149. package/dist/engine/generateScale.d.ts +0 -15
  150. package/dist/engine/generateScale.js +0 -29
  151. package/dist/engine/generateScale.test.d.ts +0 -1
  152. package/dist/engine/generateScale.test.js +0 -32
  153. package/dist/engine/index.d.ts +0 -8
  154. package/dist/engine/index.js +0 -4
  155. package/dist/engine/normalize.d.ts +0 -43
  156. package/dist/engine/normalize.js +0 -403
  157. package/dist/engine/normalize.test.d.ts +0 -1
  158. package/dist/engine/normalize.test.js +0 -136
  159. package/dist/engine/onSolid.d.ts +0 -3
  160. package/dist/engine/onSolid.js +0 -110
  161. package/dist/engine/resolveBaseColor.d.ts +0 -25
  162. package/dist/engine/resolveBaseColor.js +0 -127
  163. package/dist/engine/resolveBaseColor.test.d.ts +0 -1
  164. package/dist/engine/resolveBaseColor.test.js +0 -97
  165. package/dist/export/__snapshots__/exportTheme.test.js.snap +0 -74
  166. package/dist/export/exportTheme.d.ts +0 -47
  167. package/dist/export/exportTheme.js +0 -170
  168. package/dist/export/exportTheme.test.d.ts +0 -1
  169. package/dist/export/exportTheme.test.js +0 -118
  170. package/dist/export/index.d.ts +0 -1
  171. package/dist/export/index.js +0 -1
  172. package/dist/export/serializeColor.d.ts +0 -1
  173. package/dist/export/serializeColor.js +0 -1
  174. package/dist/export/serializeColor.test.d.ts +0 -1
  175. package/dist/export/serializeColor.test.js +0 -54
  176. package/dist/export.d.ts +0 -1
  177. package/dist/export.js +0 -1
  178. package/dist/operators/emphasis.d.ts +0 -3
  179. package/dist/operators/emphasis.js +0 -113
  180. package/dist/operators/emphasis.test.d.ts +0 -1
  181. package/dist/operators/emphasis.test.js +0 -69
  182. package/dist/operators/index.d.ts +0 -3
  183. package/dist/operators/index.js +0 -2
  184. package/dist/operators/state.d.ts +0 -3
  185. package/dist/operators/state.js +0 -102
  186. package/dist/operators/state.test.d.ts +0 -1
  187. package/dist/operators/state.test.js +0 -48
  188. package/dist/operators/types.d.ts +0 -13
  189. package/dist/operators/types.js +0 -1
  190. package/dist/operators/utils.d.ts +0 -16
  191. package/dist/operators/utils.js +0 -23
  192. package/dist/presets/curves.d.ts +0 -28
  193. package/dist/presets/curves.js +0 -145
  194. package/dist/presets/index.d.ts +0 -2
  195. package/dist/presets/index.js +0 -1
  196. package/dist/presets/tokens/index.d.ts +0 -3
  197. package/dist/presets/tokens/index.js +0 -3
  198. package/dist/presets/tokens/minimal-ui.d.ts +0 -6
  199. package/dist/presets/tokens/minimal-ui.js +0 -53
  200. package/dist/presets/tokens/modern-ui.d.ts +0 -5
  201. package/dist/presets/tokens/modern-ui.js +0 -83
  202. package/dist/presets/tokens/presets.test.d.ts +0 -1
  203. package/dist/presets/tokens/presets.test.js +0 -31
  204. package/dist/presets/tokens/radixLike-ui.d.ts +0 -6
  205. package/dist/presets/tokens/radixLike-ui.js +0 -77
  206. package/dist/serialize/index.d.ts +0 -1
  207. package/dist/serialize/index.js +0 -1
  208. package/dist/serialize/normalizeOutput.d.ts +0 -6
  209. package/dist/serialize/normalizeOutput.js +0 -45
  210. package/dist/serialize/serializeColor.d.ts +0 -21
  211. package/dist/serialize/serializeColor.js +0 -178
  212. package/dist/serialize/serializeResolved.test.d.ts +0 -1
  213. package/dist/serialize/serializeResolved.test.js +0 -45
  214. package/dist/serialize.d.ts +0 -1
  215. package/dist/serialize.js +0 -1
  216. package/dist/utils/clamp.d.ts +0 -1
  217. package/dist/utils/clamp.js +0 -1
  218. package/dist/utils/index.d.ts +0 -1
  219. package/dist/utils/index.js +0 -1
  220. package/dist/utils/lerp.d.ts +0 -1
  221. package/dist/utils/lerp.js +0 -1
  222. package/dist/utils/parseColor.d.ts +0 -6
  223. package/dist/utils/parseColor.js +0 -67
  224. package/dist/utils/parseColor.test.d.ts +0 -1
  225. package/dist/utils/parseColor.test.js +0 -51
  226. package/dist/utils/smoothstep.d.ts +0 -1
  227. package/dist/utils/smoothstep.js +0 -5
  228. package/planning/phase-10-review.md +0 -550
  229. package/planning/phase-7-review.md +0 -411
  230. package/planning/phase-8-review.md +0 -669
  231. package/planning/phase-9-review.md +0 -564
  232. package/planning/roadmap-v0.3.md +0 -284
  233. package/planning/spec-serializer-v0.3.md +0 -324
  234. package/planning/spec-v0.3.md +0 -305
  235. package/src/cli/args.test.ts +0 -28
  236. package/src/cli/args.ts +0 -66
  237. package/src/cli/codegen/__snapshots__/tokens.test.ts.snap +0 -87
  238. package/src/cli/codegen/tokens.test.ts +0 -61
  239. package/src/cli/codegen/tokens.ts +0 -191
  240. package/src/cli/config.ts +0 -71
  241. package/src/cli/validate.test.ts +0 -49
  242. package/src/cli/validate.ts +0 -38
  243. package/src/cli.ts +0 -183
  244. package/src/contrast/apca.test.ts +0 -20
  245. package/src/contrast/apca.ts +0 -26
  246. package/src/contrast/index.ts +0 -4
  247. package/src/contrast/scoring.test.ts +0 -188
  248. package/src/contrast/scoring.ts +0 -48
  249. package/src/contrast/solver.test.ts +0 -147
  250. package/src/contrast/solver.ts +0 -235
  251. package/src/contrast/types.ts +0 -20
  252. package/src/contrast/utils.ts +0 -28
  253. package/src/contrast/wcag2.test.ts +0 -21
  254. package/src/contrast/wcag2.ts +0 -24
  255. package/src/core/createTheme.ts +0 -78
  256. package/src/core/dx-helpers.test.ts +0 -82
  257. package/src/core/index.ts +0 -7
  258. package/src/core/onSolid.test.ts +0 -146
  259. package/src/core/qa.v1.test.ts +0 -149
  260. package/src/core/resolve.test.ts +0 -99
  261. package/src/core/resolve.ts +0 -11
  262. package/src/core/resolveMany.ts +0 -22
  263. package/src/core/tokenRegistry.test.ts +0 -153
  264. package/src/core/tokenRegistry.ts +0 -114
  265. package/src/engine/applyOperators.ts +0 -32
  266. package/src/engine/context.ts +0 -8
  267. package/src/engine/gamut.test.ts +0 -30
  268. package/src/engine/gamut.ts +0 -144
  269. package/src/engine/generateScale.test.ts +0 -46
  270. package/src/engine/generateScale.ts +0 -48
  271. package/src/engine/index.ts +0 -8
  272. package/src/engine/normalize.test.ts +0 -222
  273. package/src/engine/normalize.ts +0 -550
  274. package/src/engine/onSolid.ts +0 -178
  275. package/src/engine/resolveBaseColor.test.ts +0 -117
  276. package/src/engine/resolveBaseColor.ts +0 -203
  277. package/src/export/__snapshots__/exportTheme.test.ts.snap +0 -74
  278. package/src/export/exportTheme.test.ts +0 -144
  279. package/src/export/exportTheme.ts +0 -251
  280. package/src/export/index.ts +0 -1
  281. package/src/export/serializeColor.test.ts +0 -73
  282. package/src/export/serializeColor.ts +0 -1
  283. package/src/export.ts +0 -1
  284. package/src/index.ts +0 -3
  285. package/src/operators/emphasis.test.ts +0 -85
  286. package/src/operators/emphasis.ts +0 -132
  287. package/src/operators/index.ts +0 -3
  288. package/src/operators/state.test.ts +0 -66
  289. package/src/operators/state.ts +0 -122
  290. package/src/operators/types.ts +0 -14
  291. package/src/operators/utils.ts +0 -44
  292. package/src/presets/curves.ts +0 -168
  293. package/src/presets/index.ts +0 -2
  294. package/src/presets/tokens/index.ts +0 -3
  295. package/src/presets/tokens/minimal-ui.ts +0 -55
  296. package/src/presets/tokens/modern-ui.ts +0 -85
  297. package/src/presets/tokens/presets.test.ts +0 -46
  298. package/src/presets/tokens/radixLike-ui.ts +0 -79
  299. package/src/serialize/index.ts +0 -1
  300. package/src/serialize/normalizeOutput.ts +0 -63
  301. package/src/serialize/serializeColor.ts +0 -260
  302. package/src/serialize/serializeResolved.test.ts +0 -57
  303. package/src/serialize.ts +0 -1
  304. package/src/types/index.ts +0 -207
  305. package/src/utils/clamp.ts +0 -2
  306. package/src/utils/index.ts +0 -1
  307. package/src/utils/lerp.ts +0 -1
  308. package/src/utils/parseColor.test.ts +0 -66
  309. package/src/utils/parseColor.ts +0 -87
  310. package/src/utils/smoothstep.ts +0 -6
  311. package/tsconfig.build.json +0 -11
  312. package/tsconfig.json +0 -15
@@ -1,178 +0,0 @@
1
- import { computeApcaLc } from "../contrast/apca.js";
2
- import { solveContrast } from "../contrast/solver.js";
3
- import { blendSrgb, toSrgbColor } from "../contrast/utils.js";
4
- import { contrastRatio } from "../contrast/wcag2.js";
5
- import type { ContrastRequirement, OnSolidQuery } from "../types/index.js";
6
- import { applyOperators } from "./applyOperators.js";
7
- import { mapColorContextToEngine } from "./context.js";
8
- import type { OkLchColor } from "./generateScale.js";
9
- import { normalizeOnSolidQuery, normalizeQuery } from "./normalize.js";
10
- import type { BaseResolvedColor, ThemeConfig } from "./resolveBaseColor.js";
11
- import { resolveBaseColor } from "./resolveBaseColor.js";
12
-
13
- const DEFAULT_SOLVE_EPSILON = 0.01;
14
-
15
- const nearWhite: OkLchColor = { l: 97, c: 0, h: 0, alpha: 1 };
16
- const nearBlack: OkLchColor = { l: 15, c: 0, h: 0, alpha: 1 };
17
-
18
- const defaultContrastForUsage = (usage: OnSolidQuery["usage"]): ContrastRequirement => ({
19
- model: "apca",
20
- targetLc: usage === "text" ? 75 : 60,
21
- });
22
-
23
- const validateFixedAlpha = (alpha: number) => {
24
- if (alpha < 0 || alpha > 1) {
25
- throw new Error("Fixed alpha must be between 0 and 1");
26
- }
27
-
28
- return alpha;
29
- };
30
-
31
- const resolveAlphaStrategy = (
32
- alpha: OnSolidQuery["alpha"] | undefined,
33
- usage: OnSolidQuery["usage"],
34
- strict: boolean,
35
- ): { mode: "none" } | { mode: "fixed"; alpha: number } => {
36
- if (!alpha) {
37
- return { mode: "fixed", alpha: usage === "text" ? 0.92 : 0.72 };
38
- }
39
-
40
- if (alpha.mode === "none") {
41
- return alpha;
42
- }
43
-
44
- if (alpha.mode === "fixed") {
45
- return { mode: "fixed", alpha: validateFixedAlpha(alpha.alpha) };
46
- }
47
-
48
- if (strict) {
49
- throw new Error('onSolid does not support alpha.mode "solveOnBackground"');
50
- }
51
-
52
- console.warn(
53
- 'onSolid does not support alpha.mode "solveOnBackground"; falling back to fixed defaults',
54
- );
55
- return { mode: "fixed", alpha: usage === "text" ? 0.92 : 0.72 };
56
- };
57
-
58
- const checkContrastWithAlpha = (
59
- fg: OkLchColor,
60
- bg: OkLchColor,
61
- req: ContrastRequirement,
62
- alpha: number,
63
- epsilon = DEFAULT_SOLVE_EPSILON,
64
- ): { pass: boolean; value: number } => {
65
- if (req.model === "none") {
66
- return { pass: true, value: 0 };
67
- }
68
-
69
- const fgSrgb = toSrgbColor(fg);
70
- const bgSrgb = toSrgbColor(bg);
71
-
72
- if (!fgSrgb || !bgSrgb) {
73
- return { pass: false, value: Number.NaN };
74
- }
75
-
76
- const composite = blendSrgb(fgSrgb, bgSrgb, alpha);
77
-
78
- if (req.model === "apca") {
79
- const value = Math.abs(computeApcaLc(composite, bgSrgb));
80
- const minTarget = req.minLc ?? req.targetLc;
81
- const maxTarget = req.maxLc ?? Number.POSITIVE_INFINITY;
82
- return {
83
- pass: Number.isFinite(value) && value >= minTarget - epsilon && value <= maxTarget + epsilon,
84
- value,
85
- };
86
- }
87
-
88
- const value = contrastRatio(composite, bgSrgb);
89
- return { pass: Number.isFinite(value) && value + epsilon >= req.minRatio, value };
90
- };
91
-
92
- export function onSolid(query: OnSolidQuery, theme: ThemeConfig): BaseResolvedColor {
93
- const normalized = normalizeOnSolidQuery(query);
94
- const contrastRequirement = normalized.contrast ?? defaultContrastForUsage(normalized.usage);
95
- const alphaStrategy = resolveAlphaStrategy(
96
- normalized.alpha,
97
- normalized.usage,
98
- normalized.output.strict,
99
- );
100
- const alpha = alphaStrategy.mode === "none" ? 1 : alphaStrategy.alpha;
101
-
102
- const bgNormalized = normalizeQuery({
103
- role: normalized.bgRole,
104
- usage: "bg",
105
- surface: "solid",
106
- context: normalized.context,
107
- state: normalized.state,
108
- emphasis: normalized.emphasis,
109
- });
110
- const bgBase = resolveBaseColor(bgNormalized, theme);
111
- // Apply state/emphasis operators to the background before onSolid solves.
112
- const bgResolved = applyOperators(bgBase, bgNormalized, theme);
113
- const bg = bgResolved.oklch;
114
-
115
- const baseFg = bg.l >= 50 ? nearBlack : nearWhite;
116
- const seedUsed = `oklch(${baseFg.l}% ${baseFg.c} ${baseFg.h})`;
117
-
118
- const solverContext = {
119
- preset: theme.preset,
120
- surface: bgNormalized.surface,
121
- context: mapColorContextToEngine(bgNormalized.context),
122
- };
123
-
124
- const solved = solveContrast({ ...baseFg, alpha }, bg, contrastRequirement, solverContext, {
125
- strict: normalized.output.strict,
126
- });
127
-
128
- let finalAlpha = alpha;
129
- let finalColor = solved.color;
130
- let finalCheck = checkContrastWithAlpha(
131
- finalColor,
132
- bg,
133
- contrastRequirement,
134
- finalAlpha,
135
- DEFAULT_SOLVE_EPSILON,
136
- );
137
-
138
- if (!finalCheck.pass && finalAlpha < 1) {
139
- finalAlpha = 1;
140
- const solvedOpaque = solveContrast(
141
- { ...baseFg, alpha: 1 },
142
- bg,
143
- contrastRequirement,
144
- solverContext,
145
- { strict: normalized.output.strict },
146
- );
147
-
148
- finalColor = solvedOpaque.color;
149
- finalCheck = checkContrastWithAlpha(
150
- finalColor,
151
- bg,
152
- contrastRequirement,
153
- finalAlpha,
154
- DEFAULT_SOLVE_EPSILON,
155
- );
156
- }
157
-
158
- if (!finalCheck.pass && normalized.output.strict) {
159
- const target =
160
- contrastRequirement.model === "apca"
161
- ? contrastRequirement.targetLc
162
- : contrastRequirement.model === "wcag2"
163
- ? contrastRequirement.minRatio
164
- : 0;
165
- throw new Error(
166
- `onSolid contrast failed (${contrastRequirement.model}) target=${
167
- target
168
- } value=${finalCheck.value}`,
169
- );
170
- }
171
-
172
- return {
173
- oklch: { ...finalColor, alpha: finalAlpha },
174
- step: 0,
175
- variantUsed: "onSolid",
176
- seedUsed,
177
- };
178
- }
@@ -1,117 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
-
3
- import { parseColor } from "../utils/parseColor.js";
4
- import type { ColorQuery } from "../types/index.js";
5
- import { normalizeQuery } from "./normalize.js";
6
- import { resolveBaseColor } from "./resolveBaseColor.js";
7
- import type { ThemeConfig } from "./resolveBaseColor.js";
8
-
9
- const themeConfig: ThemeConfig = {
10
- seeds: {
11
- light: { neutral: "#8B8D98", accent: "#3D63DD" },
12
- dark: { neutral: "#8B8D98", accent: "#3D63DD" },
13
- },
14
- variants: {
15
- "category:food": "#C2410C",
16
- },
17
- preset: "modern",
18
- };
19
-
20
- describe("resolveBaseColor", () => {
21
- const r = (query: ColorQuery) => resolveBaseColor(normalizeQuery(query), themeConfig);
22
-
23
- it("defaults action roles to accent when variant is missing", () => {
24
- const result = r({
25
- role: "action.primary",
26
- usage: "bg",
27
- surface: "solid",
28
- context: "light",
29
- });
30
-
31
- expect(result.variantUsed).toBe("accent");
32
- expect(result.seedUsed).toBe(themeConfig.seeds.light.accent);
33
- });
34
-
35
- it("defaults text roles to neutral when variant is missing", () => {
36
- const result = r({
37
- role: "text.primary",
38
- usage: "text",
39
- surface: "surface",
40
- context: "light",
41
- });
42
-
43
- expect(result.variantUsed).toBe("neutral");
44
- expect(result.seedUsed).toBe(themeConfig.seeds.light.neutral);
45
- });
46
-
47
- it("uses custom category variants when provided", () => {
48
- const result = r({
49
- role: "bg.category",
50
- variant: "category:food",
51
- usage: "bg",
52
- surface: "surface",
53
- context: "light",
54
- });
55
-
56
- expect(result.variantUsed).toBe("category:food");
57
- expect(result.seedUsed).toBe(themeConfig.variants?.["category:food"]);
58
- });
59
-
60
- it("falls back to accent for missing category variants", () => {
61
- const result = r({
62
- role: "bg.category",
63
- variant: "category:missing",
64
- usage: "bg",
65
- surface: "surface",
66
- context: "light",
67
- });
68
-
69
- expect(result.variantUsed).toBe("accent");
70
- expect(result.seedUsed).toBe(themeConfig.seeds.light.accent);
71
- });
72
-
73
- it("chooses steps based on usage and surface", () => {
74
- const appBg = r({ role: "bg.app", usage: "bg", surface: "app", context: "light" });
75
- const solidBg = r({ role: "bg.solid", usage: "bg", surface: "solid", context: "light" });
76
- const borderSurface = r({
77
- role: "border.surface",
78
- usage: "border",
79
- surface: "surface",
80
- context: "light",
81
- });
82
- const ring = r({ role: "ring.focus", usage: "ring", surface: "surface", context: "light" });
83
-
84
- expect(appBg.step).toBe(1);
85
- expect(solidBg.step).toBe(9);
86
- expect(borderSurface.step).toBe(6);
87
- expect(ring.step).toBe(8);
88
- });
89
-
90
- it("returns valid OKLCH values with stable hue", () => {
91
- const result = r({
92
- role: "action.primary",
93
- usage: "bg",
94
- surface: "solid",
95
- context: "light",
96
- });
97
- const seed = parseColor(themeConfig.seeds.light.accent);
98
-
99
- expect(result.oklch.l).toBeGreaterThanOrEqual(0);
100
- expect(result.oklch.l).toBeLessThanOrEqual(100);
101
- expect(result.oklch.c).toBeGreaterThanOrEqual(0);
102
- expect(result.oklch.h).toBeCloseTo(seed.okLch.channels[2], 6);
103
- });
104
-
105
- it("accepts normalized queries without re-normalizing", () => {
106
- const normalized = normalizeQuery({
107
- role: "text.secondary",
108
- usage: "text",
109
- surface: "surface",
110
- context: "dark",
111
- });
112
-
113
- const result = resolveBaseColor(normalized, themeConfig);
114
-
115
- expect(result.step).toBe(11);
116
- });
117
- });
@@ -1,203 +0,0 @@
1
- import type { CurvePresetName } from "../presets/index.js";
2
- import type {
3
- ColorQuery,
4
- ColorUsage,
5
- CssColorString,
6
- SemanticVariant,
7
- SurfaceIntent,
8
- } from "../types/index.js";
9
- import { parseColor } from "../utils/parseColor.js";
10
- import { mapColorContextToEngine } from "./context.js";
11
- import { generateScale, type OkLchColor } from "./generateScale.js";
12
- import type { NormalizedQuery } from "./normalize.js";
13
- import { normalizeQuery } from "./normalize.js";
14
-
15
- type ThemeSeeds = {
16
- neutral: CssColorString;
17
- accent: CssColorString;
18
- };
19
-
20
- export type ThemeConfig = {
21
- seeds: {
22
- light: ThemeSeeds;
23
- dark: ThemeSeeds;
24
- };
25
- variants?: Record<string, CssColorString>;
26
- preset?: CurvePresetName;
27
- };
28
-
29
- type VariantResolution = {
30
- variantUsed: string;
31
- seedUsed: CssColorString;
32
- };
33
-
34
- const isCategoryVariant = (variant: string) => variant.startsWith("category:");
35
- const isChartVariant = (variant: string) => variant.startsWith("chart:");
36
-
37
- const aliasVariants = new Set<SemanticVariant>(["success", "warning", "danger", "info"]);
38
-
39
- const inferVariantFromRole = (role: string): "neutral" | "accent" => {
40
- const normalizedRole = role.toLowerCase();
41
-
42
- if (normalizedRole.startsWith("action.")) {
43
- return "accent";
44
- }
45
-
46
- if (
47
- normalizedRole.startsWith("bg.") ||
48
- normalizedRole.startsWith("surface.") ||
49
- normalizedRole.startsWith("border.") ||
50
- normalizedRole.startsWith("text.")
51
- ) {
52
- return "neutral";
53
- }
54
-
55
- return "neutral";
56
- };
57
-
58
- const resolveVariantSeed = (
59
- variant: SemanticVariant | undefined,
60
- role: string,
61
- config: ThemeConfig,
62
- contextKey: "light" | "dark",
63
- ): VariantResolution => {
64
- if (variant) {
65
- if (variant === "neutral" || variant === "accent") {
66
- return {
67
- variantUsed: variant,
68
- seedUsed: config.seeds[contextKey][variant],
69
- };
70
- }
71
-
72
- if (isCategoryVariant(variant) || isChartVariant(variant)) {
73
- const customSeed = config.variants?.[variant];
74
- if (customSeed) {
75
- return { variantUsed: variant, seedUsed: customSeed };
76
- }
77
-
78
- return { variantUsed: "accent", seedUsed: config.seeds[contextKey].accent };
79
- }
80
-
81
- if (aliasVariants.has(variant)) {
82
- return { variantUsed: "accent", seedUsed: config.seeds[contextKey].accent };
83
- }
84
-
85
- return { variantUsed: "accent", seedUsed: config.seeds[contextKey].accent };
86
- }
87
-
88
- const inferredVariant = inferVariantFromRole(role);
89
-
90
- return {
91
- variantUsed: inferredVariant,
92
- seedUsed: config.seeds[contextKey][inferredVariant],
93
- };
94
- };
95
-
96
- const clampStep = (value: number) => Math.min(12, Math.max(1, value));
97
-
98
- const resolveStep = (usage: ColorUsage, surface: SurfaceIntent): number => {
99
- switch (usage) {
100
- case "bg": {
101
- switch (surface) {
102
- case "app":
103
- return 1;
104
- case "surface":
105
- return 2;
106
- case "subtle":
107
- return 3;
108
- case "solid":
109
- return 9;
110
- case "overlay":
111
- return 2;
112
- case "data":
113
- return 9;
114
- case "transparent":
115
- return 1;
116
- default:
117
- return 2;
118
- }
119
- }
120
- case "border": {
121
- switch (surface) {
122
- case "solid":
123
- case "data":
124
- return 8;
125
- default:
126
- return 6;
127
- }
128
- }
129
- case "text":
130
- return 11;
131
- case "icon":
132
- return 11;
133
- case "ring":
134
- return 8;
135
- case "stroke":
136
- return surface === "data" ? 9 : 8;
137
- case "fill":
138
- return 9;
139
- default:
140
- return 6;
141
- }
142
- };
143
-
144
- export type BaseResolvedColor = {
145
- oklch: OkLchColor;
146
- step: number;
147
- variantUsed: string;
148
- seedUsed: CssColorString;
149
- };
150
-
151
- // Best-effort guard: ColorQuery may include state/emphasis already; a branded flag
152
- // from normalizeQuery would be the strict approach in a future version.
153
- const isNormalizedQuery = (value: ColorQuery | NormalizedQuery): value is NormalizedQuery =>
154
- typeof value === "object" &&
155
- value !== null &&
156
- typeof value.role === "string" &&
157
- typeof value.usage === "string" &&
158
- typeof value.surface === "string" &&
159
- typeof value.context === "string" &&
160
- typeof value.state === "string" &&
161
- typeof value.emphasis === "string";
162
-
163
- export function resolveBaseColor(query: ColorQuery, theme: ThemeConfig): BaseResolvedColor;
164
- export function resolveBaseColor(
165
- normalized: NormalizedQuery,
166
- theme: ThemeConfig,
167
- ): BaseResolvedColor;
168
- export function resolveBaseColor(
169
- query: ColorQuery | NormalizedQuery,
170
- theme: ThemeConfig,
171
- ): BaseResolvedColor {
172
- const normalized = isNormalizedQuery(query) ? query : normalizeQuery(query);
173
- const contextKey = mapColorContextToEngine(normalized.context);
174
- const { variantUsed, seedUsed } = resolveVariantSeed(
175
- normalized.variant,
176
- normalized.role,
177
- theme,
178
- contextKey,
179
- );
180
-
181
- const parsedSeed = parseColor(seedUsed);
182
- const seed: OkLchColor = {
183
- l: parsedSeed.okLch.channels[0] * 100,
184
- c: parsedSeed.okLch.channels[1],
185
- h: parsedSeed.okLch.channels[2],
186
- alpha: parsedSeed.okLch.alpha,
187
- };
188
-
189
- const scale = generateScale(seed, {
190
- context: contextKey,
191
- surface: normalized.surface,
192
- preset: theme.preset,
193
- });
194
- const step = clampStep(resolveStep(normalized.usage, normalized.surface));
195
- const oklch = scale[step - 1];
196
-
197
- return {
198
- oklch,
199
- step,
200
- variantUsed,
201
- seedUsed,
202
- };
203
- }
@@ -1,74 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`exportTheme > exports deterministic CSS with @supports overrides 1`] = `
4
- ":root {
5
- --pk-bg-app: #dddee1;
6
- --pk-custom-alias: #f1f4fb;
7
- --pk-text-primary: #f1f4fb;
8
- }
9
-
10
- @supports (color: oklch(0% 0 0)) {
11
- :root {
12
- --pk-bg-app: oklch(90% 0.004 264.7);
13
- --pk-bg-app-oklch: oklch(90% 0.004 264.7);
14
- --pk-custom-alias: oklch(96.7% 0.01 264.7);
15
- --pk-custom-alias-oklch: oklch(96.7% 0.01 264.7);
16
- --pk-text-primary: oklch(96.7% 0.01 264.7);
17
- --pk-text-primary-oklch: oklch(96.7% 0.01 264.7);
18
- }
19
- }
20
-
21
- @supports (color: color(display-p3 1 1 1)) {
22
- :root {
23
- --pk-bg-app-p3: color(display-p3 0.866 0.87 0.88);
24
- --pk-custom-alias-p3: color(display-p3 0.947 0.957 0.981);
25
- --pk-text-primary-p3: color(display-p3 0.947 0.957 0.981);
26
- }
27
- }
28
- "
29
- `;
30
-
31
- exports[`exportTheme > exports deterministic JSON with light/dark contexts 1`] = `
32
- {
33
- "dark": {
34
- "bg.app": {
35
- "alpha": 1,
36
- "oklch": "oklch(6% 0.004 264.7)",
37
- "srgb": "#010101",
38
- "value": "oklch(6% 0.004 264.7)",
39
- },
40
- "custom.alias": {
41
- "alpha": 1,
42
- "oklch": "oklch(27.5% 0.01 264.7)",
43
- "srgb": "#25282d",
44
- "value": "oklch(27.5% 0.01 264.7)",
45
- },
46
- "text.primary": {
47
- "alpha": 1,
48
- "oklch": "oklch(27.5% 0.01 264.7)",
49
- "srgb": "#25282d",
50
- "value": "oklch(27.5% 0.01 264.7)",
51
- },
52
- },
53
- "light": {
54
- "bg.app": {
55
- "alpha": 1,
56
- "oklch": "oklch(90% 0.004 264.7)",
57
- "srgb": "#dddee1",
58
- "value": "oklch(90% 0.004 264.7)",
59
- },
60
- "custom.alias": {
61
- "alpha": 1,
62
- "oklch": "oklch(96.7% 0.01 264.7)",
63
- "srgb": "#f1f4fb",
64
- "value": "oklch(96.7% 0.01 264.7)",
65
- },
66
- "text.primary": {
67
- "alpha": 1,
68
- "oklch": "oklch(96.7% 0.01 264.7)",
69
- "srgb": "#f1f4fb",
70
- "value": "oklch(96.7% 0.01 264.7)",
71
- },
72
- },
73
- }
74
- `;