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