Skip to content

Commit 88be8a4

Browse files
authored
feat(color): default mode 'auto' for non-string glaze.color() inputs (#54)
Object, RGB tuple, and structured `glaze.color()` inputs now default to `mode: 'auto'` with both lightness windows snapshotted from `globalConfig`, matching how a theme color resolves. String inputs are unchanged. Pass `{ mode: 'fixed' }` to opt back into the previous linear mapping.
1 parent 4ffa6ec commit 88be8a4

5 files changed

Lines changed: 209 additions & 79 deletions

File tree

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@tenphi/glaze': minor
3+
---
4+
5+
`glaze.color()` now defaults to `mode: 'auto'` across every input form, so non-string inputs adapt between light and dark like an ordinary theme color instead of being preserved verbatim with a linear dark mapping.
6+
7+
- **Object value-shorthand** (`{ h, s, l }`), **RGB tuple** (`[r, g, b]`), and **structured form** (`{ hue, saturation, lightness, ... }`) now default to `mode: 'auto'` with snapshotted scaling `{ lightLightness: globalConfig.lightLightness, darkLightness: globalConfig.darkLightness }`. The dark variant is Möbius-inverted into `globalConfig.darkLightness` (default `[15, 95]`), and the light variant is mapped through `globalConfig.lightLightness` (default `[10, 100]`) — exactly the same windows a theme color uses.
8+
- **String value-shorthand** (hex / `rgb()` / `hsl()` / `okhsl()` / `oklch()`) is unchanged. It already defaulted to `mode: 'auto'` with `{ lightLightness: false, darkLightness: [lo, 100] }`, preserving the `#000``#fff` flip.
9+
10+
**Behavior change (minor bump):**
11+
12+
- `glaze.color({ hue: H, saturation: S, lightness: 80 }).resolve()` (and the equivalent object / tuple forms) now produces a near-dark `dark.l` (e.g. ~`0.42` for `lightness: 80` under defaults) instead of staying near `0.79`.
13+
- `light.l` for object / tuple / structured inputs is now mapped through `globalConfig.lightLightness` rather than preserved verbatim (e.g. `lightness: 0` now resolves to `light.l ≈ 0.10` by default).
14+
- To restore the previous fixed-linear behavior, pass `{ mode: 'fixed' }` on the input or in the overrides. To restore the previous "preserve light lightness verbatim" behavior, pass `{ lightLightness: false }` as the trailing `scaling` argument.
15+
16+
The new scaling shape is also reflected in `token.export()` snapshots — object / tuple / structured tokens now serialize `{ lightLightness: [10, 100], darkLightness: [15, 95] }` (with the live `globalConfig` values frozen at create time) instead of `{ lightLightness: false, darkLightness: [15, 95] }`. Rehydration via `glaze.colorFrom()` round-trips byte-for-byte.

README.md

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -282,27 +282,33 @@ adapt to both lightness windows. The defaults vary by input form,
282282
because string inputs are typically end-user values (color pickers,
283283
theme settings) where natural light/dark inversion is the expectation:
284284

285+
Every input form defaults to **`mode: 'auto'`** so the resolved token
286+
adapts between light and dark like an ordinary theme color. The
287+
*scaling* snapshot taken at create time differs by input form:
288+
285289
- **String value-shorthand** (hex, `rgb()`, `hsl()`, `okhsl()`,
286290
`oklch()`):
287291
- Light variant preserves the input lightness exactly.
288292
- Dark variant is **Möbius-inverted** into `[globalConfig.darkLightness[0], 100]`,
289293
so `glaze.color('#000')` renders as `#fff` in dark mode and
290294
`glaze.color('#fff')` falls to the dark `lo` floor (default `0.15`).
291-
- Adaptation mode defaults to `'auto'`.
292295
- The dark `lo` is snapshotted from `globalConfig` at color-creation
293296
time, matching how an explicit `scaling.darkLightness: [lo, hi]`
294297
behaves.
295298

296299
- **Object / tuple value-shorthand** (`{ h, s, l }`, `[r, g, b]`) and
297300
the **structured form** (`{ hue, saturation, lightness, ... }`):
298-
- Light variant preserves the input lightness exactly.
299-
- Dark variant is linearly mapped into `globalConfig.darkLightness`
300-
(default `[15, 95]`), snapshotted at color-creation time so later
301+
- Both light and dark variants are mapped through
302+
`globalConfig.lightLightness` / `globalConfig.darkLightness`
303+
(defaults `[10, 100]` / `[15, 95]`) — the same windows a theme color
304+
uses. With the `'auto'` default the dark variant is Möbius-inverted
305+
into that dark window, so a near-white seed lands at a near-dark
306+
dark variant.
307+
- Both windows are snapshotted at color-creation time so later
301308
`glaze.configure()` calls don't retroactively change exported tokens.
302-
- Adaptation mode defaults to `'fixed'` (linear, no Möbius curve).
303309

304-
To opt back into the old fixed-linear default for string inputs, pass
305-
either `{ mode: 'fixed' }` as the second arg, or supply an explicit
310+
To opt back into the legacy fixed-linear default (no Möbius inversion),
311+
pass `{ mode: 'fixed' }` as the second arg, or supply an explicit
306312
`scaling` as the third arg (see [Lightness scaling](#lightness-scaling)).
307313

308314
```ts
@@ -373,7 +379,7 @@ All overrides:
373379
| `saturation` | Override seed saturation (0–100) |
374380
| `lightness` | Number (absolute 0–100) or `'+N'`/`'-N'`. Without `base`, relative is anchored to the literal seed; with `base`, anchored to `base`'s lightness per scheme. Supports `[normal, hc]` pairs |
375381
| `saturationFactor` | Multiplier on seed (0–1, default 1) |
376-
| `mode` | `'auto'` (default for string inputs) / `'fixed'` (default for object / tuple / structured inputs) / `'static'` — see [Adaptation Modes](#adaptation-modes) |
382+
| `mode` | `'auto'` (default for every input form) / `'fixed'` / `'static'` — see [Adaptation Modes](#adaptation-modes) |
377383
| `contrast` | WCAG floor. Without `base`, anchored to the literal seed; with `base`, solved per scheme against `base`'s resolved variant. Same shape as `RegularColorDef.contrast`. When the target can't be physically met, `glaze` emits a `console.warn` and returns the closest passing variant |
378384
| `base` | Another `glaze.color()` token **or** a raw `GlazeColorValue` (hex / `rgb()` / `OkhslColor` / `[r, g, b]`). Raw values are auto-wrapped via `glaze.color(value)` so they pick up the same auto-invert defaults as an explicit wrap. When set, `contrast` and relative `lightness` anchor to it per scheme; relative `hue` still anchors to the seed |
379385
| `opacity` | Fixed alpha 0–1 applied to every variant. Surfaces in `rgb(... / A)`, `okhsl(... / A)`, etc. Combining with `contrast` is not recommended (perceived lightness becomes unpredictable) — `glaze` emits a `console.warn` |
@@ -1423,7 +1429,7 @@ brand.colors({ surface: { lightness: 97 }, text: { base: 'surface', lightness: '
14231429
| `glaze.fromHex(hex)` | Create a theme from a hex color (`#rgb` or `#rrggbb`) |
14241430
| `glaze.fromRgb(r, g, b)` | Create a theme from RGB values (0–255) |
14251431
| `glaze.color(input, scaling?)` | Create a standalone color token from `{ hue, saturation, lightness, opacity?, contrast?, base?, name?, ... }`. Optional `scaling` overrides the lightness windows |
1426-
| `glaze.color(value, overrides?, scaling?)` | Create a standalone color token from a hex string (3/6/8 digits), an `rgb()` / `hsl()` / `okhsl()` / `oklch()` string, an `{ h, s, l }` OKHSL object, or an `[r, g, b]` (0–255) tuple. Overrides accept absolute or relative `hue` / `lightness`, `saturation`, `mode`, `contrast`, `opacity`, `name`, and `base` (a `GlazeColorToken` or any `GlazeColorValue`; raw values are auto-wrapped). When `base` is set, `contrast` and relative `lightness` are anchored to the base per scheme — see [Pairing Colors](#pairing-colors). String inputs default to `mode: 'auto'` with the dark window extended to upper `100`; object / tuple inputs default to `mode: 'fixed'`. |
1432+
| `glaze.color(value, overrides?, scaling?)` | Create a standalone color token from a hex string (3/6/8 digits), an `rgb()` / `hsl()` / `okhsl()` / `oklch()` string, an `{ h, s, l }` OKHSL object, or an `[r, g, b]` (0–255) tuple. Overrides accept absolute or relative `hue` / `lightness`, `saturation`, `mode`, `contrast`, `opacity`, `name`, and `base` (a `GlazeColorToken` or any `GlazeColorValue`; raw values are auto-wrapped). When `base` is set, `contrast` and relative `lightness` are anchored to the base per scheme — see [Pairing Colors](#pairing-colors). Every input form defaults to `mode: 'auto'`; string inputs additionally preserve light lightness exactly and extend the dark window to `[lo, 100]`, while object / tuple / structured inputs snapshot both windows from `globalConfig.lightLightness` / `globalConfig.darkLightness`. |
14271433
| `glaze.colorFrom(data)` | Rehydrate a `glaze.color()` token from a `.export()` snapshot. Inverse of `token.export()` — see [Persisting Standalone Colors](#persisting-standalone-colors) |
14281434
| `glaze.shadow(input)` | Compute a standalone shadow color (returns `ResolvedColorVariant`). `bg` / `fg` accept any `GlazeColorValue` form |
14291435
| `glaze.format(variant, format?)` | Format any `ResolvedColorVariant` as a CSS string |

0 commit comments

Comments
 (0)