@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,4 +0,0 @@
1
- import type { OkLchColor } from "../engine/generateScale.js";
2
- import type { SrgbColor } from "./types.js";
3
- export declare const toSrgbColor: (color: OkLchColor) => SrgbColor | null;
4
- export declare const blendSrgb: (fg: SrgbColor, bg: SrgbColor, alpha: number) => SrgbColor;
@@ -1,18 +0,0 @@
1
- import { converter } from "culori";
2
- const toSrgb = converter("rgb");
3
- const clamp = (value, min, max) => Math.min(max, Math.max(min, value));
4
- export const toSrgbColor = (color) => {
5
- const rgb = toSrgb({ mode: "oklch", l: clamp(color.l, 0, 100) / 100, c: color.c, h: color.h });
6
- if (!rgb) {
7
- return null;
8
- }
9
- const r = typeof rgb.r === "number" && Number.isFinite(rgb.r) ? clamp(rgb.r, 0, 1) : 0;
10
- const g = typeof rgb.g === "number" && Number.isFinite(rgb.g) ? clamp(rgb.g, 0, 1) : 0;
11
- const b = typeof rgb.b === "number" && Number.isFinite(rgb.b) ? clamp(rgb.b, 0, 1) : 0;
12
- return { r, g, b };
13
- };
14
- export const blendSrgb = (fg, bg, alpha) => ({
15
- r: fg.r * alpha + bg.r * (1 - alpha),
16
- g: fg.g * alpha + bg.g * (1 - alpha),
17
- b: fg.b * alpha + bg.b * (1 - alpha),
18
- });
@@ -1,3 +0,0 @@
1
- import type { SrgbColor } from "./types.js";
2
- export declare const relativeLuminance: (color: SrgbColor) => number;
3
- export declare const contrastRatio: (fg: SrgbColor, bg: SrgbColor) => number;
@@ -1,19 +0,0 @@
1
- const clamp01 = (value) => Math.min(1, Math.max(0, value));
2
- const toLinear = (channel) => {
3
- const value = clamp01(channel);
4
- return value <= 0.04045 ? value / 12.92 : ((value + 0.055) / 1.055) ** 2.4;
5
- };
6
- export const relativeLuminance = (color) => {
7
- const r = toLinear(color.r);
8
- const g = toLinear(color.g);
9
- const b = toLinear(color.b);
10
- return 0.2126 * r + 0.7152 * g + 0.0722 * b;
11
- };
12
- export const contrastRatio = (fg, bg) => {
13
- const fgLum = relativeLuminance(fg);
14
- const bgLum = relativeLuminance(bg);
15
- const lighter = Math.max(fgLum, bgLum);
16
- const darker = Math.min(fgLum, bgLum);
17
- const ratio = (lighter + 0.05) / (darker + 0.05);
18
- return Number.isFinite(ratio) ? ratio : Number.NaN;
19
- };
@@ -1 +0,0 @@
1
- export {};
@@ -1,17 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { contrastRatio } from "./wcag2.js";
3
- describe("wcag2 contrastRatio", () => {
4
- it("returns high contrast for black on white", () => {
5
- const ratio = contrastRatio({ r: 0, g: 0, b: 0 }, { r: 1, g: 1, b: 1 });
6
- expect(ratio).toBeGreaterThan(10);
7
- });
8
- it("returns lower contrast for mid gray on white", () => {
9
- const gray = 0x77 / 255;
10
- const ratio = contrastRatio({ r: gray, g: gray, b: gray }, { r: 1, g: 1, b: 1 });
11
- expect(ratio).toBeLessThan(4.5);
12
- });
13
- it("does not return NaN", () => {
14
- const ratio = contrastRatio({ r: 0.2, g: 0.4, b: 0.6 }, { r: 0.8, g: 0.7, b: 0.1 });
15
- expect(Number.isNaN(ratio)).toBe(false);
16
- });
17
- });
@@ -1,35 +0,0 @@
1
- import type { BaseResolvedColor, ThemeConfig } from "../engine/resolveBaseColor.js";
2
- import type { ColorContext, ColorQuery, ColorRole, OnSolidQuery, OutputOptions, ResolvedColor } from "../types/index.js";
3
- export type PaletteTheme = {
4
- /**
5
- * Resolve a single color query to the core OKLCH output shape.
6
- */
7
- resolve: (query: ColorQuery) => BaseResolvedColor;
8
- /**
9
- * Resolve a batch of color queries while preserving input order.
10
- */
11
- resolveMany: (queries: ColorQuery[]) => BaseResolvedColor[];
12
- /**
13
- * Resolve a color role with inference and DX validation.
14
- *
15
- * Inference and strict/non-strict behavior is shared with `theme.resolve(...)` and is
16
- * implemented inside query normalization.
17
- *
18
- * When `output.strict` is true, missing inference throws an error; otherwise
19
- * safe defaults are used.
20
- */
21
- color: (role: ColorRole, options?: Omit<ColorQuery, "role">) => BaseResolvedColor;
22
- /**
23
- * Resolve a foreground color against a solid background (APCA/WCAG aware).
24
- */
25
- onSolid: (query: OnSolidQuery) => BaseResolvedColor;
26
- /**
27
- * Serialize a resolved color query for external outputs (CSS, RN, JSON, etc.).
28
- */
29
- serialize: (query: ColorQuery, options?: OutputOptions) => ResolvedColor;
30
- /**
31
- * Return a new theme instance with a bound context.
32
- */
33
- withContext: (context: ColorContext) => PaletteTheme;
34
- };
35
- export declare function createTheme(config: ThemeConfig): PaletteTheme;
@@ -1,24 +0,0 @@
1
- import { onSolid } from "../engine/onSolid.js";
2
- import { serializeResolved } from "../serialize/serializeColor.js";
3
- import { resolve } from "./resolve.js";
4
- import { resolveMany } from "./resolveMany.js";
5
- export function createTheme(config) {
6
- const themeConfig = {
7
- ...config,
8
- preset: config.preset ?? "modern",
9
- variants: config.variants ?? {},
10
- };
11
- const applyBoundContext = (query, boundContext) => (boundContext ? { context: boundContext, ...query } : query);
12
- const buildTheme = (boundContext) => ({
13
- resolve: (query) => resolve(applyBoundContext(query, boundContext), themeConfig),
14
- resolveMany: (queries) => resolveMany(queries.map((query) => applyBoundContext(query, boundContext)), themeConfig),
15
- color: (role, options) => resolve(applyBoundContext({ role, ...(options ?? {}) }, boundContext), themeConfig),
16
- onSolid: (query) => onSolid(applyBoundContext(query, boundContext), themeConfig),
17
- serialize: (query, options) => {
18
- const resolved = resolve(applyBoundContext(query, boundContext), themeConfig);
19
- return serializeResolved(resolved, options);
20
- },
21
- withContext: (context) => buildTheme(context),
22
- });
23
- return buildTheme();
24
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,61 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createTheme } from "./createTheme.js";
3
- describe("Phase 2 helpers", () => {
4
- const theme = createTheme({
5
- seeds: {
6
- light: { neutral: "#8B8D98", accent: "#3D63DD" },
7
- dark: { neutral: "#8B8D98", accent: "#3D63DD" },
8
- },
9
- preset: "modern",
10
- });
11
- it("resolveMany preserves input order", () => {
12
- const queries = [
13
- { role: "bg.app", usage: "bg", surface: "app", context: "light" },
14
- { role: "text.primary", usage: "text", surface: "surface", context: "light" },
15
- ];
16
- const expected = queries.map((query) => theme.resolve(query));
17
- const results = theme.resolveMany(queries);
18
- expect(results.map((result) => result.step)).toEqual(expected.map((result) => result.step));
19
- expect(results.map((result) => result.seedUsed)).toEqual(expected.map((result) => result.seedUsed));
20
- });
21
- it("withContext applies bound context and allows overrides", () => {
22
- const darkTheme = theme.withContext("dark");
23
- const bound = darkTheme.resolve({ role: "bg.app", usage: "bg", surface: "app" });
24
- const explicit = {
25
- role: "bg.app",
26
- usage: "bg",
27
- surface: "app",
28
- context: "dark",
29
- };
30
- const explicitResolved = theme.resolve(explicit);
31
- expect(bound.step).toBe(explicitResolved.step);
32
- const override = darkTheme.resolve({
33
- role: "bg.app",
34
- usage: "bg",
35
- surface: "app",
36
- context: "light",
37
- });
38
- const light = {
39
- role: "bg.app",
40
- usage: "bg",
41
- surface: "app",
42
- context: "light",
43
- };
44
- const lightResolved = theme.resolve(light);
45
- expect(override.step).toBe(lightResolved.step);
46
- });
47
- it("theme.color infers usage and surface from role", () => {
48
- const inferred = theme.color("bg.app");
49
- const explicit = theme.resolve({ role: "bg.app", usage: "bg", surface: "app" });
50
- expect(inferred.step).toBe(explicit.step);
51
- expect(inferred.seedUsed).toBe(explicit.seedUsed);
52
- });
53
- it("theme.color throws in strict mode when inference is missing", () => {
54
- expect(() => theme.color("custom.role", {
55
- output: { strict: true },
56
- })).toThrow(/Usage is required for role/i);
57
- expect(() => theme.color("text.custom", {
58
- output: { strict: true },
59
- })).toThrow(/Surface is required for role/i);
60
- });
61
- });
@@ -1,2 +0,0 @@
1
- export { createTheme } from "./createTheme.js";
2
- export { resolveToken, resolveTokenRegistry, validateTokenDefinition, validateTokenRegistry, } from "./tokenRegistry.js";
@@ -1,2 +0,0 @@
1
- export { createTheme } from "./createTheme.js";
2
- export { resolveToken, resolveTokenRegistry, validateTokenDefinition, validateTokenRegistry, } from "./tokenRegistry.js";
@@ -1 +0,0 @@
1
- export {};
@@ -1,118 +0,0 @@
1
- import { describe, expect, it, vi } from "vitest";
2
- import * as apca from "../contrast/apca.js";
3
- import { blendSrgb, toSrgbColor } from "../contrast/utils.js";
4
- import * as operators from "../engine/applyOperators.js";
5
- import { createTheme } from "./createTheme.js";
6
- const passesApca = (fg, bg, targetLc, alpha = 1) => {
7
- const fgSrgb = toSrgbColor(fg);
8
- const bgSrgb = toSrgbColor(bg);
9
- if (!fgSrgb || !bgSrgb) {
10
- return false;
11
- }
12
- const composite = blendSrgb(fgSrgb, bgSrgb, alpha);
13
- const value = Math.abs(apca.computeApcaLc(composite, bgSrgb));
14
- return value >= targetLc;
15
- };
16
- const theme = createTheme({
17
- seeds: {
18
- light: { neutral: "#8B8D98", accent: "#3D63DD" },
19
- dark: { neutral: "#8B8D98", accent: "#3D63DD" },
20
- },
21
- preset: "modern",
22
- });
23
- describe("onSolid", () => {
24
- it("chooses a white-ish foreground on dark solids", () => {
25
- const bg = theme.resolve({
26
- role: "action.primary",
27
- usage: "bg",
28
- surface: "solid",
29
- context: "dark",
30
- });
31
- const result = theme.onSolid({
32
- bgRole: "action.primary",
33
- usage: "text",
34
- context: "dark",
35
- });
36
- expect(bg.oklch.l).toBeLessThan(50);
37
- expect(result.oklch.l).toBeGreaterThan(50);
38
- expect(passesApca(result.oklch, bg.oklch, 75, result.oklch.alpha ?? 1)).toBe(true);
39
- });
40
- it("chooses a black-ish foreground on light solids", () => {
41
- const bg = theme.resolve({
42
- role: "action.primary",
43
- usage: "bg",
44
- surface: "solid",
45
- context: "light",
46
- });
47
- const result = theme.onSolid({
48
- bgRole: "action.primary",
49
- usage: "icon",
50
- context: "light",
51
- });
52
- expect(bg.oklch.l).toBeGreaterThanOrEqual(50);
53
- expect(result.oklch.l).toBeLessThan(50);
54
- expect(passesApca(result.oklch, bg.oklch, 60, result.oklch.alpha ?? 1)).toBe(true);
55
- });
56
- it("raises alpha to 1 when needed for contrast", () => {
57
- const bg = theme.resolve({
58
- role: "action.primary",
59
- usage: "bg",
60
- surface: "solid",
61
- context: "dark",
62
- });
63
- const result = theme.onSolid({
64
- bgRole: "action.primary",
65
- usage: "icon",
66
- context: "dark",
67
- contrast: { model: "apca", targetLc: 90 },
68
- });
69
- expect(result.oklch.alpha).toBe(1);
70
- expect(passesApca(result.oklch, bg.oklch, 90, 1)).toBe(true);
71
- expect(passesApca(result.oklch, bg.oklch, 90, 0.72)).toBe(false);
72
- });
73
- it("throws in strict mode when contrast is impossible", () => {
74
- expect(() => theme.onSolid({
75
- bgRole: "action.primary",
76
- usage: "text",
77
- context: "light",
78
- contrast: { model: "apca", targetLc: 140 },
79
- output: { strict: true },
80
- })).toThrow(/onSolid contrast failed|Contrast solver failed/i);
81
- });
82
- it("accepts results within epsilon for strict checks", () => {
83
- const spy = vi.spyOn(apca, "computeApcaLc").mockImplementation(() => 74.995);
84
- try {
85
- expect(() => theme.onSolid({
86
- bgRole: "action.primary",
87
- usage: "text",
88
- context: "light",
89
- contrast: { model: "apca", targetLc: 75 },
90
- output: { strict: true },
91
- })).not.toThrow();
92
- }
93
- finally {
94
- spy.mockRestore();
95
- }
96
- });
97
- it("keeps alpha at 1 when mode is none", () => {
98
- const result = theme.onSolid({
99
- bgRole: "action.primary",
100
- usage: "text",
101
- context: "dark",
102
- alpha: { mode: "none" },
103
- });
104
- expect(result.oklch.alpha).toBe(1);
105
- });
106
- it("applies state and emphasis to the background before solving", () => {
107
- const spy = vi.spyOn(operators, "applyOperators");
108
- theme.onSolid({
109
- bgRole: "action.primary",
110
- usage: "text",
111
- context: "light",
112
- state: "hover",
113
- emphasis: "strong",
114
- });
115
- expect(spy).toHaveBeenCalledWith(expect.any(Object), expect.objectContaining({ state: "hover", emphasis: "strong" }), expect.any(Object));
116
- spy.mockRestore();
117
- });
118
- });
@@ -1 +0,0 @@
1
- export {};
@@ -1,112 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { serializeColor } from "../export/serializeColor.js";
3
- import { createTheme } from "./createTheme.js";
4
- const buildTheme = () => createTheme({
5
- seeds: {
6
- light: { neutral: "#111827", accent: "#3d63dd" },
7
- dark: { neutral: "#111827", accent: "#3d63dd" },
8
- },
9
- });
10
- const toOklchString = (oklch) => serializeColor(oklch, { preferSpace: "oklch", includeMeta: true });
11
- describe("QA v1", () => {
12
- it("resolves light/dark background without NaNs", () => {
13
- const theme = buildTheme();
14
- const light = theme.resolve({
15
- role: "bg.app",
16
- usage: "bg",
17
- context: "light",
18
- surface: "app",
19
- });
20
- const dark = theme.resolve({
21
- role: "bg.app",
22
- usage: "bg",
23
- context: "dark",
24
- surface: "app",
25
- });
26
- const lightSerialized = toOklchString(light.oklch);
27
- const darkSerialized = toOklchString(dark.oklch);
28
- expect(typeof lightSerialized.value).toBe("string");
29
- expect(lightSerialized.value.includes("oklch(")).toBe(true);
30
- expect(Number.isFinite(lightSerialized.alpha)).toBe(true);
31
- expect(lightSerialized.alpha).toBeGreaterThanOrEqual(0);
32
- expect(lightSerialized.alpha).toBeLessThanOrEqual(1);
33
- expect(typeof darkSerialized.value).toBe("string");
34
- expect(darkSerialized.value.includes("oklch(")).toBe(true);
35
- expect(Number.isFinite(darkSerialized.alpha)).toBe(true);
36
- expect(darkSerialized.alpha).toBeGreaterThanOrEqual(0);
37
- expect(darkSerialized.alpha).toBeLessThanOrEqual(1);
38
- });
39
- it("solves primary/secondary text on solid backgrounds", () => {
40
- const theme = buildTheme();
41
- const primary = theme.onSolid({
42
- bgRole: "action.primary",
43
- usage: "text",
44
- context: "light",
45
- contrast: { model: "apca", targetLc: 75 },
46
- });
47
- const secondary = theme.onSolid({
48
- bgRole: "action.primary",
49
- usage: "text",
50
- context: "light",
51
- contrast: { model: "apca", targetLc: 60 },
52
- });
53
- const primarySerialized = toOklchString(primary.oklch);
54
- const secondarySerialized = toOklchString(secondary.oklch);
55
- expect(primarySerialized.value).not.toBe("");
56
- expect(primarySerialized.alpha).toBeGreaterThanOrEqual(0);
57
- expect(primarySerialized.alpha).toBeLessThanOrEqual(1);
58
- expect(secondarySerialized.value).not.toBe("");
59
- expect(secondarySerialized.alpha).toBeGreaterThanOrEqual(0);
60
- expect(secondarySerialized.alpha).toBeLessThanOrEqual(1);
61
- });
62
- it("handles solid button states and onSolid text", () => {
63
- const theme = buildTheme();
64
- const base = {
65
- role: "action.primary",
66
- usage: "bg",
67
- surface: "solid",
68
- context: "light",
69
- };
70
- const states = ["default", "hover", "active"];
71
- const serializedStates = states.map((state) => toOklchString(theme.resolve({ ...base, state }).oklch).value);
72
- serializedStates.forEach((value) => {
73
- expect(value.startsWith("oklch(")).toBe(true);
74
- });
75
- const onSolidText = toOklchString(theme.onSolid({ bgRole: "action.primary", usage: "text", context: "light" }).oklch);
76
- expect(onSolidText.alpha).toBeGreaterThanOrEqual(0.85);
77
- expect(onSolidText.alpha).toBeLessThanOrEqual(1);
78
- });
79
- it("produces a distinct focus ring color with meta", () => {
80
- const theme = buildTheme();
81
- const ring = toOklchString(theme.resolve({
82
- role: "focus.ring",
83
- variant: "accent",
84
- usage: "ring",
85
- context: "light",
86
- surface: "surface",
87
- contrast: { model: "apca", targetLc: 45 },
88
- }).oklch);
89
- const surface = toOklchString(theme.resolve({
90
- role: "surface.card",
91
- usage: "bg",
92
- context: "light",
93
- surface: "surface",
94
- }).oklch);
95
- expect(ring.value).not.toBe(surface.value);
96
- expect(ring.meta?.gamutMapping).toBeDefined();
97
- });
98
- it("keeps overlay scrim alpha stable", () => {
99
- const theme = buildTheme();
100
- const scrim = toOklchString(theme.resolve({
101
- role: "overlay.scrim",
102
- usage: "bg",
103
- surface: "overlay",
104
- context: "dark",
105
- alpha: { mode: "fixed", alpha: 0.55 },
106
- }).oklch);
107
- expect(scrim.alpha).toBeGreaterThanOrEqual(0);
108
- expect(scrim.alpha).toBeLessThanOrEqual(1);
109
- expect(scrim.value.includes("oklch(")).toBe(true);
110
- expect(scrim.value.includes("NaN")).toBe(false);
111
- });
112
- });
@@ -1,3 +0,0 @@
1
- import type { BaseResolvedColor, ThemeConfig } from "../engine/resolveBaseColor.js";
2
- import type { ColorQuery } from "../types/index.js";
3
- export declare function resolve(query: ColorQuery, theme: ThemeConfig): BaseResolvedColor;
@@ -1,8 +0,0 @@
1
- import { applyOperators } from "../engine/applyOperators.js";
2
- import { normalizeQuery } from "../engine/normalize.js";
3
- import { resolveBaseColor } from "../engine/resolveBaseColor.js";
4
- export function resolve(query, theme) {
5
- const normalized = normalizeQuery(query);
6
- const base = resolveBaseColor(normalized, theme);
7
- return applyOperators(base, normalized, theme);
8
- }
@@ -1 +0,0 @@
1
- export {};
@@ -1,89 +0,0 @@
1
- import { describe, expect, it } from "vitest";
2
- import { createTheme } from "./createTheme.js";
3
- const theme = createTheme({
4
- seeds: {
5
- light: { neutral: "#8B8D98", accent: "#3D63DD" },
6
- dark: { neutral: "#8B8D98", accent: "#3D63DD" },
7
- },
8
- preset: "modern",
9
- });
10
- describe("resolve", () => {
11
- it("applies state operators via theme.resolve", () => {
12
- const baseLight = theme.resolve({
13
- role: "bg.solid",
14
- usage: "bg",
15
- surface: "solid",
16
- context: "light",
17
- });
18
- const hoverLight = theme.resolve({
19
- role: "bg.solid",
20
- usage: "bg",
21
- surface: "solid",
22
- context: "light",
23
- state: "hover",
24
- });
25
- const baseDark = theme.resolve({
26
- role: "bg.solid",
27
- usage: "bg",
28
- surface: "solid",
29
- context: "dark",
30
- });
31
- const hoverDark = theme.resolve({
32
- role: "bg.solid",
33
- usage: "bg",
34
- surface: "solid",
35
- context: "dark",
36
- state: "hover",
37
- });
38
- const activeDark = theme.resolve({
39
- role: "bg.solid",
40
- usage: "bg",
41
- surface: "solid",
42
- context: "dark",
43
- state: "active",
44
- });
45
- const activeLight = theme.resolve({
46
- role: "bg.solid",
47
- usage: "bg",
48
- surface: "solid",
49
- context: "light",
50
- state: "active",
51
- });
52
- const disabledLight = theme.resolve({
53
- role: "bg.solid",
54
- usage: "bg",
55
- surface: "solid",
56
- context: "light",
57
- state: "disabled",
58
- });
59
- const disabledDark = theme.resolve({
60
- role: "bg.solid",
61
- usage: "bg",
62
- surface: "solid",
63
- context: "dark",
64
- state: "disabled",
65
- });
66
- expect(hoverLight.oklch.l).toBeLessThan(baseLight.oklch.l);
67
- expect(hoverDark.oklch.l).toBeGreaterThan(baseDark.oklch.l);
68
- expect(Math.abs(activeLight.oklch.l - baseLight.oklch.l)).toBeGreaterThan(Math.abs(hoverLight.oklch.l - baseLight.oklch.l));
69
- expect(Math.abs(activeDark.oklch.l - baseDark.oklch.l)).toBeGreaterThan(Math.abs(hoverDark.oklch.l - baseDark.oklch.l));
70
- expect(disabledLight.oklch.c).toBeLessThan(baseLight.oklch.c * 0.6);
71
- expect(disabledDark.oklch.c).toBeLessThan(baseDark.oklch.c * 0.6);
72
- });
73
- it("applies emphasis via theme.resolve", () => {
74
- const base = theme.resolve({
75
- role: "text.primary",
76
- usage: "text",
77
- surface: "surface",
78
- context: "light",
79
- });
80
- const muted = theme.resolve({
81
- role: "text.primary",
82
- usage: "text",
83
- surface: "surface",
84
- context: "light",
85
- emphasis: "muted",
86
- });
87
- expect(muted.oklch.c).toBeLessThan(base.oklch.c);
88
- });
89
- });
@@ -1,8 +0,0 @@
1
- import type { BaseResolvedColor, ThemeConfig } from "../engine/resolveBaseColor.js";
2
- import type { ColorQuery } from "../types/index.js";
3
- /**
4
- * Resolve a batch of color queries while preserving input order.
5
- *
6
- * Convenience helper to resolve multiple color queries in a single call.
7
- */
8
- export declare function resolveMany(queries: ColorQuery[], theme: ThemeConfig): BaseResolvedColor[];
@@ -1,17 +0,0 @@
1
- import { applyOperators } from "../engine/applyOperators.js";
2
- import { normalizeQuery } from "../engine/normalize.js";
3
- import { resolveBaseColor } from "../engine/resolveBaseColor.js";
4
- /**
5
- * Resolve a batch of color queries while preserving input order.
6
- *
7
- * Convenience helper to resolve multiple color queries in a single call.
8
- */
9
- export function resolveMany(queries, theme) {
10
- const results = [];
11
- for (const query of queries) {
12
- const normalized = normalizeQuery(query);
13
- const base = resolveBaseColor(normalized, theme);
14
- results.push(applyOperators(base, normalized, theme));
15
- }
16
- return results;
17
- }
@@ -1,23 +0,0 @@
1
- import type { BaseResolvedColor } from "../engine/resolveBaseColor.js";
2
- import type { TokenDefinition, TokenRegistry } from "../types/index.js";
3
- import type { PaletteTheme } from "./createTheme.js";
4
- /**
5
- * Validate a token definition for safe registry usage.
6
- *
7
- * Guarantees (Phase 3):
8
- * - registry stays declarative (no output options / no embedded color literals)
9
- * - tokens are base tokens (interactive states declared via `token.states`)
10
- */
11
- export declare const validateTokenDefinition: (token: TokenDefinition) => void;
12
- /**
13
- * Validate a token registry and each token definition it contains.
14
- */
15
- export declare const validateTokenRegistry: (registry: TokenRegistry) => void;
16
- /**
17
- * Resolve a token definition through the provided theme.
18
- */
19
- export declare const resolveToken: (token: TokenDefinition, theme: PaletteTheme) => BaseResolvedColor;
20
- /**
21
- * Resolve all tokens in a registry while preserving key order.
22
- */
23
- export declare const resolveTokenRegistry: (registry: TokenRegistry, theme: PaletteTheme) => Record<string, BaseResolvedColor>;