@tenphi/glaze 0.11.1 → 0.12.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/api.md CHANGED
@@ -240,6 +240,8 @@ type MinContrast = number | 'AA' | 'AAA' | 'AA-large' | 'AAA-large';
240
240
 
241
241
  You can also pass any numeric ratio directly (e.g., `contrast: 4.5`, `contrast: 11`). The constraint is applied independently for each scheme — if the `lightness` already satisfies the floor it's kept, otherwise the solver adjusts lightness until the target is met.
242
242
 
243
+ By default, `autoFlip` lets the solver cross to the opposite side of the base color when the requested lightness direction cannot satisfy contrast. Set `glaze.configure({ autoFlip: false })` to keep strict directionality: unmet colors pin to that direction's 0 or 100 lightness extreme instead of falling back to the original requested value.
244
+
243
245
  **Full lightness spectrum in HC mode:** in high-contrast variants the `lightLightness` and `darkLightness` window constraints are bypassed entirely. Colors can reach the full 0–100 range, maximizing perceivable contrast.
244
246
 
245
247
  #### Per-color hue override
@@ -306,7 +308,8 @@ glaze.color(value: GlazeColorValue, overrides?: GlazeColorOverrides, scaling?: G
306
308
  | `okhsl()` | `'okhsl(152 95% 74%)'` | Glaze's own emit format. Alpha dropped with warning. |
307
309
  | `oklch()` | `'oklch(0.85 0.18 152)'` | Glaze's own emit format. Alpha dropped with warning. |
308
310
  | `OkhslColor` object | `{ h: 152, s: 0.95, l: 0.74 }` | Glaze's native shape (h: 0–360, s/l: 0–1). Passing 0–100 for `s`/`l` throws with a hint to use the structured form. |
309
- | RGB tuple | `[38, 252, 178]` | 0–255, same range as `glaze.fromRgb`. |
311
+ | `RgbColor` object | `{ r: 38, g: 252, b: 178 }` | sRGB 0–255. RGB tuple `[r, g, b]` is not supported — use this object form. |
312
+ | `OklchColor` object | `{ l: 0.85, c: 0.18, h: 152 }` | OKLCh (L/C: 0–1, H: degrees), same semantics as `oklch()` strings. |
310
313
 
311
314
  `GlazeColorInput` (the structured input) is `{ hue, saturation, lightness, ... }`:
312
315
 
@@ -328,11 +331,11 @@ Named CSS colors (`'red'`, `'blueviolet'`) are not supported.
328
331
 
329
332
  Every input form defaults to `mode: 'auto'` so the resolved token adapts between light and dark like an ordinary theme color. The *scaling* snapshot taken at create time differs by input form:
330
333
 
331
- - **String value-shorthand** (`'#000'`, `'rgb(...)'`, etc.):
334
+ - **Value-shorthand** (hex, `rgb()` / `hsl()` / `okhsl()` / `oklch()` strings, `{ r, g, b }`, `{ h, s, l }`, `{ l, c, h }`):
332
335
  - Light variant preserves the input lightness exactly (`lightLightness: false`).
333
- - Dark variant is Möbius-inverted into `[globalConfig.darkLightness[0], 100]`, so `glaze.color('#000')` renders as `#fff` in dark mode and `glaze.color('#fff')` falls to the dark `lo` floor (default `0.15`).
334
- - **Object / tuple / structured inputs**:
335
- - Both light and dark variants are mapped through `globalConfig.lightLightness` / `globalConfig.darkLightness` (defaults `[10, 100]` / `[15, 95]`) — the same windows a theme color uses.
336
+ - Dark variant uses `globalConfig.darkLightness` (default `[15, 95]`), snapshotted at create time.
337
+ - **Structured input** (`{ hue, saturation, lightness, ... }`):
338
+ - Both variants use `globalConfig.lightLightness` / `globalConfig.darkLightness` (defaults `[10, 100]` / `[15, 95]`) — same as a theme color.
336
339
  - All windows are **snapshotted at color-creation time** so later `glaze.configure()` calls don't retroactively change exported tokens. `token.export()` round-trips byte-for-byte.
337
340
 
338
341
  To opt back into the legacy fixed-linear default (no Möbius inversion), pass `{ mode: 'fixed' }` as the second arg, or supply an explicit `scaling` (see [`GlazeColorScaling`](#glazecolorscaling)).
@@ -357,10 +360,10 @@ Overrides for the value-shorthand overload's second argument:
357
360
 
358
361
  Per-call lightness-window override. Mirrors `GlazeConfig`'s field names:
359
362
 
360
- | Key | Default for string input | Default for object / tuple / structured input | Effect |
363
+ | Key | Default for value-shorthand | Default for structured input | Effect |
361
364
  |---|---|---|---|
362
365
  | `lightLightness` | `false` | `globalConfig.lightLightness` (snapshotted) | `false` = preserve input. Pass `[lo, hi]` to opt into a remap window. |
363
- | `darkLightness` | `[globalConfig.darkLightness[0], 100]` (snapshotted) | `globalConfig.darkLightness` (snapshotted) | `false` = preserve input in dark too. Pass `[lo, hi]` to override the window. |
366
+ | `darkLightness` | `globalConfig.darkLightness` (snapshotted) | `globalConfig.darkLightness` (snapshotted) | `false` = preserve input in dark too. Pass `[lo, hi]` to override the window (e.g. `[15, 100]` for a `#000` → white dark flip). |
364
367
 
365
368
  Passing `scaling` is **all-or-nothing** — both fields are replaced. To keep one field's default while overriding the other, restate the default explicitly.
366
369
 
@@ -368,10 +371,10 @@ Passing `scaling` is **all-or-nothing** — both fields are replaced. To keep on
368
371
  // Preserve raw lightness in dark mode too
369
372
  glaze.color('#26fcb2', undefined, { darkLightness: false });
370
373
 
371
- // Opt back into a theme-style window
372
- glaze.color('#26fcb2', undefined, {
374
+ // Opt into theme-style light remap + extended dark (e.g. #000 → white in dark)
375
+ glaze.color('#000000', undefined, {
373
376
  lightLightness: [10, 100],
374
- darkLightness: [15, 95],
377
+ darkLightness: [15, 100],
375
378
  });
376
379
 
377
380
  // Structured form takes scaling as the second positional arg
@@ -931,6 +934,7 @@ glaze.configure({
931
934
  | `modes.dark` | `true` | Include dark variants in exports. |
932
935
  | `modes.highContrast` | `false` | Include HC variants. |
933
936
  | `shadowTuning` | `undefined` | Default tuning for all shadow colors. Per-color tuning merges field-by-field. |
937
+ | `autoFlip` | `true` | When solving `contrast`, allow the solver to switch away from the requested lightness direction if that side can't meet the target. With `false`, only the requested direction is considered; unmet contrasts pin the lightness to that direction's extreme (and emit a warning). |
934
938
 
935
939
  | Method | Description |
936
940
  |---|---|
@@ -1070,5 +1074,7 @@ import {
1070
1074
  | `lightnessRange` | `[0, 1]` | Search bounds. |
1071
1075
  | `epsilon` | `1e-4` | Convergence threshold. |
1072
1076
  | `maxIterations` | `14` | Max binary-search iterations per branch. |
1077
+ | `initialDirection` | higher-contrast side | Direction to search first (`'lighter'` or `'darker'`). Theme resolution sets this from the requested lightness relative to the base color. |
1078
+ | `flip` | `false` | When `true`, try the opposite direction if the initial one doesn't meet the target. When `false`, only the initial direction is searched — unmet contrasts pin the result to that direction's extreme. |
1073
1079
 
1074
- Result: `{ lightness, contrast, met, branch: 'lighter' | 'darker' | 'preferred' }`.
1080
+ Result: `{ lightness, contrast, met, branch: 'lighter' | 'darker' | 'preferred', flipped? }`. `flipped: true` indicates the initial direction failed and the opposite direction satisfied the target.
@@ -10,6 +10,8 @@ The default theme is what most components consume — its tokens are emitted unp
10
10
 
11
11
  You design the default theme once, and `extend()` propagates that design across every status hue.
12
12
 
13
+ Every color definition has an **`inherit`** flag (default: `true`) controlling whether it flows into child themes via `extend()`. Set `inherit: false` to scope a color to its parent theme only — this is how sibling themes stay lean, carrying only the tokens they actually need.
14
+
13
15
  ## Hue / saturation seeds
14
16
 
15
17
  Declare hues as named constants up top, plus a single shared seed saturation:
@@ -123,15 +125,15 @@ The disabled chip + label pair uses `mode: 'auto'` and **explicit numeric contra
123
125
  ```ts
124
126
  'disabled-surface': {
125
127
  base: 'surface', lightness: '-1', saturation: 0.2,
126
- contrast: [1.1, 1.2], inherit: false,
128
+ contrast: [1.5, 2], inherit: false,
127
129
  },
128
130
  'disabled-surface-text': {
129
- base: 'surface', lightness: '-1', saturation: 0.3,
130
- contrast: [2, 2.5], inherit: false,
131
+ base: 'disabled-surface', lightness: '+1', saturation: 0.3,
132
+ contrast: 3, inherit: false,
131
133
  },
132
134
  ```
133
135
 
134
- These tokens *anchor a perceived ratio against `surface`*, so the disabled state resolves to the same numbers in light, dark, and HC (chip ≈ 1.4 vs surface, label ≈ 2.0, text-on-chip ≈ 1.4). An alpha-tinted overlay would have asymmetric behavior — composited alpha against a near-white light surface produces a much weaker chip than the same overlay against a near-dark dark surface, and the disabled state would stop *looking* disabled in one of the schemes.
136
+ Each token anchors to its immediate parent surface — `*-surface` contrasts against the root `surface`, while `*-surface-text` contrasts against its own chip (`disabled-surface`). This keeps the disabled state self-contained and resolves to consistent ratios in light, dark, and HC (chip ≈ 1.5–2× vs surface, label ≈ on chip). An alpha-tinted overlay would have asymmetric behavior — composited alpha against a near-white light surface produces a much weaker chip than the same overlay against a near-dark dark surface, and the disabled state would stop *looking* disabled in one of the schemes.
135
137
 
136
138
  The general rule: when a color needs to *feel the same across schemes*, anchor it with `mode: 'auto'` + a numeric contrast against a surface, not with a preset.
137
139
 
@@ -143,7 +145,9 @@ The general rule: when a color needs to *feel the same across schemes*, anchor i
143
145
  },
144
146
  ```
145
147
 
146
- `mode: 'fixed'` skips the dark-scheme Möbius inversion and only does a linear window mapping, so `surface-inverse` reads as a dark surface in *every* scheme — light, dark, and HC. Use it for tooltips, code blocks, popovers with their own dark theme. Pair with `#white` for foreground text.
148
+ `mode: 'fixed'` skips the dark-scheme Möbius inversion and only does a linear window mapping, so `surface-inverse` reads as a dark surface in *every* scheme — light, dark, and HC. In high-contrast variants the window is bypassed entirely (identity), so the color stays at its raw lightness across all four schemes.
149
+
150
+ Use it for tooltips, code blocks, popovers with their own dark theme. Pair with `#white` for foreground text.
147
151
 
148
152
  This is the canonical "I want this color to stay recognizable" pattern. The other `mode: 'fixed'` use is the entire accent system below.
149
153
 
@@ -197,6 +201,8 @@ Mirrors the neutral disabled pair from above but with higher saturation so the c
197
201
  },
198
202
  ```
199
203
 
204
+ The HC pair `[1.4, 1.3]` is intentionally *lower* in high-contrast mode — the tinted chip naturally gains more contrast against `surface` when the lightness window bypasses (identity mapping), so we loosen the constraint to leave room for stronger text-on-chip contrast. The text token uses `contrast: 1.51`, which is the maximum value that stays below Glaze's auto-flip threshold (the solver would otherwise invert the color past the midpoint, producing a result on the wrong side of its base). This keeps the label legible without flipping into an unexpected hue.
205
+
200
206
  These are inherited (no `inherit: false`), so each colored sibling theme automatically emits `<theme>-accent-disabled-surface` and `<theme>-accent-disabled-surface-text`. PRIMARY-style disabled buttons stay tinted with the active theme's hue (danger-tinted danger button, success-tinted success button), preserving brand identity even in the disabled state.
201
207
 
202
208
  ## Per-color hue overrides (code highlighting)
@@ -206,7 +212,7 @@ The `code-*` tokens use **absolute `hue` numbers** regardless of the seed. Each
206
212
  ```ts
207
213
  'code-comment': { base: 'surface', hue: 280, saturation: 0.1, lightness: '-1', contrast: [4.5, 7], inherit: false },
208
214
  'code-keyword': { base: 'surface', hue: 348, saturation: 1, lightness: '-1', contrast: [5, 7.5], inherit: false },
209
- 'code-string': { base: 'surface', hue: PURPLE_HUE, saturation: 1, lightness: '-1', contrast: [4.5, 7], inherit: false },
215
+ 'code-string': { base: 'surface', hue: SUCCESS_HUE, saturation: 1, lightness: '-1', contrast: [4.5, 7], inherit: false },
210
216
  // …code-punctuation, code-number, code-function, code-attribute follow the same shape
211
217
  ```
212
218
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tenphi/glaze",
3
- "version": "0.11.1",
3
+ "version": "0.12.0",
4
4
  "description": "OKHSL-based color theme generator with WCAG contrast solving for light, dark, and high-contrast schemes",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",