@clhaas/palette-kit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -0
- package/README.md +80 -87
- package/dist/contrast/contrast.d.ts +16 -0
- package/dist/contrast/contrast.js +102 -0
- package/dist/core/intent-registry.d.ts +11 -0
- package/dist/core/intent-registry.js +70 -0
- package/dist/core/oklch.d.ts +16 -0
- package/dist/core/oklch.js +56 -0
- package/dist/create-palette-kit.d.ts +9 -0
- package/dist/create-palette-kit.js +67 -0
- package/dist/engine/context/context.d.ts +13 -0
- package/dist/engine/context/context.js +37 -0
- package/dist/engine/level/curves.d.ts +17 -0
- package/dist/engine/level/curves.js +49 -0
- package/dist/engine/level/level.d.ts +4 -0
- package/dist/engine/level/level.js +13 -0
- package/dist/engine/relation/relation.d.ts +105 -0
- package/dist/engine/relation/relation.js +137 -0
- package/dist/engine/resolve/resolve.d.ts +36 -0
- package/dist/engine/resolve/resolve.js +116 -0
- package/dist/engine/state/state.d.ts +46 -0
- package/dist/engine/state/state.js +68 -0
- package/dist/engine/usage/fill.d.ts +9 -0
- package/dist/engine/usage/fill.js +9 -0
- package/dist/engine/usage/lines.d.ts +9 -0
- package/dist/engine/usage/lines.js +9 -0
- package/dist/engine/usage/overlays.d.ts +9 -0
- package/dist/engine/usage/overlays.js +9 -0
- package/dist/engine/usage/strategy.d.ts +56 -0
- package/dist/engine/usage/strategy.js +30 -0
- package/dist/engine/usage/visualVocabulary.d.ts +9 -0
- package/dist/engine/usage/visualVocabulary.js +9 -0
- package/dist/export/serialize.d.ts +14 -0
- package/dist/export/serialize.js +89 -0
- package/dist/export/types.d.ts +37 -0
- package/dist/export/types.js +31 -0
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/operators/convert.d.ts +32 -0
- package/dist/operators/convert.js +80 -0
- package/dist/presets/presets.d.ts +95 -0
- package/dist/presets/presets.js +308 -0
- package/dist/types/index.d.ts +111 -187
- package/dist/utils/errors/errors.d.ts +17 -0
- package/dist/utils/errors/errors.js +22 -0
- package/docs/API.md +167 -0
- package/docs/Alpha.md +14 -0
- package/docs/Architecture.md +56 -0
- package/docs/CLI.md +22 -0
- package/docs/Concepts.md +73 -0
- package/docs/Config.md +144 -0
- package/docs/Diagnostics.md +22 -0
- package/docs/Exporters.md +33 -0
- package/docs/FAQ.md +59 -0
- package/docs/Migration.md +61 -0
- package/docs/Overlays.md +33 -0
- package/docs/README.md +60 -0
- package/docs/Text.md +41 -0
- package/docs/Tokens.md +42 -0
- package/docs/Usage-JSON.md +39 -0
- package/docs/Usage-ReactNative.md +63 -0
- package/docs/Usage-Web.md +66 -0
- package/docs/Validation.md +97 -0
- package/docs/Why.md +37 -0
- package/docs/_api-surface.md +53 -0
- package/docs/snippets/serialize-oklch.md +9 -0
- package/docs/spec.md +98 -0
- package/package.json +74 -59
- package/.codex/skills/color-pipeline-implementer/SKILL.md +0 -23
- package/.codex/skills/commit-message-crafter/SKILL.md +0 -63
- package/.codex/skills/commit-message-crafter/references/benchmarks.md +0 -20
- package/.codex/skills/contrast-solver-helper/SKILL.md +0 -20
- package/.codex/skills/exporters-builder/SKILL.md +0 -20
- package/.codex/skills/markdownlint-writer/SKILL.md +0 -32
- package/.codex/skills/phase-implementation-runbook/SKILL.md +0 -92
- package/.codex/skills/type-contract-auditor/SKILL.md +0 -21
- package/.github/skills/review-guide/SKILL.md +0 -23
- package/.github/skills/review-guide/references/review-guide-v0.3.md +0 -629
- package/.markdownlint.json +0 -4
- package/AGENTS.md +0 -16
- package/biome.json +0 -43
- package/dist/cli/args.d.ts +0 -12
- package/dist/cli/args.js +0 -56
- package/dist/cli/args.test.d.ts +0 -1
- package/dist/cli/args.test.js +0 -22
- package/dist/cli/codegen/__snapshots__/tokens.test.js.snap +0 -87
- package/dist/cli/codegen/tokens.d.ts +0 -12
- package/dist/cli/codegen/tokens.js +0 -139
- package/dist/cli/codegen/tokens.test.d.ts +0 -1
- package/dist/cli/codegen/tokens.test.js +0 -51
- package/dist/cli/config.d.ts +0 -40
- package/dist/cli/config.js +0 -34
- package/dist/cli/validate.d.ts +0 -2
- package/dist/cli/validate.js +0 -33
- package/dist/cli/validate.test.d.ts +0 -1
- package/dist/cli/validate.test.js +0 -40
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -148
- package/dist/contrast/apca.d.ts +0 -2
- package/dist/contrast/apca.js +0 -15
- package/dist/contrast/apca.test.d.ts +0 -1
- package/dist/contrast/apca.test.js +0 -16
- package/dist/contrast/index.d.ts +0 -4
- package/dist/contrast/index.js +0 -4
- package/dist/contrast/scoring.d.ts +0 -4
- package/dist/contrast/scoring.js +0 -31
- package/dist/contrast/scoring.test.d.ts +0 -1
- package/dist/contrast/scoring.test.js +0 -148
- package/dist/contrast/solver.d.ts +0 -13
- package/dist/contrast/solver.js +0 -170
- package/dist/contrast/solver.test.d.ts +0 -1
- package/dist/contrast/solver.test.js +0 -75
- package/dist/contrast/types.d.ts +0 -17
- package/dist/contrast/types.js +0 -1
- package/dist/contrast/utils.d.ts +0 -4
- package/dist/contrast/utils.js +0 -18
- package/dist/contrast/wcag2.d.ts +0 -3
- package/dist/contrast/wcag2.js +0 -19
- package/dist/contrast/wcag2.test.d.ts +0 -1
- package/dist/contrast/wcag2.test.js +0 -17
- package/dist/core/createTheme.d.ts +0 -35
- package/dist/core/createTheme.js +0 -24
- package/dist/core/dx-helpers.test.d.ts +0 -1
- package/dist/core/dx-helpers.test.js +0 -61
- package/dist/core/index.d.ts +0 -2
- package/dist/core/index.js +0 -2
- package/dist/core/onSolid.test.d.ts +0 -1
- package/dist/core/onSolid.test.js +0 -118
- package/dist/core/qa.v1.test.d.ts +0 -1
- package/dist/core/qa.v1.test.js +0 -112
- package/dist/core/resolve.d.ts +0 -3
- package/dist/core/resolve.js +0 -8
- package/dist/core/resolve.test.d.ts +0 -1
- package/dist/core/resolve.test.js +0 -89
- package/dist/core/resolveMany.d.ts +0 -8
- package/dist/core/resolveMany.js +0 -17
- package/dist/core/tokenRegistry.d.ts +0 -23
- package/dist/core/tokenRegistry.js +0 -83
- package/dist/core/tokenRegistry.test.d.ts +0 -1
- package/dist/core/tokenRegistry.test.js +0 -133
- package/dist/engine/applyOperators.d.ts +0 -3
- package/dist/engine/applyOperators.js +0 -23
- package/dist/engine/context.d.ts +0 -4
- package/dist/engine/context.js +0 -1
- package/dist/engine/gamut.d.ts +0 -13
- package/dist/engine/gamut.js +0 -101
- package/dist/engine/gamut.test.d.ts +0 -1
- package/dist/engine/gamut.test.js +0 -23
- package/dist/engine/generateScale.d.ts +0 -15
- package/dist/engine/generateScale.js +0 -29
- package/dist/engine/generateScale.test.d.ts +0 -1
- package/dist/engine/generateScale.test.js +0 -32
- package/dist/engine/index.d.ts +0 -8
- package/dist/engine/index.js +0 -4
- package/dist/engine/normalize.d.ts +0 -43
- package/dist/engine/normalize.js +0 -403
- package/dist/engine/normalize.test.d.ts +0 -1
- package/dist/engine/normalize.test.js +0 -136
- package/dist/engine/onSolid.d.ts +0 -3
- package/dist/engine/onSolid.js +0 -110
- package/dist/engine/resolveBaseColor.d.ts +0 -25
- package/dist/engine/resolveBaseColor.js +0 -127
- package/dist/engine/resolveBaseColor.test.d.ts +0 -1
- package/dist/engine/resolveBaseColor.test.js +0 -97
- package/dist/export/__snapshots__/exportTheme.test.js.snap +0 -74
- package/dist/export/exportTheme.d.ts +0 -47
- package/dist/export/exportTheme.js +0 -170
- package/dist/export/exportTheme.test.d.ts +0 -1
- package/dist/export/exportTheme.test.js +0 -118
- package/dist/export/index.d.ts +0 -1
- package/dist/export/index.js +0 -1
- package/dist/export/serializeColor.d.ts +0 -1
- package/dist/export/serializeColor.js +0 -1
- package/dist/export/serializeColor.test.d.ts +0 -1
- package/dist/export/serializeColor.test.js +0 -54
- package/dist/export.d.ts +0 -1
- package/dist/export.js +0 -1
- package/dist/operators/emphasis.d.ts +0 -3
- package/dist/operators/emphasis.js +0 -113
- package/dist/operators/emphasis.test.d.ts +0 -1
- package/dist/operators/emphasis.test.js +0 -69
- package/dist/operators/index.d.ts +0 -3
- package/dist/operators/index.js +0 -2
- package/dist/operators/state.d.ts +0 -3
- package/dist/operators/state.js +0 -102
- package/dist/operators/state.test.d.ts +0 -1
- package/dist/operators/state.test.js +0 -48
- package/dist/operators/types.d.ts +0 -13
- package/dist/operators/types.js +0 -1
- package/dist/operators/utils.d.ts +0 -16
- package/dist/operators/utils.js +0 -23
- package/dist/presets/curves.d.ts +0 -28
- package/dist/presets/curves.js +0 -145
- package/dist/presets/index.d.ts +0 -2
- package/dist/presets/index.js +0 -1
- package/dist/presets/tokens/index.d.ts +0 -3
- package/dist/presets/tokens/index.js +0 -3
- package/dist/presets/tokens/minimal-ui.d.ts +0 -6
- package/dist/presets/tokens/minimal-ui.js +0 -53
- package/dist/presets/tokens/modern-ui.d.ts +0 -5
- package/dist/presets/tokens/modern-ui.js +0 -83
- package/dist/presets/tokens/presets.test.d.ts +0 -1
- package/dist/presets/tokens/presets.test.js +0 -31
- package/dist/presets/tokens/radixLike-ui.d.ts +0 -6
- package/dist/presets/tokens/radixLike-ui.js +0 -77
- package/dist/serialize/index.d.ts +0 -1
- package/dist/serialize/index.js +0 -1
- package/dist/serialize/normalizeOutput.d.ts +0 -6
- package/dist/serialize/normalizeOutput.js +0 -45
- package/dist/serialize/serializeColor.d.ts +0 -21
- package/dist/serialize/serializeColor.js +0 -178
- package/dist/serialize/serializeResolved.test.d.ts +0 -1
- package/dist/serialize/serializeResolved.test.js +0 -45
- package/dist/serialize.d.ts +0 -1
- package/dist/serialize.js +0 -1
- package/dist/utils/clamp.d.ts +0 -1
- package/dist/utils/clamp.js +0 -1
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.js +0 -1
- package/dist/utils/lerp.d.ts +0 -1
- package/dist/utils/lerp.js +0 -1
- package/dist/utils/parseColor.d.ts +0 -6
- package/dist/utils/parseColor.js +0 -67
- package/dist/utils/parseColor.test.d.ts +0 -1
- package/dist/utils/parseColor.test.js +0 -51
- package/dist/utils/smoothstep.d.ts +0 -1
- package/dist/utils/smoothstep.js +0 -5
- package/planning/phase-10-review.md +0 -550
- package/planning/phase-7-review.md +0 -411
- package/planning/phase-8-review.md +0 -669
- package/planning/phase-9-review.md +0 -564
- package/planning/roadmap-v0.3.md +0 -284
- package/planning/spec-serializer-v0.3.md +0 -324
- package/planning/spec-v0.3.md +0 -305
- package/src/cli/args.test.ts +0 -28
- package/src/cli/args.ts +0 -66
- package/src/cli/codegen/__snapshots__/tokens.test.ts.snap +0 -87
- package/src/cli/codegen/tokens.test.ts +0 -61
- package/src/cli/codegen/tokens.ts +0 -191
- package/src/cli/config.ts +0 -71
- package/src/cli/validate.test.ts +0 -49
- package/src/cli/validate.ts +0 -38
- package/src/cli.ts +0 -183
- package/src/contrast/apca.test.ts +0 -20
- package/src/contrast/apca.ts +0 -26
- package/src/contrast/index.ts +0 -4
- package/src/contrast/scoring.test.ts +0 -188
- package/src/contrast/scoring.ts +0 -48
- package/src/contrast/solver.test.ts +0 -147
- package/src/contrast/solver.ts +0 -235
- package/src/contrast/types.ts +0 -20
- package/src/contrast/utils.ts +0 -28
- package/src/contrast/wcag2.test.ts +0 -21
- package/src/contrast/wcag2.ts +0 -24
- package/src/core/createTheme.ts +0 -78
- package/src/core/dx-helpers.test.ts +0 -82
- package/src/core/index.ts +0 -7
- package/src/core/onSolid.test.ts +0 -146
- package/src/core/qa.v1.test.ts +0 -149
- package/src/core/resolve.test.ts +0 -99
- package/src/core/resolve.ts +0 -11
- package/src/core/resolveMany.ts +0 -22
- package/src/core/tokenRegistry.test.ts +0 -153
- package/src/core/tokenRegistry.ts +0 -114
- package/src/engine/applyOperators.ts +0 -32
- package/src/engine/context.ts +0 -8
- package/src/engine/gamut.test.ts +0 -30
- package/src/engine/gamut.ts +0 -144
- package/src/engine/generateScale.test.ts +0 -46
- package/src/engine/generateScale.ts +0 -48
- package/src/engine/index.ts +0 -8
- package/src/engine/normalize.test.ts +0 -222
- package/src/engine/normalize.ts +0 -550
- package/src/engine/onSolid.ts +0 -178
- package/src/engine/resolveBaseColor.test.ts +0 -117
- package/src/engine/resolveBaseColor.ts +0 -203
- package/src/export/__snapshots__/exportTheme.test.ts.snap +0 -74
- package/src/export/exportTheme.test.ts +0 -144
- package/src/export/exportTheme.ts +0 -251
- package/src/export/index.ts +0 -1
- package/src/export/serializeColor.test.ts +0 -73
- package/src/export/serializeColor.ts +0 -1
- package/src/export.ts +0 -1
- package/src/index.ts +0 -3
- package/src/operators/emphasis.test.ts +0 -85
- package/src/operators/emphasis.ts +0 -132
- package/src/operators/index.ts +0 -3
- package/src/operators/state.test.ts +0 -66
- package/src/operators/state.ts +0 -122
- package/src/operators/types.ts +0 -14
- package/src/operators/utils.ts +0 -44
- package/src/presets/curves.ts +0 -168
- package/src/presets/index.ts +0 -2
- package/src/presets/tokens/index.ts +0 -3
- package/src/presets/tokens/minimal-ui.ts +0 -55
- package/src/presets/tokens/modern-ui.ts +0 -85
- package/src/presets/tokens/presets.test.ts +0 -46
- package/src/presets/tokens/radixLike-ui.ts +0 -79
- package/src/serialize/index.ts +0 -1
- package/src/serialize/normalizeOutput.ts +0 -63
- package/src/serialize/serializeColor.ts +0 -260
- package/src/serialize/serializeResolved.test.ts +0 -57
- package/src/serialize.ts +0 -1
- package/src/types/index.ts +0 -207
- package/src/utils/clamp.ts +0 -2
- package/src/utils/index.ts +0 -1
- package/src/utils/lerp.ts +0 -1
- package/src/utils/parseColor.test.ts +0 -66
- package/src/utils/parseColor.ts +0 -87
- package/src/utils/smoothstep.ts +0 -6
- package/tsconfig.build.json +0 -11
- package/tsconfig.json +0 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,30 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
<!-- markdownlint-disable MD024 -->
|
|
4
|
+
|
|
5
|
+
## v0.4.0
|
|
6
|
+
|
|
7
|
+
### Breaking changes
|
|
8
|
+
|
|
9
|
+
- Replaces the previous experimental `createTheme` API with `createPaletteKit`.
|
|
10
|
+
- Publishes only the package root export. CLI commands, token exporters, and
|
|
11
|
+
serializer subpaths are not part of v0.4.
|
|
12
|
+
- Resolver options are modeled as explicit axes: `usage`, `intent`, `level`,
|
|
13
|
+
relation, `state`, `context`, and `output`.
|
|
14
|
+
|
|
15
|
+
### Features
|
|
16
|
+
|
|
17
|
+
- Public `createPaletteKit` factory and `palette.resolve`.
|
|
18
|
+
- Public resolver presets: `soft`, `neutral`, and `strong`.
|
|
19
|
+
- Explicit `resolverConfig` overrides for level curves, state deltas, relation
|
|
20
|
+
parameters, and chroma limits.
|
|
21
|
+
- APCA contrast enforcement for `on` relations with a default Lc 60 target.
|
|
22
|
+
- Functional `over` and `under` overlay relations with configured alpha and
|
|
23
|
+
depth behavior.
|
|
24
|
+
- Supported outputs: `oklch`, `oklab`, `srgb`, `p3`, `hex`, and `rgba`.
|
|
25
|
+
- Strict public TypeScript option types for invalid usage/level/relation/state
|
|
26
|
+
combinations where possible.
|
|
27
|
+
|
|
3
28
|
## v0.3.0
|
|
4
29
|
|
|
5
30
|
### Breaking changes
|
package/README.md
CHANGED
|
@@ -1,127 +1,120 @@
|
|
|
1
1
|
# Palette Kit
|
|
2
2
|
|
|
3
|
-
Palette Kit is a
|
|
3
|
+
Palette Kit v0.4 is a deterministic OKLCH color resolution engine. It resolves
|
|
4
|
+
semantic color requests from explicit axes and serializes the result only after
|
|
5
|
+
resolution.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
7
|
-
```
|
|
9
|
+
```sh
|
|
8
10
|
npm install @clhaas/palette-kit
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Quick Start
|
|
12
14
|
|
|
13
15
|
```ts
|
|
14
|
-
import {
|
|
16
|
+
import { createPaletteKit } from "@clhaas/palette-kit";
|
|
15
17
|
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
18
|
+
const palette = createPaletteKit({
|
|
19
|
+
context: "light",
|
|
20
|
+
output: "oklch",
|
|
21
|
+
preset: "neutral",
|
|
22
|
+
intents: {
|
|
23
|
+
brand: { hue: 260, chroma: 0.14 },
|
|
24
|
+
neutral: { hue: 0, chroma: 0 },
|
|
20
25
|
},
|
|
21
|
-
preset: "modern",
|
|
22
26
|
});
|
|
23
27
|
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
context: "light",
|
|
28
|
+
const surface = palette.resolve({
|
|
29
|
+
usage: "fill",
|
|
30
|
+
intent: "neutral",
|
|
31
|
+
level: 2,
|
|
29
32
|
});
|
|
30
33
|
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
contrast: { model: "apca", targetLc: 75 },
|
|
34
|
+
const text = palette.resolve({
|
|
35
|
+
usage: "visualVocabulary",
|
|
36
|
+
intent: "brand",
|
|
37
|
+
on: surface,
|
|
36
38
|
});
|
|
37
39
|
```
|
|
38
40
|
|
|
39
|
-
##
|
|
41
|
+
## Public Runtime API
|
|
40
42
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
```ts
|
|
44
|
-
import { createTheme } from "@clhaas/palette-kit";
|
|
45
|
-
import { serializeResolved } from "@clhaas/palette-kit/serialize";
|
|
43
|
+
The package root exports:
|
|
46
44
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
});
|
|
45
|
+
- `createPaletteKit`
|
|
46
|
+
- `softResolverConfig`
|
|
47
|
+
- `neutralResolverConfig`
|
|
48
|
+
- `strongResolverConfig`
|
|
49
|
+
- `defaultResolverConfig`
|
|
53
50
|
|
|
54
|
-
|
|
55
|
-
const color = serializeResolved(resolved, { preferSpace: "srgb", srgbFormat: "hex" });
|
|
51
|
+
Public TypeScript types are also exported from the package root.
|
|
56
52
|
|
|
57
|
-
|
|
58
|
-
|
|
53
|
+
There are no public subpath exports, CLI commands, token exporters, or codegen
|
|
54
|
+
APIs in v0.4.
|
|
59
55
|
|
|
60
|
-
##
|
|
56
|
+
## Resolver Rules
|
|
61
57
|
|
|
62
|
-
|
|
58
|
+
| Usage | Level | Relations |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `fill` | Required | `on` optional |
|
|
61
|
+
| `visualVocabulary` | Forbidden | `on` required |
|
|
62
|
+
| `lines` | Required | `on` optional |
|
|
63
|
+
| `overlays` | Required | `over` or `under` optional |
|
|
63
64
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
`state` defaults to `"default"`. Non-default states require explicit
|
|
66
|
+
`stateDirection`; Palette Kit never infers whether state should increase or
|
|
67
|
+
decrease lightness.
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
},
|
|
73
|
-
});
|
|
69
|
+
Context is explicit. Provide palette-level `context`, resolver-level `context`,
|
|
70
|
+
or host-injected `systemDefaultContext`. Context affects default level curves;
|
|
71
|
+
for example, dark context inverts the structural lightness scale while
|
|
72
|
+
preserving intent hue and chroma.
|
|
74
73
|
|
|
75
|
-
|
|
76
|
-
"bg.app": { usage: "bg", surface: "app" },
|
|
77
|
-
"text.primary": { usage: "text", surface: "surface" },
|
|
78
|
-
};
|
|
74
|
+
## Output
|
|
79
75
|
|
|
80
|
-
|
|
81
|
-
const json = exportThemeJson(theme, tokens, { includeSpaces: ["srgb"] });
|
|
82
|
-
```
|
|
76
|
+
Supported outputs:
|
|
83
77
|
|
|
84
|
-
|
|
78
|
+
- `oklch`: normalized OKLCH object
|
|
79
|
+
- `oklab`: OKLab object
|
|
80
|
+
- `srgb`: `{ r, g, b, alpha }`
|
|
81
|
+
- `p3`: Display-P3 `{ r, g, b, alpha }`
|
|
82
|
+
- `hex`: `#rrggbb`
|
|
83
|
+
- `rgba`: `{ r, g, b, a }`
|
|
85
84
|
|
|
86
|
-
-
|
|
87
|
-
|
|
85
|
+
RGB-like outputs use clipped 8-bit channels. Output never changes semantic
|
|
86
|
+
resolution.
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
Output precedence is resolver-level `output`, then palette-level `output`, then
|
|
89
|
+
host-injected `systemDefaultOutput`, then the explicit `oklch` default.
|
|
90
90
|
|
|
91
|
-
##
|
|
91
|
+
## Configuration
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
- `docs/_api-surface.md`
|
|
95
|
-
- `docs/Exporters.md`
|
|
96
|
-
- `docs/CLI.md`
|
|
97
|
-
- `docs/Migration.md`
|
|
93
|
+
`createPaletteKit` accepts `preset` and explicit `resolverConfig` overrides.
|
|
98
94
|
|
|
99
|
-
|
|
95
|
+
```ts
|
|
96
|
+
const palette = createPaletteKit({
|
|
97
|
+
context: "light",
|
|
98
|
+
preset: "soft",
|
|
99
|
+
intents,
|
|
100
|
+
resolverConfig: {
|
|
101
|
+
relationParams: {
|
|
102
|
+
on: { contrastTarget: 75 },
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
});
|
|
106
|
+
```
|
|
100
107
|
|
|
101
|
-
|
|
102
|
-
- CJS is not supported in v0.3; use ESM or dynamic `import()` in CJS environments.
|
|
103
|
-
- Subpath imports:
|
|
104
|
-
- `@clhaas/palette-kit/serialize`
|
|
105
|
-
- `@clhaas/palette-kit/export`
|
|
106
|
-
- `@clhaas/palette-kit/cli`
|
|
107
|
-
- Tree-shaking: runtime imports (`@clhaas/palette-kit`) do not bundle build-time tools (exporters/CLI).
|
|
108
|
+
The default preset is `neutral`.
|
|
108
109
|
|
|
109
|
-
|
|
110
|
+
## Documentation
|
|
110
111
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
seeds: {
|
|
117
|
-
light: { neutral: "#111827", accent: "#3d63dd" },
|
|
118
|
-
dark: { neutral: "#111827", accent: "#3d63dd" },
|
|
119
|
-
},
|
|
120
|
-
});
|
|
121
|
-
console.log(theme);
|
|
122
|
-
})();
|
|
123
|
-
```
|
|
112
|
+
- [Docs index](docs/README.md)
|
|
113
|
+
- [API](docs/API.md)
|
|
114
|
+
- [Configuration](docs/Config.md)
|
|
115
|
+
- [Specification summary](docs/spec.md)
|
|
116
|
+
- [Migration](docs/Migration.md)
|
|
124
117
|
|
|
125
|
-
##
|
|
118
|
+
## Module Format
|
|
126
119
|
|
|
127
|
-
|
|
120
|
+
Palette Kit is ESM-only.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type OklchColor } from '../core/oklch.js';
|
|
2
|
+
import type { Context } from '../engine/context/context.js';
|
|
3
|
+
import type { ChromaConfig, RelationParamsConfig } from '../presets/presets.js';
|
|
4
|
+
export type ContrastResolutionConfig = Readonly<{
|
|
5
|
+
on: RelationParamsConfig['on'];
|
|
6
|
+
chromaLimits: ChromaConfig;
|
|
7
|
+
}>;
|
|
8
|
+
export type ContrastResolutionInput = Readonly<{
|
|
9
|
+
color: OklchColor;
|
|
10
|
+
target: OklchColor;
|
|
11
|
+
context: Context;
|
|
12
|
+
config: ContrastResolutionConfig;
|
|
13
|
+
}>;
|
|
14
|
+
export declare function measureApcaContrast(foreground: OklchColor, background: OklchColor): number;
|
|
15
|
+
export declare function measureWcagContrast(foreground: OklchColor, background: OklchColor): number;
|
|
16
|
+
export declare function resolveOnContrast({ color, config, context, target, }: ContrastResolutionInput): OklchColor;
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { APCAcontrast, sRGBtoY } from 'apca-w3';
|
|
2
|
+
import { normalizeOklch } from '../core/oklch.js';
|
|
3
|
+
import { serializeOklchToSrgb } from '../export/serialize.js';
|
|
4
|
+
import { createContrastUnsatisfiableError } from '../utils/errors/errors.js';
|
|
5
|
+
const LIGHTNESS_STEP = 0.5;
|
|
6
|
+
const CONTRAST_PRECISION = 2;
|
|
7
|
+
const clampLightness = (value) => Math.min(100, Math.max(0, value));
|
|
8
|
+
const roundContrast = (contrast) => Number(contrast.toFixed(CONTRAST_PRECISION));
|
|
9
|
+
export function measureApcaContrast(foreground, background) {
|
|
10
|
+
const foregroundRgb = serializeOklchToSrgb(foreground);
|
|
11
|
+
const backgroundRgb = serializeOklchToSrgb(background);
|
|
12
|
+
const contrast = APCAcontrast(sRGBtoY([foregroundRgb.r, foregroundRgb.g, foregroundRgb.b]), sRGBtoY([backgroundRgb.r, backgroundRgb.g, backgroundRgb.b]));
|
|
13
|
+
const numericContrast = typeof contrast === 'number' ? contrast : Number(contrast);
|
|
14
|
+
if (Number.isFinite(numericContrast)) {
|
|
15
|
+
return numericContrast;
|
|
16
|
+
}
|
|
17
|
+
const wcagContrast = measureWcagContrast(foreground, background);
|
|
18
|
+
const polarity = foreground.l <= background.l ? 1 : -1;
|
|
19
|
+
return polarity * wcagContrast * 10;
|
|
20
|
+
}
|
|
21
|
+
const channelToLinear = (channel) => {
|
|
22
|
+
const normalized = channel / 255;
|
|
23
|
+
return normalized <= 0.03928
|
|
24
|
+
? normalized / 12.92
|
|
25
|
+
: ((normalized + 0.055) / 1.055) ** 2.4;
|
|
26
|
+
};
|
|
27
|
+
const relativeLuminance = (color) => {
|
|
28
|
+
const rgb = serializeOklchToSrgb(color);
|
|
29
|
+
return (0.2126 * channelToLinear(rgb.r) +
|
|
30
|
+
0.7152 * channelToLinear(rgb.g) +
|
|
31
|
+
0.0722 * channelToLinear(rgb.b));
|
|
32
|
+
};
|
|
33
|
+
export function measureWcagContrast(foreground, background) {
|
|
34
|
+
const foregroundLuminance = relativeLuminance(foreground);
|
|
35
|
+
const backgroundLuminance = relativeLuminance(background);
|
|
36
|
+
const lighter = Math.max(foregroundLuminance, backgroundLuminance);
|
|
37
|
+
const darker = Math.min(foregroundLuminance, backgroundLuminance);
|
|
38
|
+
return (lighter + 0.05) / (darker + 0.05);
|
|
39
|
+
}
|
|
40
|
+
const hasTargetContrast = (contrast, target) => Math.abs(contrast) >= target;
|
|
41
|
+
const createCandidate = (color, lightness, chroma) => normalizeOklch({
|
|
42
|
+
alpha: color.alpha,
|
|
43
|
+
c: Math.max(0, chroma),
|
|
44
|
+
h: color.h,
|
|
45
|
+
l: clampLightness(lightness),
|
|
46
|
+
});
|
|
47
|
+
const selectContrastDirections = (target, context) => {
|
|
48
|
+
if (target.l < 45) {
|
|
49
|
+
return ['increase', 'decrease'];
|
|
50
|
+
}
|
|
51
|
+
if (target.l > 55) {
|
|
52
|
+
return ['decrease', 'increase'];
|
|
53
|
+
}
|
|
54
|
+
return context === 'dark'
|
|
55
|
+
? ['increase', 'decrease']
|
|
56
|
+
: ['decrease', 'increase'];
|
|
57
|
+
};
|
|
58
|
+
const scanLightness = (color, target, chroma, direction, maxLuminanceShift, contrastTarget) => {
|
|
59
|
+
const signedStep = direction === 'increase' ? LIGHTNESS_STEP : -LIGHTNESS_STEP;
|
|
60
|
+
const iterations = Math.ceil(maxLuminanceShift / LIGHTNESS_STEP);
|
|
61
|
+
let bestColor = createCandidate(color, color.l, chroma);
|
|
62
|
+
let bestContrast = measureApcaContrast(bestColor, target);
|
|
63
|
+
for (let index = 0; index <= iterations; index += 1) {
|
|
64
|
+
const lightness = color.l + signedStep * index;
|
|
65
|
+
const shift = Math.abs(lightness - color.l);
|
|
66
|
+
if (shift > maxLuminanceShift) {
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
const candidate = createCandidate(color, lightness, chroma);
|
|
70
|
+
const contrast = measureApcaContrast(candidate, target);
|
|
71
|
+
if (Math.abs(contrast) > Math.abs(bestContrast)) {
|
|
72
|
+
bestColor = candidate;
|
|
73
|
+
bestContrast = contrast;
|
|
74
|
+
}
|
|
75
|
+
if (hasTargetContrast(contrast, contrastTarget)) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { bestColor, bestContrast };
|
|
80
|
+
};
|
|
81
|
+
export function resolveOnContrast({ color, config, context, target, }) {
|
|
82
|
+
const contrastTarget = config.on.contrastTarget;
|
|
83
|
+
const maxReduction = Math.min(color.c, color.c * config.chromaLimits.maxReduction);
|
|
84
|
+
const chromaStep = config.chromaLimits.reductionStep;
|
|
85
|
+
let bestContrast = measureApcaContrast(color, target);
|
|
86
|
+
if (hasTargetContrast(bestContrast, contrastTarget)) {
|
|
87
|
+
return Object.freeze(color);
|
|
88
|
+
}
|
|
89
|
+
for (let reduction = 0; reduction <= maxReduction + chromaStep / 2; reduction += chromaStep) {
|
|
90
|
+
const chroma = Math.max(0, color.c - reduction);
|
|
91
|
+
for (const direction of selectContrastDirections(target, context)) {
|
|
92
|
+
const result = scanLightness(color, target, chroma, direction, config.on.maxLuminanceShift, contrastTarget);
|
|
93
|
+
if (Math.abs(result.bestContrast) > Math.abs(bestContrast)) {
|
|
94
|
+
bestContrast = result.bestContrast;
|
|
95
|
+
}
|
|
96
|
+
if (hasTargetContrast(result.bestContrast, contrastTarget)) {
|
|
97
|
+
return Object.freeze(result.bestColor);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
throw createContrastUnsatisfiableError(roundContrast(Math.abs(bestContrast)), contrastTarget);
|
|
102
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type IntentName = string;
|
|
2
|
+
export type IntentDefinition = {
|
|
3
|
+
hue: number;
|
|
4
|
+
chroma: number;
|
|
5
|
+
};
|
|
6
|
+
export type IntentRegistry<I extends string = string> = Readonly<{
|
|
7
|
+
intents: Readonly<Record<I, Readonly<IntentDefinition>>>;
|
|
8
|
+
}>;
|
|
9
|
+
export declare function createIntentRegistry<const I extends string>(intents: Record<I, IntentDefinition>): IntentRegistry<I>;
|
|
10
|
+
export declare function hasIntent<I extends string>(registry: IntentRegistry<I>, intent: IntentName): intent is I;
|
|
11
|
+
export declare function getIntent<I extends string>(registry: IntentRegistry<I>, intent: IntentName): Readonly<IntentDefinition>;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { createUnknownIntentError } from '../utils/errors/errors.js';
|
|
2
|
+
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
3
|
+
const normalizeHue = (hue) => {
|
|
4
|
+
const normalized = ((hue % 360) + 360) % 360;
|
|
5
|
+
return Object.is(normalized, -0) ? 0 : normalized;
|
|
6
|
+
};
|
|
7
|
+
const forbiddenIntentTokens = Object.freeze({
|
|
8
|
+
level: new Set(['strong', 'subtle', 'weak', 'muted', 'heavy']),
|
|
9
|
+
relation: new Set(['on', 'over', 'under', 'overlay']),
|
|
10
|
+
state: new Set(['hover', 'active', 'focus', 'selected', 'disabled']),
|
|
11
|
+
usage: new Set(['text', 'border', 'icon', 'fill', 'line', 'lines']),
|
|
12
|
+
visual: new Set(['green', 'red', 'blue', 'dark', 'light']),
|
|
13
|
+
});
|
|
14
|
+
const splitIntentName = (name) => name
|
|
15
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
16
|
+
.split(/[-_]+|\s+/)
|
|
17
|
+
.map((part) => part.toLowerCase())
|
|
18
|
+
.filter((part) => part.length > 0);
|
|
19
|
+
const validateIntentName = (name) => {
|
|
20
|
+
if (name.length === 0) {
|
|
21
|
+
throw new Error('Intent name must not be empty.');
|
|
22
|
+
}
|
|
23
|
+
if (/\s/.test(name)) {
|
|
24
|
+
throw new Error(`Intent name "${name}" must not contain whitespace.`);
|
|
25
|
+
}
|
|
26
|
+
if (name.includes('.')) {
|
|
27
|
+
throw new Error(`Intent name "${name}" must use a flat namespace.`);
|
|
28
|
+
}
|
|
29
|
+
const tokens = splitIntentName(name);
|
|
30
|
+
for (const [category, forbiddenTokens] of Object.entries(forbiddenIntentTokens)) {
|
|
31
|
+
if (tokens.some((token) => forbiddenTokens.has(token))) {
|
|
32
|
+
throw new Error(`Intent name "${name}" must describe meaning only and must not encode ${category}.`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const normalizeIntentDefinition = (name, definition) => {
|
|
37
|
+
if (!isFiniteNumber(definition.hue)) {
|
|
38
|
+
throw new Error(`Intent "${name}" hue must be a finite number.`);
|
|
39
|
+
}
|
|
40
|
+
if (!isFiniteNumber(definition.chroma)) {
|
|
41
|
+
throw new Error(`Intent "${name}" chroma must be a finite number.`);
|
|
42
|
+
}
|
|
43
|
+
if (definition.chroma < 0) {
|
|
44
|
+
throw new Error(`Intent "${name}" chroma must be greater than or equal to 0.`);
|
|
45
|
+
}
|
|
46
|
+
return Object.freeze({
|
|
47
|
+
chroma: definition.chroma,
|
|
48
|
+
hue: normalizeHue(definition.hue),
|
|
49
|
+
});
|
|
50
|
+
};
|
|
51
|
+
export function createIntentRegistry(intents) {
|
|
52
|
+
const entries = Object.entries(intents);
|
|
53
|
+
const normalized = {};
|
|
54
|
+
for (const [name, definition] of entries) {
|
|
55
|
+
validateIntentName(name);
|
|
56
|
+
normalized[name] = normalizeIntentDefinition(name, definition);
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
intents: Object.freeze(normalized),
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
export function hasIntent(registry, intent) {
|
|
63
|
+
return Object.hasOwn(registry.intents, intent);
|
|
64
|
+
}
|
|
65
|
+
export function getIntent(registry, intent) {
|
|
66
|
+
if (!hasIntent(registry, intent)) {
|
|
67
|
+
throw createUnknownIntentError(intent);
|
|
68
|
+
}
|
|
69
|
+
return registry.intents[intent];
|
|
70
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type OklchColor = {
|
|
2
|
+
space: 'oklch';
|
|
3
|
+
l: number;
|
|
4
|
+
c: number;
|
|
5
|
+
h: number;
|
|
6
|
+
alpha: number;
|
|
7
|
+
};
|
|
8
|
+
export type OklchInput = {
|
|
9
|
+
l: number;
|
|
10
|
+
c: number;
|
|
11
|
+
h: number;
|
|
12
|
+
alpha?: number;
|
|
13
|
+
};
|
|
14
|
+
export declare function normalizeOklch(input: OklchInput): OklchColor;
|
|
15
|
+
export declare function isOklchColor(value: unknown): value is OklchColor;
|
|
16
|
+
export declare function assertOklchColor(value: unknown): asserts value is OklchColor;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const isFiniteNumber = (value) => typeof value === 'number' && Number.isFinite(value);
|
|
2
|
+
const normalizeHue = (hue) => {
|
|
3
|
+
const normalized = ((hue % 360) + 360) % 360;
|
|
4
|
+
return Object.is(normalized, -0) ? 0 : normalized;
|
|
5
|
+
};
|
|
6
|
+
const validateFiniteChannel = (name, value) => {
|
|
7
|
+
if (!isFiniteNumber(value)) {
|
|
8
|
+
throw new Error(`OKLCH ${name} must be a finite number.`);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
export function normalizeOklch(input) {
|
|
12
|
+
validateFiniteChannel('l', input.l);
|
|
13
|
+
validateFiniteChannel('c', input.c);
|
|
14
|
+
validateFiniteChannel('h', input.h);
|
|
15
|
+
if (input.l < 0 || input.l > 100) {
|
|
16
|
+
throw new Error('OKLCH l must be between 0 and 100.');
|
|
17
|
+
}
|
|
18
|
+
if (input.c < 0) {
|
|
19
|
+
throw new Error('OKLCH c must be greater than or equal to 0.');
|
|
20
|
+
}
|
|
21
|
+
const alpha = input.alpha ?? 1;
|
|
22
|
+
validateFiniteChannel('alpha', alpha);
|
|
23
|
+
if (alpha < 0 || alpha > 1) {
|
|
24
|
+
throw new Error('OKLCH alpha must be between 0 and 1.');
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
alpha,
|
|
28
|
+
c: input.c,
|
|
29
|
+
h: normalizeHue(input.h),
|
|
30
|
+
l: input.l,
|
|
31
|
+
space: 'oklch',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
export function isOklchColor(value) {
|
|
35
|
+
if (typeof value !== 'object' || value === null) {
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
const candidate = value;
|
|
39
|
+
return (candidate.space === 'oklch' &&
|
|
40
|
+
isFiniteNumber(candidate.l) &&
|
|
41
|
+
candidate.l >= 0 &&
|
|
42
|
+
candidate.l <= 100 &&
|
|
43
|
+
isFiniteNumber(candidate.c) &&
|
|
44
|
+
candidate.c >= 0 &&
|
|
45
|
+
isFiniteNumber(candidate.h) &&
|
|
46
|
+
candidate.h >= 0 &&
|
|
47
|
+
candidate.h < 360 &&
|
|
48
|
+
isFiniteNumber(candidate.alpha) &&
|
|
49
|
+
candidate.alpha >= 0 &&
|
|
50
|
+
candidate.alpha <= 1);
|
|
51
|
+
}
|
|
52
|
+
export function assertOklchColor(value) {
|
|
53
|
+
if (!isOklchColor(value)) {
|
|
54
|
+
throw new Error('Expected a normalized OKLCH color.');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ColorOutput } from './export/types.js';
|
|
2
|
+
import type { PaletteDefaultOutput, PaletteKit, PaletteKitConfig } from './types/index.js';
|
|
3
|
+
/**
|
|
4
|
+
* Creates an immutable Palette Kit resolver instance.
|
|
5
|
+
*
|
|
6
|
+
* The factory normalizes the provided intent registry once, keeps context and
|
|
7
|
+
* output defaults explicit, and never reads ambient platform state.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createPaletteKit<const I extends string, const PaletteOutput extends ColorOutput | undefined = undefined, const SystemDefaultOutput extends ColorOutput | undefined = undefined>(config: PaletteKitConfig<I, PaletteOutput, SystemDefaultOutput>): PaletteKit<I, PaletteDefaultOutput<PaletteOutput, SystemDefaultOutput>>;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { createIntentRegistry, } from './core/intent-registry.js';
|
|
2
|
+
import { assertContext } from './engine/context/context.js';
|
|
3
|
+
import { resolveColor } from './engine/resolve/resolve.js';
|
|
4
|
+
import { serializeColor } from './export/serialize.js';
|
|
5
|
+
import { assertColorOutput, resolveOutput, } from './export/types.js';
|
|
6
|
+
import { defaultResolverConfig, getResolverPresetConfig, mergeResolverConfig, } from './presets/presets.js';
|
|
7
|
+
function resolveSerializedOutput(color, output) {
|
|
8
|
+
if (output === 'oklch') {
|
|
9
|
+
return color;
|
|
10
|
+
}
|
|
11
|
+
return serializeColor(color, output);
|
|
12
|
+
}
|
|
13
|
+
function validateOptionalContext(context) {
|
|
14
|
+
if (context !== undefined) {
|
|
15
|
+
assertContext(context);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function validateOptionalOutput(output) {
|
|
19
|
+
if (output !== undefined) {
|
|
20
|
+
assertColorOutput(output);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function createResolveFunction(intentRegistry, paletteContext, systemDefaultContext, paletteOutput, systemDefaultOutput, resolverConfig) {
|
|
24
|
+
return (options) => {
|
|
25
|
+
const output = resolveOutput({
|
|
26
|
+
paletteOutput,
|
|
27
|
+
resolverOutput: options.output,
|
|
28
|
+
systemDefaultOutput,
|
|
29
|
+
});
|
|
30
|
+
const resolved = resolveColor({
|
|
31
|
+
intent: options.intent,
|
|
32
|
+
intentRegistry,
|
|
33
|
+
level: options.level,
|
|
34
|
+
on: options.on,
|
|
35
|
+
over: options.over,
|
|
36
|
+
paletteContext,
|
|
37
|
+
resolverConfig,
|
|
38
|
+
resolverContext: options.context,
|
|
39
|
+
state: options.state,
|
|
40
|
+
stateDirection: options.stateDirection,
|
|
41
|
+
systemDefaultContext,
|
|
42
|
+
under: options.under,
|
|
43
|
+
usage: options.usage,
|
|
44
|
+
});
|
|
45
|
+
return resolveSerializedOutput(resolved.color, output);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Creates an immutable Palette Kit resolver instance.
|
|
50
|
+
*
|
|
51
|
+
* The factory normalizes the provided intent registry once, keeps context and
|
|
52
|
+
* output defaults explicit, and never reads ambient platform state.
|
|
53
|
+
*/
|
|
54
|
+
export function createPaletteKit(config) {
|
|
55
|
+
validateOptionalContext(config.context);
|
|
56
|
+
validateOptionalContext(config.systemDefaultContext);
|
|
57
|
+
validateOptionalOutput(config.output);
|
|
58
|
+
validateOptionalOutput(config.systemDefaultOutput);
|
|
59
|
+
const presetConfig = config.preset === undefined
|
|
60
|
+
? defaultResolverConfig
|
|
61
|
+
: getResolverPresetConfig(config.preset);
|
|
62
|
+
const resolverConfig = mergeResolverConfig(presetConfig, config.resolverConfig);
|
|
63
|
+
const intentRegistry = createIntentRegistry(config.intents);
|
|
64
|
+
return Object.freeze({
|
|
65
|
+
resolve: createResolveFunction(intentRegistry, config.context, config.systemDefaultContext, config.output, config.systemDefaultOutput, resolverConfig),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export declare const CONTEXTS: readonly ["light", "dark"];
|
|
2
|
+
export type Context = (typeof CONTEXTS)[number];
|
|
3
|
+
export type ContextResolutionInput = Readonly<{
|
|
4
|
+
resolverContext?: unknown;
|
|
5
|
+
paletteContext?: unknown;
|
|
6
|
+
systemDefaultContext?: unknown;
|
|
7
|
+
}>;
|
|
8
|
+
export type ContextCurveValues<T> = Readonly<Record<Context, T>>;
|
|
9
|
+
export type ContextCurveHook<T> = (context: Context) => T;
|
|
10
|
+
export declare function isContext(value: unknown): value is Context;
|
|
11
|
+
export declare function assertContext(value: unknown): asserts value is Context;
|
|
12
|
+
export declare function resolveContext({ resolverContext, paletteContext, systemDefaultContext, }: ContextResolutionInput): Context;
|
|
13
|
+
export declare function createContextCurveHook<T>(values: ContextCurveValues<T>): ContextCurveHook<T>;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { createUnresolvedContextError } from '../../utils/errors/errors.js';
|
|
2
|
+
export const CONTEXTS = Object.freeze(['light', 'dark']);
|
|
3
|
+
const contextList = CONTEXTS.join(', ');
|
|
4
|
+
const formatInvalidContextError = (value) => `Invalid context "${String(value)}". Expected one of: ${contextList}.`;
|
|
5
|
+
export function isContext(value) {
|
|
6
|
+
return (typeof value === 'string' && CONTEXTS.includes(value));
|
|
7
|
+
}
|
|
8
|
+
export function assertContext(value) {
|
|
9
|
+
if (!isContext(value)) {
|
|
10
|
+
throw new Error(formatInvalidContextError(value));
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function resolveContext({ resolverContext, paletteContext, systemDefaultContext, }) {
|
|
14
|
+
if (resolverContext !== undefined) {
|
|
15
|
+
assertContext(resolverContext);
|
|
16
|
+
return resolverContext;
|
|
17
|
+
}
|
|
18
|
+
if (paletteContext !== undefined) {
|
|
19
|
+
assertContext(paletteContext);
|
|
20
|
+
return paletteContext;
|
|
21
|
+
}
|
|
22
|
+
if (systemDefaultContext !== undefined) {
|
|
23
|
+
assertContext(systemDefaultContext);
|
|
24
|
+
return systemDefaultContext;
|
|
25
|
+
}
|
|
26
|
+
throw createUnresolvedContextError();
|
|
27
|
+
}
|
|
28
|
+
export function createContextCurveHook(values) {
|
|
29
|
+
const frozenValues = Object.freeze({
|
|
30
|
+
dark: values.dark,
|
|
31
|
+
light: values.light,
|
|
32
|
+
});
|
|
33
|
+
return Object.freeze((context) => {
|
|
34
|
+
assertContext(context);
|
|
35
|
+
return frozenValues[context];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Context } from '../context/context.js';
|
|
2
|
+
import type { Usage } from '../usage/strategy.js';
|
|
3
|
+
import { type Level } from './level.js';
|
|
4
|
+
export type LevelDrivenUsage = Exclude<Usage, 'visualVocabulary'>;
|
|
5
|
+
export type LevelCurve<T> = (level: Level, context: Context) => T;
|
|
6
|
+
export type FillLevelCurve = LevelCurve<number>;
|
|
7
|
+
export type LinesLevelCurve = LevelCurve<number>;
|
|
8
|
+
export type OverlayLevelResult = Readonly<{
|
|
9
|
+
luminanceDelta: number;
|
|
10
|
+
}>;
|
|
11
|
+
export type OverlaysLevelCurve = LevelCurve<OverlayLevelResult>;
|
|
12
|
+
export type LevelCurveConfig = Readonly<{
|
|
13
|
+
fill: FillLevelCurve;
|
|
14
|
+
lines: LinesLevelCurve;
|
|
15
|
+
overlays: OverlaysLevelCurve;
|
|
16
|
+
}>;
|
|
17
|
+
export declare const defaultLevelCurves: LevelCurveConfig;
|