@tenphi/glaze 0.15.1 → 0.16.1
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/README.md +3 -0
- package/dist/index.cjs +834 -89
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +453 -7
- package/dist/index.d.mts +453 -7
- package/dist/index.mjs +825 -90
- package/dist/index.mjs.map +1 -1
- package/docs/api.md +257 -14
- package/docs/methodology.md +437 -222
- package/docs/migration.md +70 -5
- package/docs/okhst.md +45 -2
- package/package.json +1 -1
package/docs/migration.md
CHANGED
|
@@ -68,7 +68,31 @@ Relative `tone` offsets that overshoot `[0, 100]` now **mirror to the other side
|
|
|
68
68
|
|
|
69
69
|
### Resolved variants store tone
|
|
70
70
|
|
|
71
|
-
`ResolvedColorVariant` now exposes `t` (tone, 0–1) instead of `l`. If you read resolved internals, convert with `variantToOkhsl(variant).l`. Token/CSS/JSON output
|
|
71
|
+
`ResolvedColorVariant` now exposes `t` (tone, 0–1) instead of `l`. If you read resolved internals, convert with `variantToOkhsl(variant).l`. Token/CSS/JSON output uses native formats by default (`oklch` for `tokens()` / `json()`, `okhsl` for `tasty()`); use `tasty({ format: 'okhsl' })` or `tasty({ format: 'okhst' })` for Glaze-native spaces.
|
|
72
|
+
|
|
73
|
+
### 0.16.0 — output format defaults and Tasty-only spaces
|
|
74
|
+
|
|
75
|
+
**Breaking changes in 0.16.0:**
|
|
76
|
+
|
|
77
|
+
| Export | Old default | New default |
|
|
78
|
+
|---|---|---|
|
|
79
|
+
| `tokens()` / `json()` (theme + palette) | `okhsl` | `oklch` |
|
|
80
|
+
| Standalone `.json()` | `okhsl` | `oklch` |
|
|
81
|
+
| `tasty()` | `okhsl` (unchanged) | `okhsl` |
|
|
82
|
+
|
|
83
|
+
`'okhsl'` and the new `'okhst'` output format are **Tasty-only**. Passing either to `css()`, `tailwind()`, `tokens()`, or `json()` throws. Migrate:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
// before
|
|
87
|
+
theme.tokens({ format: 'okhsl' })
|
|
88
|
+
theme.json()
|
|
89
|
+
|
|
90
|
+
// after — pick one
|
|
91
|
+
theme.tasty({ format: 'okhsl' }) // Tasty #name keys, okhsl strings
|
|
92
|
+
theme.tokens({ format: 'oklch' }) // native CSS (new default)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**New:** `splitHue` on `css()` / `tasty()` (theme + palette) and standalone `color.css()` emits hue as a separate custom property for runtime re-skinning. Requires `format: 'oklch'` and every color to be pastel. See [api.md → Hue channel splitting](api.md#hue-channel-splitting-splithue).
|
|
72
96
|
|
|
73
97
|
### Export snapshots
|
|
74
98
|
|
|
@@ -76,16 +100,19 @@ Relative `tone` offsets that overshoot `[0, 100]` now **mirror to the other side
|
|
|
76
100
|
|
|
77
101
|
## Choosing an export
|
|
78
102
|
|
|
79
|
-
Glaze emits the same resolved colors in
|
|
103
|
+
Glaze emits the same resolved colors in six shapes. Pick one based on your renderer / tooling.
|
|
80
104
|
|
|
81
105
|
| Method | Output shape | Use it for |
|
|
82
106
|
|---|---|---|
|
|
83
107
|
| `palette.tasty(options?)` | `{ '#name': { '': value, '@dark': value, '@hc': value } }` | The [Tasty](https://tasty.style/docs) style system. Single object, state aliases keyed inside each token. |
|
|
84
108
|
| `palette.tokens(options?)` | `{ light: { name: value }, dark: { name: value }, ... }` | Most CSS-in-JS systems. Per-variant flat maps, easy to feed into a `:root { ... }` selector via your framework's globals. |
|
|
85
109
|
| `palette.css(options?)` | `{ light: '--name-color: rgb(...);', dark: '...', ... }` | Framework-free CSS / static stylesheets. Variant-grouped CSS custom property strings ready to wrap in `:root` and `prefers-color-scheme` queries. |
|
|
86
|
-
| `palette.json(options?)` | `{ themeName: { name: { light, dark, ... } } }` | Tooling, JSON pipelines
|
|
110
|
+
| `palette.json(options?)` | `{ themeName: { name: { light, dark, ... } } }` | Tooling, JSON pipelines. |
|
|
111
|
+
| `palette.dtcg(options?)` | `{ light: { name: { $type, $value } }, dark: { ... }, ... }` | W3C [DTCG 2025.10](https://www.designtokens.org/) `.tokens.json` — Figma, Tokens Studio, Style Dictionary, Terrazzo, Penpot. One document per scheme. |
|
|
112
|
+
| `palette.dtcgResolver(options?)` | `{ version, sets, modifiers, resolutionOrder }` | W3C DTCG **Resolver-Module** — a single document describing every scheme variant as `sets` + a `scheme` modifier with a context per variant. For resolver tools such as Dispersa. |
|
|
113
|
+
| `palette.tailwind(options?)` | `'@theme { --color-*: ... } .dark { ... } ...'` | Tailwind CSS v4. A single ready-to-paste `@theme` block plus dark / high-contrast overrides. |
|
|
87
114
|
|
|
88
|
-
|
|
115
|
+
`tasty()`, `tokens()`, `json()`, `dtcg()`, `dtcgResolver()`, and `tailwind()` accept `modes` (`{ dark, highContrast }`). `css()` always returns all four strings (`light`, `dark`, `lightContrast`, `darkContrast`). The CSS-string exports accept `format` (`'rgb' \| 'hsl' \| 'oklch'` on `tokens`/`json`/`css`/`tailwind`; `'okhsl' \| 'okhst'` on `tasty()` only); `dtcg()` and `dtcgResolver()` use `colorSpace` (`'srgb' \| 'oklch'`) instead. See [api.md → Palette](api.md#palette) for full options.
|
|
89
116
|
|
|
90
117
|
## Wiring exports into the app
|
|
91
118
|
|
|
@@ -164,9 +191,47 @@ const data = palette.json();
|
|
|
164
191
|
|
|
165
192
|
Feed into your tooling pipeline. Each color is grouped by theme name, then by token name, then by variant — no prefix logic to undo.
|
|
166
193
|
|
|
194
|
+
### W3C DTCG (`.tokens.json`)
|
|
195
|
+
|
|
196
|
+
```ts
|
|
197
|
+
import { writeFileSync } from 'node:fs';
|
|
198
|
+
const dtcg = palette.dtcg();
|
|
199
|
+
|
|
200
|
+
writeFileSync('tokens.light.tokens.json', JSON.stringify(dtcg.light, null, 2));
|
|
201
|
+
if (dtcg.dark) {
|
|
202
|
+
writeFileSync('tokens.dark.tokens.json', JSON.stringify(dtcg.dark, null, 2));
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Each document is a spec-conformant token tree. One file per scheme is the most tool-compatible convention — Style Dictionary treats them as themes, Tokens Studio as sets, and Figma as variable modes. Use `colorSpace: 'oklch'` for wide-gamut, Glaze-native values (no `hex`); the default `'srgb'` emits components plus a `hex` hint that every reader understands. Feed the files straight into Style Dictionary v4+, Tokens Studio, or any DTCG-compatible tool — no Glaze-specific transform needed.
|
|
207
|
+
|
|
208
|
+
### W3C DTCG Resolver-Module (single document)
|
|
209
|
+
|
|
210
|
+
When you want **one file** describing every scheme variant — for a resolver tool such as [Dispersa](https://github.com/dispersa-core/dispersa) — use `dtcgResolver()` instead of `dtcg()`:
|
|
211
|
+
|
|
212
|
+
```ts
|
|
213
|
+
import { writeFileSync } from 'node:fs';
|
|
214
|
+
const resolver = palette.dtcgResolver({ modes: { highContrast: true } });
|
|
215
|
+
|
|
216
|
+
writeFileSync('resolver.json', JSON.stringify(resolver, null, 2));
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
The document places the light tokens in `sets.base.sources[0]` (the default context) and emits a single `scheme` modifier with a context per variant (`light` / `dark` / `lightContrast` / `darkContrast`). Each non-default context holds that variant's exact resolved tokens — Glaze resolves `darkContrast` independently, so the four-context shape keeps every value correct (two independent modifiers would compose additively and produce wrong dark + high-contrast values). Rename the set, modifier, or contexts via `setName` / `modifierName` / `contextNames` if your resolver expects different labels. Prefer `dtcg()` when you need maximum per-file tool compatibility; prefer `dtcgResolver()` when a resolver tool consumes the document for you.
|
|
220
|
+
|
|
221
|
+
### Tailwind CSS v4
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
const css = palette.tailwind();
|
|
225
|
+
// Write to your CSS entry, or paste into a `@import "tailwindcss"` stylesheet:
|
|
226
|
+
// @theme { --color-primary-surface: oklch(...); ... }
|
|
227
|
+
// .dark { --color-primary-surface: oklch(...); ... }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
The `--color-*` namespace makes every color available as `bg-*` / `text-*` / `border-*`. Drive dark mode from the OS preference instead of a class with `darkSelector: '@media (prefers-color-scheme: dark)'` (it nests `:root` automatically). High-contrast overrides land under `.high-contrast` and `.dark.high-contrast` by default; both are omitted unless `modes.highContrast` is enabled.
|
|
231
|
+
|
|
167
232
|
## Prefix map strategies
|
|
168
233
|
|
|
169
|
-
`palette.tokens()` / `tasty()` / `css()` accept a `prefix` option:
|
|
234
|
+
`palette.tokens()` / `tasty()` / `css()` / `dtcg()` / `tailwind()` accept a `prefix` option:
|
|
170
235
|
|
|
171
236
|
| Value | Result |
|
|
172
237
|
|---|---|
|
package/docs/okhst.md
CHANGED
|
@@ -169,14 +169,14 @@ type ContrastSpec =
|
|
|
169
169
|
| number // bare WCAG ratio
|
|
170
170
|
| ContrastPreset // 'AA' | 'AAA' | 'AA-large' | 'AAA-large' (WCAG)
|
|
171
171
|
| { wcag: HCPair<number | ContrastPreset> }
|
|
172
|
-
| { apca: HCPair<number> };
|
|
172
|
+
| { apca: HCPair<number | ApcaPreset> };
|
|
173
173
|
|
|
174
174
|
contrast?: HCPair<ContrastSpec>;
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
A bare number or preset means WCAG. The `[normal, highContrast]` pair may live at
|
|
178
178
|
the outer level (`[4.5, 7]`, `[{ wcag: 4.5 }, { wcag: 7 }]`) **or** inside the
|
|
179
|
-
metric (`{ wcag: [4.5, 7] }`, `{ apca: [45, 60] }`). `resolveContrastForMode`
|
|
179
|
+
metric (`{ wcag: [4.5, 7] }`, `{ apca: [45, 60] }`, `{ apca: ['content', 'body'] }`). `resolveContrastForMode`
|
|
180
180
|
peels the outer pair by mode, then the inner metric pair by the same mode, then
|
|
181
181
|
resolves presets, returning `{ metric, target }`.
|
|
182
182
|
|
|
@@ -196,6 +196,49 @@ luminance — the soft-clamp constants are calibrated against `Ys`, so the solve
|
|
|
196
196
|
feeds it the matching basis. This is a faithful-but-simplified APCA (it omits
|
|
197
197
|
the spatial/font-size lookup that maps Lc to a usable text size).
|
|
198
198
|
|
|
199
|
+
#### Polarity (roles)
|
|
200
|
+
|
|
201
|
+
APCA is **asymmetric**: `|apcaContrast(a, b)| ≠ |apcaContrast(b, a)|`, because
|
|
202
|
+
the normal-polarity exponents (dark text on light bg) differ from the
|
|
203
|
+
reverse-polarity exponents (light text on dark bg). Glaze picks the argument
|
|
204
|
+
order from each color's semantic **role** against its base:
|
|
205
|
+
|
|
206
|
+
| Role | Polarity | Argument order | Aliases (name inference) |
|
|
207
|
+
| --------- | -------- | ------------------------------ | ------------------------------------------- |
|
|
208
|
+
| `text` | `fg` | `apcaContrast(candidate, base)`| `text`, `fg`, `foreground`, `content`, `ink`, `label`, `stroke` |
|
|
209
|
+
| `border` | `fg` | `apcaContrast(candidate, base)`| `border`, `divider`, `outline`, `separator`, `hairline`, `rule` |
|
|
210
|
+
| `surface` | `bg` | `apcaContrast(base, candidate)`| `surface`, `bg`, `background`, `fill`, `canvas`, `paper`, `layer` |
|
|
211
|
+
|
|
212
|
+
A color's role is resolved per the chain: explicit `role` → inferred from the
|
|
213
|
+
color name (last recognized token wins, so `button-text` → `text`,
|
|
214
|
+
`input-bg` → `surface`) → the opposite of the base's role → `'text'` (foreground)
|
|
215
|
+
default. Name inference is on by default (`config.inferRole: true`) and can be
|
|
216
|
+
disabled. WCAG is symmetric, so role never changes WCAG results — it only fixes
|
|
217
|
+
APCA argument order.
|
|
218
|
+
|
|
219
|
+
#### APCA presets
|
|
220
|
+
|
|
221
|
+
APCA targets may be a raw Lc number or a named preset (APCA Bronze Simple Mode
|
|
222
|
+
conformance levels), independent of role:
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
type ApcaPreset = 'preferred' | 'body' | 'content' | 'large' | 'non-text' | 'min';
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
| Preset | Lc | Use case |
|
|
229
|
+
| ------------- | --- | ---------------------------------------------------- |
|
|
230
|
+
| `'preferred'` | 90 | Preferred body / column text |
|
|
231
|
+
| `'body'` | 75 | Minimum body / column text |
|
|
232
|
+
| `'content'` | 60 | Readable non-body content (~WCAG AA 4.5:1) |
|
|
233
|
+
| `'large'` | 45 | Large/bold headlines; fine icons/outlines (~3:1) |
|
|
234
|
+
| `'non-text'` | 30 | Solid icons/controls; placeholder/disabled text |
|
|
235
|
+
| `'min'` | 15 | Dividers/decorative; APCA "point of invisibility" |
|
|
236
|
+
|
|
237
|
+
```ts
|
|
238
|
+
contrast: { apca: 'content' } // Lc 60
|
|
239
|
+
contrast: { apca: ['content', 'body'] } // HC pair: 60 normal, 75 high-contrast
|
|
240
|
+
```
|
|
241
|
+
|
|
199
242
|
## Verification (APCA / WCAG drift)
|
|
200
243
|
|
|
201
244
|
Because chromatic swatches inherit gray's tone-derived lightness but drift in
|
package/package.json
CHANGED