@tenphi/cookbook 0.32.0 → 0.33.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/docs/customization-rules.md +7 -5
- package/docs/migration.md +52 -26
- package/docs/theme-and-components.md +32 -15
- package/docs/upstream/glaze/README.md +46 -26
- package/docs/upstream/glaze/docs/contrast.mdx +751 -0
- package/docs/upstream/glaze/docs/exporting.mdx +634 -0
- package/docs/upstream/glaze/docs/getting-started.mdx +489 -0
- package/docs/upstream/glaze/docs/hdr.mdx +523 -0
- package/docs/upstream/glaze/docs/methodology.mdx +922 -0
- package/docs/upstream/glaze/docs/migration.md +477 -286
- package/docs/upstream/glaze/docs/mixes.mdx +348 -0
- package/docs/upstream/glaze/docs/okhst.mdx +679 -0
- package/docs/upstream/glaze/docs/reference/color-definitions.md +552 -0
- package/docs/upstream/glaze/docs/reference/color-tokens.md +420 -0
- package/docs/upstream/glaze/docs/reference/configuration.md +527 -0
- package/docs/upstream/glaze/docs/reference/contrast.md +277 -0
- package/docs/upstream/glaze/docs/reference/index.md +225 -0
- package/docs/upstream/glaze/docs/reference/output.md +713 -0
- package/docs/upstream/glaze/docs/reference/palette.md +434 -0
- package/docs/upstream/glaze/docs/reference/theme.md +331 -0
- package/docs/upstream/glaze/docs/reference/utilities.md +646 -0
- package/docs/upstream/glaze/docs/schemes.mdx +635 -0
- package/docs/upstream/glaze/docs/shadows-and-glows.mdx +519 -0
- package/docs/upstream/manifest.json +2 -2
- package/package.json +3 -3
- package/docs/upstream/glaze/docs/api.md +0 -2021
- package/docs/upstream/glaze/docs/methodology.md +0 -686
- package/docs/upstream/glaze/docs/okhst.md +0 -207
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Contrast and high contrast
|
|
3
|
+
description: HCPair, ContrastSpec and its WCAG and APCA presets, high-contrast escalation, and the manual contrast level.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
This page lists the contrast types, presets, and rules. For how the solver finds a tone, when to pick WCAG or APCA, and live examples, see [Contrast solving](../contrast.mdx). For how a color adapts across schemes, see [Light, dark, and high contrast](../schemes.mdx).
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import type {
|
|
10
|
+
HCPair,
|
|
11
|
+
ContrastSpec,
|
|
12
|
+
MinContrast,
|
|
13
|
+
ContrastPreset,
|
|
14
|
+
ApcaPreset,
|
|
15
|
+
} from '@tenphi/glaze';
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## `HCPair<T>`
|
|
19
|
+
|
|
20
|
+
A value used in both contrast tiers, or an explicit `[normal, highContrast]` pair:
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
type HCPair<T> = T | [T, T];
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The first entry drives the `light` and `dark` variants, the second `lightContrast` and `darkContrast`. A single value applies to both.
|
|
27
|
+
|
|
28
|
+
| Field | Type | Pair example |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `tone` on a [regular color](./color-definitions.md#regularcolordef) | `HCPair<ToneValue>` | `tone: ['-8', '-16']` |
|
|
31
|
+
| `contrast` on a regular or [mix](./color-definitions.md#mixcolordef) color | `HCPair<ContrastSpec>` | `contrast: [4.5, 7]` |
|
|
32
|
+
| `intensity` on a [shadow or glow](./color-definitions.md#shadowcolordef--glowcolordef) | `HCPair<number>` | `intensity: [8, 14]` |
|
|
33
|
+
| `value` on a [mix](./color-definitions.md#mixcolordef) | `HCPair<number>` | `value: [10, 20]` |
|
|
34
|
+
| `tone` and `contrast` on a [color token](./color-tokens.md) | `HCPair<…>` | `tone: [74, 80]` |
|
|
35
|
+
|
|
36
|
+
```ts
|
|
37
|
+
tone: '-8'; // -8 in normal and high contrast
|
|
38
|
+
tone: ['-8', '-16']; // -8 normal, -16 high contrast
|
|
39
|
+
contrast: { apca: 'content' }; // preset, auto-escalated in high contrast
|
|
40
|
+
contrast: { apca: ['content', 'body'] }; // explicit normal / HC targets
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
For `contrast`, the pair can wrap the whole spec or sit inside the metric (see [`ContrastSpec`](#contrastspec)). An explicit high-contrast value in either position turns off the automatic [escalation](#high-contrast-escalation) for that color.
|
|
44
|
+
|
|
45
|
+
Under a numeric [`contrastLevel`](#manual-contrast-level), the normal variants are resolved between the two entries.
|
|
46
|
+
|
|
47
|
+
## `ContrastSpec`
|
|
48
|
+
|
|
49
|
+
A contrast floor with a choice of metric:
|
|
50
|
+
|
|
51
|
+
```ts
|
|
52
|
+
type ContrastPreset = 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
|
|
53
|
+
type ApcaPreset =
|
|
54
|
+
| 'preferred'
|
|
55
|
+
| 'body'
|
|
56
|
+
| 'content'
|
|
57
|
+
| 'large'
|
|
58
|
+
| 'non-text'
|
|
59
|
+
| 'min';
|
|
60
|
+
|
|
61
|
+
type MinContrast = number | ContrastPreset; // a bare WCAG target
|
|
62
|
+
|
|
63
|
+
type ContrastSpec =
|
|
64
|
+
| number // WCAG ratio
|
|
65
|
+
| ContrastPreset // WCAG preset
|
|
66
|
+
| { wcag: HCPair<number | ContrastPreset> }
|
|
67
|
+
| { apca: HCPair<number | ApcaPreset> }; // APCA Lc target
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Every `contrast` field takes `HCPair<ContrastSpec>`: [`RegularColorDef`](./color-definitions.md#contrast), [`MixColorDef`](./color-definitions.md#mixcolordef), and the [color token](./color-tokens.md) inputs.
|
|
71
|
+
|
|
72
|
+
| Form | Metric | Example | Normal | High contrast |
|
|
73
|
+
| --- | --- | --- | --- | --- |
|
|
74
|
+
| Number | WCAG | `contrast: 4.5` | 4.5 | 4.5 |
|
|
75
|
+
| WCAG preset | WCAG | `contrast: 'AAA'` | 7 | 7 |
|
|
76
|
+
| `{ wcag }` | WCAG | `contrast: { wcag: 6 }` | 6 | 6 |
|
|
77
|
+
| Outer pair | WCAG | `contrast: [4.5, 7]` | 4.5 | 7 |
|
|
78
|
+
| Inner pair | WCAG | `contrast: { wcag: [4.5, 7] }` | 4.5 | 7 |
|
|
79
|
+
| Pair of objects | WCAG | `contrast: [{ wcag: 4.5 }, { wcag: 7 }]` | 4.5 | 7 |
|
|
80
|
+
| `{ apca }` number | APCA | `contrast: { apca: 60 }` | Lc 60 | Lc 75 (escalated) |
|
|
81
|
+
| `{ apca }` preset | APCA | `contrast: { apca: 'content' }` | Lc 60 | Lc 75 (escalated) |
|
|
82
|
+
| Inner APCA pair | APCA | `contrast: { apca: [45, 60] }` | Lc 45 | Lc 60 |
|
|
83
|
+
| Inner APCA preset pair | APCA | `contrast: { apca: ['content', 'body'] }` | Lc 60 | Lc 75 |
|
|
84
|
+
|
|
85
|
+
- A bare number or preset means **WCAG**. Use `{ wcag }` / `{ apca }` to name the metric.
|
|
86
|
+
- A WCAG number below `1` is treated as `1`. A negative APCA number uses its magnitude.
|
|
87
|
+
- The floor is measured against the color's `base`, in each scheme separately. It is a floor, not a target: a color whose preferred tone already clears it with the solver's small margin (×1.01 for WCAG, +0.5 Lc for APCA) keeps it, and one that misses moves only as far as the floor requires. How the search works, `autoFlip`, and unreachable targets are covered in [Contrast solving](../contrast.mdx#how-the-solver-finds-a-tone).
|
|
88
|
+
- WCAG is symmetric. APCA is not, so a color's [role](./color-definitions.md#roles) decides which side of the pair is measured as text.
|
|
89
|
+
- On a theme color, `contrast` requires `base`. Without one, validation throws `glaze: color "<name>" has "contrast" without "base".` On a [color token](./color-tokens.md#pairing-colors) without a `base`, the floor is measured against the token's own input color.
|
|
90
|
+
- `contrast` together with `opacity` emits a `console.warn`: a translucent color has no fixed perceived tone.
|
|
91
|
+
- A floor that is physically unreachable pins a regular color or token to the extreme of the side the solver searched first (not necessarily the closer one) and warns. A mix pins its ratio or opacity to the end of the range it searched and warns too: normally the pure target, but a mix whose value is already 100 (including the high-contrast entry of `value: [80, 100]`) can only search toward the base, so it pins to the pure base. A preferred tone or value that clears the floor itself is never given up for an unmet extreme: a value-100 mix whose pure target passes keeps it. Tone is contrast-uniform for grays, so Glaze also re-measures each chromatic regular color whose floor the solver met and emits a deduped drift advisory if it lands under the floor; the solver measures the real color, so this normally stays silent. See [When a floor can't be met](../contrast.mdx#when-a-floor-cant-be-met) and [Warnings](./configuration.md#warnings).
|
|
92
|
+
|
|
93
|
+
### WCAG presets
|
|
94
|
+
|
|
95
|
+
`ContrastPreset`:
|
|
96
|
+
|
|
97
|
+
| Preset | Ratio | WCAG criterion | High-contrast successor |
|
|
98
|
+
| --- | --- | --- | --- |
|
|
99
|
+
| `'AA-large'` | 3 | SC 1.4.3, large text | `'AAA-large'` (4.5) |
|
|
100
|
+
| `'AA'` | 4.5 | SC 1.4.3 | `'AAA'` (7) |
|
|
101
|
+
| `'AAA-large'` | 4.5 | SC 1.4.6, large text | — (top tier) |
|
|
102
|
+
| `'AAA'` | 7 | SC 1.4.6 | — (top tier) |
|
|
103
|
+
|
|
104
|
+
`resolveMinContrast(value)` maps a `MinContrast` to its ratio; see [Resolving a spec](./utilities.md#resolving-a-spec).
|
|
105
|
+
|
|
106
|
+
### APCA presets
|
|
107
|
+
|
|
108
|
+
`ApcaPreset` keywords are the APCA Bronze Simple Mode conformance levels. They don't depend on the role.
|
|
109
|
+
|
|
110
|
+
| Preset | Lc | Use |
|
|
111
|
+
| --- | --- | --- |
|
|
112
|
+
| `'preferred'` | 90 | Preferred body and column text |
|
|
113
|
+
| `'body'` | 75 | Minimum for body and column text |
|
|
114
|
+
| `'content'` | 60 | Readable non-body content (about WCAG AA 4.5:1) |
|
|
115
|
+
| `'large'` | 45 | Large or bold headlines; fine icons and outlines (about 3:1) |
|
|
116
|
+
| `'non-text'` | 30 | Solid icons and controls; placeholder and disabled text |
|
|
117
|
+
| `'min'` | 15 | Dividers and decoration; APCA's point of invisibility |
|
|
118
|
+
|
|
119
|
+
The same map is exported as `APCA_PRESETS` (see [Constants and types](./utilities.md#constants-and-types)). A raw Lc number works anywhere a preset does.
|
|
120
|
+
|
|
121
|
+
## High-contrast escalation
|
|
122
|
+
|
|
123
|
+
Without an explicit high-contrast entry, the `lightContrast` / `darkContrast` variants solve for a stronger floor:
|
|
124
|
+
|
|
125
|
+
| Authored | Normal | High contrast |
|
|
126
|
+
| --- | --- | --- |
|
|
127
|
+
| `'AA'`, `{ wcag: 'AA' }` | 4.5 | 7 (`'AAA'`) |
|
|
128
|
+
| `'AA-large'`, `{ wcag: 'AA-large' }` | 3 | 4.5 (`'AAA-large'`) |
|
|
129
|
+
| `'AAA'`, `'AAA-large'` | 7, 4.5 | Unchanged — already the top WCAG tier. |
|
|
130
|
+
| A WCAG number (`4.5`, `{ wcag: 6 }`) | as written | Unchanged — a bare ratio has no successor tier. |
|
|
131
|
+
| An APCA number or preset | Lc | Lc + 15, capped at 106 (a target already above 106 is kept). |
|
|
132
|
+
| An explicit pair, outer or inner | first entry | second entry, no escalation. |
|
|
133
|
+
|
|
134
|
+
- **WCAG promotion** follows the spec's own Enhanced level, SC 1.4.3 → SC 1.4.6.
|
|
135
|
+
- **APCA Enhanced Level** adds `APCA_HC_ENHANCEMENT` (15 Lc), the Lc analog of AAA over AA, and clamps the result to `APCA_MAX_LC` (106). The cap only limits the boost: a normal target already above 106 stays as it is, never lower than the normal tier's.
|
|
136
|
+
- **An explicit high-contrast value** in the outer `contrast` pair or the inner `wcag` / `apca` pair replaces the automatic step. Use one to hold a ceiling. APCA caps large or bold text at Lc 90 to avoid glare, so for a heading write `{ apca: ['preferred', 'preferred'] }` rather than letting high contrast boost it past 90.
|
|
137
|
+
- **Other pairs** (`tone`, `intensity`, mix `value`) have no automatic escalation: a single value is used in both tiers.
|
|
138
|
+
- **Tone windows** don't apply in high contrast: placement and the contrast search use the full `0–100` tone range. See [Light, dark, and high contrast](../schemes.mdx#high-contrast).
|
|
139
|
+
|
|
140
|
+
## Metric switching
|
|
141
|
+
|
|
142
|
+
A `contrast` pair can't switch metric between its entries. A WCAG ratio and an APCA Lc are different scales, so `[4.5, { apca: 75 }]` has no target between its ends, and even without a manual level the two variants would be judged incomparably. Validation throws:
|
|
143
|
+
|
|
144
|
+
```text
|
|
145
|
+
glaze: color "text" has a "contrast" pair that switches metric
|
|
146
|
+
(wcag → apca).
|
|
147
|
+
A WCAG ratio and an APCA Lc are different scales; use one metric for
|
|
148
|
+
both entries, e.g. { wcag: [normal, highContrast] }.
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Pair values inside one metric instead: `{ wcag: [4.5, 7] }` or `{ apca: [60, 90] }`.
|
|
152
|
+
|
|
153
|
+
## Manual contrast level
|
|
154
|
+
|
|
155
|
+
`contrastLevel` puts the normal end of the two-tier contrast model on a `0–100` slider. It is a config field, so it works globally, per theme, per token, and through `extend()`:
|
|
156
|
+
|
|
157
|
+
```ts
|
|
158
|
+
import { glaze } from '@tenphi/glaze';
|
|
159
|
+
|
|
160
|
+
glaze.configure({ contrastLevel: 60 });
|
|
161
|
+
const theme = glaze(280, 80, { contrastLevel: 60 });
|
|
162
|
+
const brand = glaze.color('#26fcb2', { contrastLevel: 60 });
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The level moves the **normal** variants, `light` and `dark`. It says nothing about high contrast: `lightContrast` / `darkContrast` stay the true high-contrast resolution at every level, and [`modes.highContrast`](./output.md#output-modes) alone decides whether they are emitted.
|
|
166
|
+
|
|
167
|
+
| Value | Meaning |
|
|
168
|
+
| --- | --- |
|
|
169
|
+
| `'auto'` | Default. The normal variants take the authored normal entries as they are. |
|
|
170
|
+
| `0` | The same output as `'auto'`, reached through the slider. |
|
|
171
|
+
| `100` | The normal variants are the high-contrast ones. Set globally, the separate tier is dropped as a duplicate; see [High-contrast output](#high-contrast-output). |
|
|
172
|
+
| `1`–`99` (any number between) | The normal variants are resolved at that level. |
|
|
173
|
+
|
|
174
|
+
- Levels `0` and `100` reproduce the classic `light` / `dark` and `lightContrast` / `darkContrast` output bit for bit.
|
|
175
|
+
- Numbers are clamped to `0–100`. A non-finite number, or any value other than a number or `'auto'` (such as the string `'50'`), throws: `glaze: contrastLevel must be a finite number 0–100 or 'auto' (got NaN).`
|
|
176
|
+
- The field is listed with the rest of the config in [`GlazeConfig`](./configuration.md#glazeconfig) and [`GlazeConfigOverride`](./configuration.md#glazeconfigoverride).
|
|
177
|
+
|
|
178
|
+
### What the level interpolates
|
|
179
|
+
|
|
180
|
+
Exactly the three things that make a high-contrast variant differ from its normal one:
|
|
181
|
+
|
|
182
|
+
| Mechanism | At level `L` |
|
|
183
|
+
| --- | --- |
|
|
184
|
+
| Authored `HCPair`s: `tone`, `contrast`, shadow / glow `intensity`, mix `value` | The two entries are interpolated. |
|
|
185
|
+
| The tone-window bypass | Each window endpoint moves linearly toward the full range: light `10 → 0`, dark `15 → 0` and `95 → 100` with the defaults. The render `eps` is not interpolated. |
|
|
186
|
+
| Contrast escalation (`AA → AAA`, APCA `+15 Lc`) | Both ends are resolved with the [escalation rules](#high-contrast-escalation), then the numeric target is interpolated. `'AA'` at level 50 solves for 5.75; `{ apca: 'content' }` at level 25 for Lc 63.75. |
|
|
187
|
+
|
|
188
|
+
The interpolated values go through the ordinary resolve, so a contrast floor at any level is **solved**, not approximated. Which side of its base a solved color prefers is fixed once per half of the ramp; see [Which side of its base a color takes](#which-side-of-its-base-a-color-takes).
|
|
189
|
+
|
|
190
|
+
They feed the two normal passes only. The high-contrast passes ignore the level and resolve exactly as in `'auto'` mode.
|
|
191
|
+
|
|
192
|
+
Hue, saturation, `darkDesaturation`, `opacity`, and `pastel` don't vary with high contrast, so the level doesn't change them.
|
|
193
|
+
|
|
194
|
+
### Un-interpolable tone pairs switch at 50
|
|
195
|
+
|
|
196
|
+
A pair whose entries are of different kinds has no midpoint: blending them would change *which* resolution rule applies partway up the ramp. These pairs use the normal entry below level 50 and the high-contrast entry from 50 up:
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
tone: [50, 'max']; // absolute vs extreme
|
|
200
|
+
tone: [50, '+20']; // absolute vs delta (on a color with a base)
|
|
201
|
+
tone: ['max', 'min']; // extremes carry no magnitude
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
Same-kind pairs interpolate smoothly: `[30, 20]`, `['-6', '-12']`, `{ wcag: [4.5, 7] }`, `[{ apca: 60 }, { apca: 90 }]`. Numeric pairs (`intensity`, `value`) always interpolate.
|
|
205
|
+
|
|
206
|
+
A `contrast` pair can't switch metric at all; see [Metric switching](#metric-switching).
|
|
207
|
+
|
|
208
|
+
### Which side of its base a color takes
|
|
209
|
+
|
|
210
|
+
[`autoFlip`](./color-definitions.md#autoflip) decides per solve, and its tie-break (when both sides of the base meet the floor, take the one nearer the preferred tone) depends on the target. Along a ramp of targets that tie-break can change, so a naive solve would let a color jump across its own base as the slider moves.
|
|
211
|
+
|
|
212
|
+
Under a manual level, each level first runs a probe: a solve at the nearer endpoint's target (level `0` below 50, level `100` from 50 up), from that level's preferred tone and base. The color then prefers the side the probe lands on:
|
|
213
|
+
|
|
214
|
+
- While the preferred side meets the floor, the color stays there, even where the other side would be nearer its preferred tone.
|
|
215
|
+
- The probe's target is the same across each half of the ramp, so the preferred side normally holds through it and changes only at level 50, where un-interpolable pairs also switch, when the two endpoints land on different sides. The preferred tone and the base move with the tone windows, though, so a color whose endpoint solve is close to a tie (both sides about equally near its preferred tone) can switch sides at another level.
|
|
216
|
+
- Flipping is re-ordered, not disabled. At a level where the preferred side can't reach the interpolated target, the solver falls back to the opposite side, so the floor is met wherever either side can meet it. Where neither side can, the color pins to the preferred side's extreme. A color near the limit of what either side can reach can therefore cross its base at other levels, and back again, even when both endpoints land on the same side.
|
|
217
|
+
- A probe that lands on its base pins no side. With `contrast: [1, 4.5]`, the level-0 probe keeps the preferred tone, and for a `'+0'` delta that is the base's own tone. Each level then picks a side by the solver's own tie rule, exactly as the same color without a `tone` would.
|
|
218
|
+
|
|
219
|
+
For example, in a gray theme (`glaze(0, 0)`), a static tone-95 color with `contrast: [3, 6]` on a static tone-60 base stays above the base through level 12, where it reaches white, and sits below it from level 13 (tone 19.6) on. White reaches only 3.38:1 against the base, and the target, rising from 3 to 6, passes that at level 12.7.
|
|
220
|
+
|
|
221
|
+
A color can also cross and come back. In the same theme, black reaches 4.55:1 against a static tone-49.77 base and white 4.61:1. A color on that base with `tone: '-1'` and `contrast: 'AA'` sits below it in light at both endpoints, but at levels 3 and 4 only white meets the rising target, so it moves above the base. From level 5 neither side meets the target, and it pins back to black.
|
|
222
|
+
|
|
223
|
+
The underlying solver option is `preferInitial` on [`findToneForContrast`](./utilities.md#findtoneforcontrastoptions), for the same behavior in a custom pipeline.
|
|
224
|
+
|
|
225
|
+
### High-contrast output
|
|
226
|
+
|
|
227
|
+
The level and the high-contrast tier are independent, and they compose: the slider raises the baseline, and a `prefers-contrast: more` block still escalates on top of whatever level the reader chose.
|
|
228
|
+
|
|
229
|
+
- `resolve()` returns four variants at every level. `lightContrast` / `darkContrast` are bit-identical to what `'auto'` resolves for those slots.
|
|
230
|
+
- [`modes.highContrast`](./output.md#output-modes) alone decides whether the tier is emitted, as in `'auto'` mode. A level doesn't turn it on or off, with the one exception below.
|
|
231
|
+
- `css()` ignores `modes` and always returns four strings. At a mid level its `lightContrast` / `darkContrast` strings carry escalated values that differ from the normal ones, so an existing `@media (prefers-contrast: more)` block keeps doing real work.
|
|
232
|
+
- A level on a **single theme or token** never changes which modes are emitted: `modes` is global, and one palette sibling must not collapse the shared token structure.
|
|
233
|
+
|
|
234
|
+
| `contrastLevel` | `modes.highContrast: false` (default) | `modes.highContrast: true` |
|
|
235
|
+
| --- | --- | --- |
|
|
236
|
+
| `'auto'` or `0` | `light`, `dark` | + `lightContrast`, `darkContrast` |
|
|
237
|
+
| `1`–`99` | `light`, `dark` at the level | + the tier, unchanged by the level |
|
|
238
|
+
| `100`, set globally | `light`, `dark` equal the high-contrast values | The same: the tier is dropped as a duplicate. |
|
|
239
|
+
|
|
240
|
+
At a global level of `100` the normal variants *are* the high-contrast ones, so a separate tier would be an exact copy. Glaze emits a single light / dark set and suppresses even an explicit per-call `modes: { highContrast: true }`. A level of `100` set on one theme or token still reports into the tier, with values equal to its own normal ones.
|
|
241
|
+
|
|
242
|
+
### Driving it at runtime
|
|
243
|
+
|
|
244
|
+
`configure()` bumps the config version, which invalidates every theme and token cache, so a slider only needs to re-export:
|
|
245
|
+
|
|
246
|
+
```ts
|
|
247
|
+
import { glaze } from '@tenphi/glaze';
|
|
248
|
+
|
|
249
|
+
const theme = glaze(280, 80);
|
|
250
|
+
theme.colors({
|
|
251
|
+
surface: { tone: 97 },
|
|
252
|
+
'surface-text': { base: 'surface', tone: '-52', contrast: 'AA' },
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
const slider = document.querySelector<HTMLInputElement>(
|
|
256
|
+
'#contrast-level',
|
|
257
|
+
)!;
|
|
258
|
+
const style = document.head.appendChild(document.createElement('style'));
|
|
259
|
+
|
|
260
|
+
slider.oninput = () => {
|
|
261
|
+
glaze.configure({ contrastLevel: slider.valueAsNumber });
|
|
262
|
+
const { light, dark } = theme.css();
|
|
263
|
+
style.textContent =
|
|
264
|
+
`:root { ${light} }\n` +
|
|
265
|
+
`@media (prefers-color-scheme: dark) { :root { ${dark} } }`;
|
|
266
|
+
};
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
A resolve at a manual level runs the same four passes as an `'auto'` one. Only level `100` skips the two high-contrast passes, because the normal passes already produced those values.
|
|
270
|
+
|
|
271
|
+
### Clearing it, exporting it, base links
|
|
272
|
+
|
|
273
|
+
- **`configure()` never clears a field by omission.** `glaze.configure({})` keeps the current level. Pass `contrastLevel: 'auto'` to leave manual mode, globally or on one theme of a palette.
|
|
274
|
+
- **`.export()` freezes only an authored level.** A level set on the instance, or passed to `.export(override)`, is written to the snapshot. A level inherited from `glaze.configure()` is a live reader preference and is left out, so a restored theme still follows the current slider. `modes` and `states` are never written to a snapshot. See [`theme.export()`](./theme.md#themeexportoverride).
|
|
275
|
+
- **Set the level globally, or on both sides of a base link.** A dependent [color token](./color-tokens.md#pairing-colors) reads its base token as the base resolved it, so a level set on only one of them solves the dependent's floor at its own level against a base at another. Tone deltas still step cleanly: an `'auto'` base under another level's dark window is read back through the link's, so a `'+0'` link lands on its base and dark steps use the link's scale. In the high-contrast tier, a dependent reads its base's high-contrast slots (and at level `100`, the base's mirrored normal ones).
|
|
276
|
+
|
|
277
|
+
`resolveContrastForLevel(spec, level, polarity?)` resolves a `contrast` spec at a level the same way, for custom pipelines. For levels strictly between `0` and `100` it throws on a pair that switches metric. See [Resolving a spec](./utilities.md#resolving-a-spec).
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: API reference
|
|
3
|
+
description: A map of every public export in @tenphi/glaze, the conventions they share, and how authoring snapshots are versioned.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
The reference is organized for lookup. This page maps every public export to the page that documents it, then covers the conventions the whole API shares and how authoring snapshots are versioned. For explanations and visuals, start with the concept pages: [Light, dark, and high contrast](../schemes.mdx), [Contrast solving](../contrast.mdx), [Shadows and glows](../shadows-and-glows.mdx), [Mixes and transparency](../mixes.mdx), and [Tone and OKHST](../okhst.mdx).
|
|
7
|
+
|
|
8
|
+
```ts
|
|
9
|
+
import { glaze } from '@tenphi/glaze';
|
|
10
|
+
import type { GlazeTheme, ColorMap } from '@tenphi/glaze';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The package ships ESM and CommonJS with full type declarations and has no runtime dependencies.
|
|
14
|
+
|
|
15
|
+
## Reference pages
|
|
16
|
+
|
|
17
|
+
| Page | Covers |
|
|
18
|
+
| --- | --- |
|
|
19
|
+
| [Themes](./theme.md) | `glaze()`, `fromHex` / `fromRgb` / `themeFrom`, theme properties and methods, `extend`, `resolve`, `export`. |
|
|
20
|
+
| [Color definitions](./color-definitions.md) | The `ColorDef` union: regular, shadow, glow, and mix colors, every field, and a glow's HDR value. |
|
|
21
|
+
| [Contrast and high contrast](./contrast.md) | `HCPair`, `ContrastSpec`, WCAG and APCA presets, high-contrast escalation, the manual contrast level. |
|
|
22
|
+
| [Color tokens](./color-tokens.md) | Standalone `glaze.color()` tokens, their methods, and `glaze.colorFrom()`. |
|
|
23
|
+
| [Palettes](./palette.md) | `glaze.palette()`, prefixes, collisions, the primary alias, `glaze.paletteFrom()`. |
|
|
24
|
+
| [Output formats](./output.md) | Every output method and option, color formats, output modes, state aliases, `splitHue`, HDR output. |
|
|
25
|
+
| [Configuration](./configuration.md) | `glaze.configure()` and every `GlazeConfig` field, per-instance overrides, validation, warnings. |
|
|
26
|
+
| [Utilities](./utilities.md) | `glaze.format` / `glaze.formatHdr`, the HDR channel helpers, standalone `glaze.shadow` / `glaze.glow`, color math, tone utilities, the contrast solver, role helpers, guards. |
|
|
27
|
+
|
|
28
|
+
## `glaze` and its statics
|
|
29
|
+
|
|
30
|
+
`glaze` is the only value most code needs. It is a function with static methods attached:
|
|
31
|
+
|
|
32
|
+
| Member | Returns | Documented in |
|
|
33
|
+
| --- | --- | --- |
|
|
34
|
+
| `glaze(hue, saturation?, config?)` | `GlazeTheme` | [Themes](./theme.md#glazehue-saturation-config) |
|
|
35
|
+
| `glaze(seed, config?)` | `GlazeTheme` | [Themes](./theme.md#glazeseed-config) |
|
|
36
|
+
| `glaze.fromHex(hex)` | `GlazeTheme` | [Themes](./theme.md#glazefromhexhex--glazefromrgbr-g-b) |
|
|
37
|
+
| `glaze.fromRgb(r, g, b)` | `GlazeTheme` | [Themes](./theme.md#glazefromhexhex--glazefromrgbr-g-b) |
|
|
38
|
+
| `glaze.themeFrom(data)` | `GlazeTheme` | [Themes](./theme.md#glazethemefromdata--glazefromdata) |
|
|
39
|
+
| `glaze.from(data)` | `GlazeTheme` | Alias of `themeFrom` — [Themes](./theme.md#glazethemefromdata--glazefromdata) |
|
|
40
|
+
| `glaze.palette(themes, options?)` | `GlazePalette` | [Palettes](./palette.md#glazepalettethemes-options) |
|
|
41
|
+
| `glaze.paletteFrom(data)` | `GlazePalette` | [Palettes](./palette.md#paletteexportoverride--glazepalettefromdata) |
|
|
42
|
+
| `glaze.color(input, config?)` | `GlazeColorToken` | [Color tokens](./color-tokens.md#glazecolorinput-config) |
|
|
43
|
+
| `glaze.colorFrom(data)` | `GlazeColorToken` | [Color tokens](./color-tokens.md#tokenexportoverride--glazecolorfromdata) |
|
|
44
|
+
| `glaze.shadow(input)` | `ResolvedColorVariant` | [Utilities](./utilities.md#glazeshadowinput--glazeglowinput) |
|
|
45
|
+
| `glaze.glow(input)` | `ResolvedColorVariant` | [Utilities](./utilities.md#glazeshadowinput--glazeglowinput) |
|
|
46
|
+
| `glaze.format(variant, format?, pastel?)` | `string` | [Utilities](./utilities.md#glazeformatvariant-format-pastel) |
|
|
47
|
+
| `glaze.formatHdr(variant, hdrFormat?)` | `string \| undefined` | [Utilities](./utilities.md#glazeformathdrvariant-hdrformat) |
|
|
48
|
+
| `glaze.configure(config)` | `void` | [Configuration](./configuration.md#glazeconfigureconfig) |
|
|
49
|
+
| `glaze.getConfig()` | `GlazeConfigResolved` | [Configuration](./configuration.md#glazegetconfig) |
|
|
50
|
+
| `glaze.resetConfig()` | `void` | [Configuration](./configuration.md#global-config) |
|
|
51
|
+
| `glaze.isThemeExport(data)` | `data is GlazeThemeExport` | [Authoring snapshots](#authoring-snapshots-and-versioning) |
|
|
52
|
+
| `glaze.isColorTokenExport(data)` | `data is GlazeColorTokenExport` | [Authoring snapshots](#authoring-snapshots-and-versioning) |
|
|
53
|
+
| `glaze.isPaletteExport(data)` | `data is GlazePaletteExport` | [Authoring snapshots](#authoring-snapshots-and-versioning) |
|
|
54
|
+
|
|
55
|
+
## Instance methods
|
|
56
|
+
|
|
57
|
+
| Instance | Members | Documented in |
|
|
58
|
+
| --- | --- | --- |
|
|
59
|
+
| `GlazeTheme` | `hue`, `saturation`, `darkHue`, `darkSaturation`, `getConfig`, `colors`, `color`, `remove`, `has`, `list`, `reset`, `extend`, `resolve`, `export` | [Themes](./theme.md#theme-methods) |
|
|
60
|
+
| `GlazeTheme` output | `tokens`, `tasty`, `json`, `css`, `dtcg`, `dtcgResolver`, `tailwind` | [Output formats](./output.md) |
|
|
61
|
+
| `GlazePalette` | `list`, `primary`, `theme`, `themes`, `export`, and the seven output methods | [Palettes](./palette.md#palette-members) |
|
|
62
|
+
| `GlazeColorToken` | `resolve`, `token`, `tasty`, `json`, `css`, `dtcg`, `dtcgResolver`, `tailwind`, `export` | [Color tokens](./color-tokens.md#token-methods) |
|
|
63
|
+
|
|
64
|
+
## Named exports
|
|
65
|
+
|
|
66
|
+
Everything below is exported from `'@tenphi/glaze'` next to `glaze`. Apart from `GLAZE_EXPORT_VERSION` and the type guards, these are lower-level building blocks for advanced use and custom pipelines.
|
|
67
|
+
|
|
68
|
+
| Export | Kind | Documented in |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| `glaze` | Function | This page |
|
|
71
|
+
| `GLAZE_EXPORT_VERSION` | Constant (`2`) | [Authoring snapshots](#authoring-snapshots-and-versioning) |
|
|
72
|
+
| `isThemeExport`, `isColorTokenExport`, `isPaletteExport` | Type guards (same functions as the `glaze.*` statics) | [Authoring snapshots](#authoring-snapshots-and-versioning) |
|
|
73
|
+
| `assertNativeFormat`, `assertAllPastel` | Export guards | [Utilities](./utilities.md#export-guards); where they apply: [Color formats](./output.md#color-formats) (`assertNativeFormat`), [`splitHue` requirements](./output.md#requirements) (`assertAllPastel`) |
|
|
74
|
+
| `findToneForContrast`, `findValueForMixContrast`, `resolveMinContrast`, `resolveContrastForMode`, `resolveContrastForLevel`, `resolveApcaTarget`, `apcaContrast` | Contrast solver functions | [Utilities](./utilities.md#contrast-solver) |
|
|
75
|
+
| `APCA_PRESETS`, `APCA_HC_ENHANCEMENT`, `APCA_MAX_LC` | Contrast constants | [Utilities](./utilities.md#constants-and-types); presets in [Contrast and high contrast](./contrast.md#apca-presets) |
|
|
76
|
+
| `normalizeRole`, `inferRoleFromName`, `roleToPolarity`, `oppositeRole` | Role helpers | [Utilities](./utilities.md#role-helpers) |
|
|
77
|
+
| `toTone`, `fromTone`, `toneFromY`, `yFromTone`, `okhstToOkhsl`, `okhslToOkhst`, `variantToOkhsl`, `REF_EPS` | OKHST tone utilities | [Utilities](./utilities.md#okhst-tone-utilities); the model in [Tone and OKHST](../okhst.mdx) |
|
|
78
|
+
| `okhslToLinearSrgb`, `okhslToSrgb`, `okhslToOklab`, `oklabToOkhsl`, `srgbToOkhsl`, `hslToSrgb`, `okhslToOklch`, `cuspLightness`, `parseHex`, `parseHexAlpha`, `srgbToHex`, `relativeLuminanceFromLinearRgb`, `contrastRatioFromLuminance`, `gamutClampedLuminance`, `apcaLuminanceFromLinearRgb` | Color math | [Utilities](./utilities.md#color-math) |
|
|
79
|
+
| `formatOkhsl`, `formatOkhst`, `formatRgb`, `formatHsl`, `formatOklch` | Format writers (0–1 scale) | [Utilities](./utilities.md#format-writers) |
|
|
80
|
+
| `formatSrgb`, `formatSrgbLinear` | HDR writers: extended or linear sRGB channels, unclamped, as `color(srgb R G B)` / `color(srgb-linear R G B)` | [Utilities](./utilities.md#format-writers); the format: [Extended sRGB](../hdr.mdx#extended-srgb) |
|
|
81
|
+
| `hdrToSrgb`, `hdrToLinearSrgb` | HDR channels: a `ResolvedHdr` → extended or linear sRGB, unclamped | [Utilities](./utilities.md#hdrtosrgbhdr--hdrtolinearsrgbhdr) |
|
|
82
|
+
|
|
83
|
+
## Types
|
|
84
|
+
|
|
85
|
+
All public types are exported with `import type`.
|
|
86
|
+
|
|
87
|
+
| Area | Types | Documented in |
|
|
88
|
+
| --- | --- | --- |
|
|
89
|
+
| Theme | `GlazeTheme`, `GlazeThemeSeed`, `GlazeExtendOptions`, `GlazeThemeExport` | [Themes](./theme.md) |
|
|
90
|
+
| Resolved values | `ResolvedColor`, `ResolvedColorVariant` | [Themes](./theme.md#themeresolve) |
|
|
91
|
+
| HDR values | `ResolvedHdr`, `GlazeHdrFormat` | [Color definitions](./color-definitions.md#hdr-values-resolvedhdr), [Utilities](./utilities.md#glazeformathdrvariant-hdrformat), [Output formats](./output.md#hdr-output) |
|
|
92
|
+
| Color definitions | `ColorDef`, `ColorMap`, `RegularColorDef`, `ShadowColorDef`, `GlowColorDef`, `MixColorDef`, `LightingTuning`, `ShadowTuning`, `GlowTuning` | [Color definitions](./color-definitions.md) |
|
|
93
|
+
| Tone and adaptation | `ToneValue`, `RelativeValue`, `ExtremeValue`, `AdaptationMode` | [Color definitions](./color-definitions.md), [Configuration](./configuration.md) |
|
|
94
|
+
| Roles | `Role`, `RoleInput`, `Polarity` | [Color definitions](./color-definitions.md), [Utilities](./utilities.md) |
|
|
95
|
+
| Contrast | `HCPair`, `MinContrast`, `ContrastSpec`, `ContrastPreset`, `ApcaPreset` | [Contrast and high contrast](./contrast.md) |
|
|
96
|
+
| Contrast solver | `ResolvedContrast`, `FindToneForContrastOptions`, `FindToneForContrastResult`, `FindValueForMixContrastOptions`, `FindValueForMixContrastResult` | [Utilities](./utilities.md) |
|
|
97
|
+
| Configuration | `GlazeConfig`, `GlazeConfigOverride`, `GlazeConfigResolved`, `ToneWindow` | [Configuration](./configuration.md) |
|
|
98
|
+
| Color values | `GlazeColorValue`, `HexColor`, `OkhslColor`, `OkhstColor`, `RgbColor`, `OklchColor` | [Color tokens](./color-tokens.md) |
|
|
99
|
+
| Color tokens | `GlazeColorToken`, `GlazeColorInput`, `GlazeFromInput`, `GlazeColorOverrides`, `GlazeColorCssOptions`, `GlazeColorTailwindOptions`, `GlazeColorDtcgResult`, `GlazeColorDtcgResolverOptions`, `GlazeColorTokenExport`, `GlazeColorInputExport`, `GlazeColorOverridesExport` | [Color tokens](./color-tokens.md) |
|
|
100
|
+
| Standalone shadow and glow | `GlazeLightingInput`, `GlazeShadowInput`, `GlazeGlowInput` | [Utilities](./utilities.md) |
|
|
101
|
+
| Palette | `GlazePalette`, `GlazePaletteOptions`, `GlazePaletteExportOptions`, `GlazePaletteTastyOptions`, `GlazePaletteCssOptions`, `GlazePaletteExport` | [Palettes](./palette.md) |
|
|
102
|
+
| Output options | `GlazeColorFormat`, `GlazeOutputModes`, `GlazeTokenOptions`, `GlazeTastyOptions`, `GlazeJsonOptions`, `GlazeCssOptions`, `GlazeCssResult`, `GlazeCssSchemeBlocks`, `GlazeTailwindOptions` | [Output formats](./output.md) |
|
|
103
|
+
| DTCG | `GlazeDtcgOptions`, `GlazeDtcgResult`, `DtcgColorSpace`, `DtcgColorToken`, `DtcgColorValue`, `DtcgSrgbColorValue`, `DtcgOklchColorValue`, `DtcgDocument` | [Output formats](./output.md#dtcgoptions) |
|
|
104
|
+
| DTCG HDR extension | `GlazeDtcgExtensions`, `GlazeDtcgHdr` | [Output formats](./output.md#dtcg-extensions) |
|
|
105
|
+
| DTCG resolver | `GlazeDtcgResolverOptions`, `GlazeDtcgResolverContextNames`, `GlazeDtcgResolverDocument`, `DtcgResolverSet`, `DtcgResolverModifier`, `DtcgResolverRef`, `DtcgTokenTree` | [Output formats](./output.md#dtcgresolveroptions) |
|
|
106
|
+
| `splitHue` planning | `HuePlan`, `HueDeclaration`, `ChannelCtx` | [Output formats](./output.md#planning-types) |
|
|
107
|
+
| Snapshots | `GlazeExportKind`, `GlazeExportVersion` | [Authoring snapshots](#authoring-snapshots-and-versioning) |
|
|
108
|
+
|
|
109
|
+
## Conventions
|
|
110
|
+
|
|
111
|
+
### Scales
|
|
112
|
+
|
|
113
|
+
Glaze mixes a few numeric scales. Which one applies depends on where a value is authored:
|
|
114
|
+
|
|
115
|
+
| Value | Scale | Where |
|
|
116
|
+
| --- | --- | --- |
|
|
117
|
+
| Hue | 0–360 degrees | Everywhere: seeds, per-color `hue` / `darkHue`, resolved variants, value objects. |
|
|
118
|
+
| Seed saturation | 0–100 | `glaze(hue, saturation)`, `GlazeThemeSeed.saturation` / `darkSaturation`, `theme.saturation`, the structured `glaze.color({ saturation })`, `GlazeColorOverrides.saturation`. |
|
|
119
|
+
| Per-color saturation | 0–1, a factor of the seed | `RegularColorDef.saturation` / `darkSaturation`; `saturationFactor` / `darkSaturationFactor` on color tokens. With `from`, an explicit `saturation` is still a factor of the seed. |
|
|
120
|
+
| Tone | 0–100 | Authored `tone`, tone deltas (`'+20'`). Shadow and glow `intensity` is a step in tones (0–100). |
|
|
121
|
+
| Tone window endpoints | 0–100 OKHSL lightness, not tone | `lightTone` / `darkTone` `lo` and `hi`. The default `darkTone: [15, 95]` holds tones 9.25–95.9, so a tone-100 `auto` surface lands at tone 9.25 in dark. See [Tone windows](./configuration.md#tone-windows). |
|
|
122
|
+
| Mix `value` | 0–100 | `MixColorDef.value` (0 = base, 100 = target). |
|
|
123
|
+
| Resolved variant | `h` 0–360; `s`, `t`, `alpha` 0–1 | `ResolvedColorVariant`, the input to `glaze.format()`. Its optional `hdr` (`ResolvedHdr`) uses the same scales plus `exposure`. |
|
|
124
|
+
| Headroom, exposure | Stops above SDR white; each stop doubles luminance. `headroom` 0–2. | `glowTuning.headroom` and a glow's `tuning.headroom`; `ResolvedHdr.exposure` and DTCG `exposure`. 1 stop past white is tone 121.98, 2 stops tone 144.34. |
|
|
125
|
+
| HDR channels | Extended or linear sRGB, unclamped; `1` is SDR white. One stop above it is `1.3533` extended, `2` linear. | Extended: every exporter's HDR strings, DTCG `hdr.value`, `glaze.formatHdr()` by default, `hdrToSrgb`, `formatSrgb`. Linear: `glaze.formatHdr(v, 'srgb-linear')`, `hdrToLinearSrgb`, `formatSrgbLinear`. |
|
|
126
|
+
| Value objects | `{ h, s, l }` and `{ h, s, t }`: `s`, `l`, `t` 0–1. `{ r, g, b }`: 0–255. `{ l, c, h }`: `L` and `C` 0–1, `H` degrees. | `from`, `glaze.color()`, `glaze.shadow()` / `glaze.glow()` inputs. |
|
|
127
|
+
| Format writers and converters | `h` 0–360; `s`, `l`, `t` 0–1 | `formatOkhsl` … `formatOklch` and the color math functions. The format writers warn when they get 0–100 percentages (the pre-2.0 scale). |
|
|
128
|
+
| Opacity, ratios | 0–1 | `opacity`, `darkDesaturation`, lighting `tint` / `reach` / `hueBleed`. |
|
|
129
|
+
| Contrast level | 0–100, or `'auto'` | `contrastLevel`. |
|
|
130
|
+
| sRGB channels | 0–255 | `glaze.fromRgb(r, g, b)`, `rgb()` output. |
|
|
131
|
+
|
|
132
|
+
### Scheme variant names
|
|
133
|
+
|
|
134
|
+
Every resolved color has four variants: `light`, `dark`, `lightContrast`, and `darkContrast`. `ResolvedColor` and the results of `tokens()`, `json()`, `css()`, and `dtcg()` use these names as keys. `tasty()` keys the variants by state alias, `tailwind()` by selector, and `dtcgResolver()` uses the names as its default context names. See [Scheme variants](./output.md#scheme-variants).
|
|
135
|
+
|
|
136
|
+
### High-contrast pairs
|
|
137
|
+
|
|
138
|
+
Fields that can differ in high contrast accept an `HCPair<T>`: either a single value or a `[normal, highContrast]` tuple.
|
|
139
|
+
|
|
140
|
+
```ts
|
|
141
|
+
type HCPair<T> = T | [T, T];
|
|
142
|
+
|
|
143
|
+
theme.colors({
|
|
144
|
+
border: { base: 'surface', tone: ['-8', '-20'] },
|
|
145
|
+
'surface-text': { base: 'surface', contrast: ['AA', 'AAA'] },
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
`tone`, `contrast`, shadow and glow `intensity`, and mix `value` accept pairs. The first entry drives `light` / `dark`, the second `lightContrast` / `darkContrast`. Under a manual `contrastLevel` the normal variants interpolate between the two. Full rules: [Contrast and high contrast](./contrast.md).
|
|
150
|
+
|
|
151
|
+
### Tone values
|
|
152
|
+
|
|
153
|
+
An authored `tone` is a number (absolute, 0–100), a signed string (`'+N'` / `'-N'`, a tone delta from `base`), or `'max'` / `'min'` (the scheme's tone extreme). See [Color definitions](./color-definitions.md#tone-values) and, for what tone means, [Tone and OKHST](../okhst.mdx).
|
|
154
|
+
|
|
155
|
+
## Authoring snapshots and versioning
|
|
156
|
+
|
|
157
|
+
Every instance can be saved as a JSON-safe **authoring snapshot** — the definitions and a frozen config, not resolved colors — and restored later:
|
|
158
|
+
|
|
159
|
+
| Export | Restore | Kind |
|
|
160
|
+
| --- | --- | --- |
|
|
161
|
+
| [`theme.export(override?)`](./theme.md#themeexportoverride) | [`glaze.themeFrom(data)`](./theme.md#glazethemefromdata--glazefromdata) | `'theme'` |
|
|
162
|
+
| [`token.export(override?)`](./color-tokens.md#tokenexportoverride--glazecolorfromdata) | [`glaze.colorFrom(data)`](./color-tokens.md#tokenexportoverride--glazecolorfromdata) | `'color'` |
|
|
163
|
+
| [`palette.export(override?)`](./palette.md#paletteexportoverride--glazepalettefromdata) | [`glaze.paletteFrom(data)`](./palette.md#paletteexportoverride--glazepalettefromdata) | `'palette'` |
|
|
164
|
+
|
|
165
|
+
Resolved output (`tokens()`, `css()`, `json()`, …) is a different thing: it can't be restored. [Exporting and integration](../exporting.mdx) explains when to use which.
|
|
166
|
+
|
|
167
|
+
### `kind` and `version`
|
|
168
|
+
|
|
169
|
+
Every `.export()` writes two fields:
|
|
170
|
+
|
|
171
|
+
- `kind` — `GlazeExportKind`: `'theme'`, `'color'`, or `'palette'`.
|
|
172
|
+
- `version` — `GLAZE_EXPORT_VERSION`, currently `2` (`GlazeExportVersion` is its literal type).
|
|
173
|
+
|
|
174
|
+
| Version | Written by |
|
|
175
|
+
| --- | --- |
|
|
176
|
+
| `1` | Glaze 1.1–2.x. |
|
|
177
|
+
| `2` | Glaze 3: shadow `intensity`, `fg`, and tuning changed meaning, and `glow` was added. |
|
|
178
|
+
|
|
179
|
+
### Restore checks
|
|
180
|
+
|
|
181
|
+
`themeFrom`, `colorFrom`, and `paletteFrom` apply the same rules:
|
|
182
|
+
|
|
183
|
+
| Snapshot | Result |
|
|
184
|
+
| --- | --- |
|
|
185
|
+
| No `kind` / no `version` (legacy or hand-written) | Accepted. |
|
|
186
|
+
| `kind` doesn't match the restore function | Throws, e.g. `glaze.themeFrom: expected kind "theme", got "palette".` |
|
|
187
|
+
| `version` not an integer ≥ 1 | Throws `… invalid "version" field — expected an integer >= 1 (got <value>).` |
|
|
188
|
+
| `version` greater than `GLAZE_EXPORT_VERSION` | Throws `… unsupported export version <n> (this library supports version 2). Upgrade @tenphi/glaze to load this snapshot.` |
|
|
189
|
+
| `version: 1` theme or palette with shadow colors or a `shadowTuning` config | Throws (below). |
|
|
190
|
+
| `version: 1` without shadows | Restores as before. |
|
|
191
|
+
|
|
192
|
+
A version-1 theme snapshot with shadows is rejected because Glaze 3 changed what shadow `intensity`, `fg`, and tuning mean: restoring it would silently render every shadow differently. The error names the shadows and points to the upgrade guide:
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
glaze.themeFrom: this snapshot is export version 1 (Glaze 1.1–2) and contains
|
|
196
|
+
shadow colors ("card-shadow").
|
|
197
|
+
Glaze 3 changed what shadow "intensity", "fg" and tuning mean, so it would
|
|
198
|
+
restore with different shadows.
|
|
199
|
+
Re-author them as described in docs/migration.md ("3.0 — shadows and
|
|
200
|
+
glows") and export again.
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A theme entry inside a palette snapshot that has no `version` of its own is checked against the palette's `version`. The check keys on `version: 1`, so a snapshot without any `version` (a hand-written one, or one from Glaze 1.0) is not checked: re-author its shadows before restoring it. How to re-author Glaze 2 shadows is in [3.0 — shadows and glows](../migration.md#30--shadows-and-glows).
|
|
204
|
+
|
|
205
|
+
### Type guards
|
|
206
|
+
|
|
207
|
+
`isThemeExport`, `isColorTokenExport`, and `isPaletteExport` (also available as `glaze.isThemeExport` etc.) tell the snapshot kinds apart. They check `kind` when present and fall back to the shape for legacy snapshots:
|
|
208
|
+
|
|
209
|
+
| Guard | `true` when |
|
|
210
|
+
| --- | --- |
|
|
211
|
+
| `isThemeExport(data)` | A plain object with `kind: 'theme'` or no `kind`, numeric `hue` and `saturation`, and no `form` or `themes` field. |
|
|
212
|
+
| `isColorTokenExport(data)` | A plain object with `kind: 'color'` or no `kind`, and `form` of `'value'` or `'structured'`. |
|
|
213
|
+
| `isPaletteExport(data)` | A plain object with `kind: 'palette'` or no `kind`, a `themes` object, no `form`, and no numeric `hue`. |
|
|
214
|
+
|
|
215
|
+
A `kind` that names a different snapshot type always returns `false`. The guards check structure only; they don't validate `version` or the color definitions.
|
|
216
|
+
|
|
217
|
+
```ts
|
|
218
|
+
import { glaze } from '@tenphi/glaze';
|
|
219
|
+
|
|
220
|
+
const data: unknown = JSON.parse(saved);
|
|
221
|
+
|
|
222
|
+
if (glaze.isPaletteExport(data)) glaze.paletteFrom(data);
|
|
223
|
+
else if (glaze.isThemeExport(data)) glaze.themeFrom(data);
|
|
224
|
+
else if (glaze.isColorTokenExport(data)) glaze.colorFrom(data);
|
|
225
|
+
```
|