|
1 | 1 | # @tenphi/glaze |
2 | 2 |
|
| 3 | +## 0.10.0 |
| 4 | + |
| 5 | +### Minor Changes |
| 6 | + |
| 7 | +- [#50](https://github.com/tenphi/glaze/pull/50) [`6e2d42d`](https://github.com/tenphi/glaze/commit/6e2d42dac6aa571ec02636bd029c662b2bf7fa3f) Thanks [@tenphi](https://github.com/tenphi)! - Revamp `glaze.color()` with a value-shorthand overload, seed-anchored |
| 8 | + contrast solving, a per-call lightness-scaling argument, and a `.css()` |
| 9 | + export. `glaze.shadow()` now accepts the same value forms as `glaze.color()`. |
| 10 | + |
| 11 | + **New defaults for `glaze.color()`** — split by input form so end-user |
| 12 | + string values (color picker / theme settings) get a natural light/dark |
| 13 | + inversion, while programmatic object / tuple / structured inputs keep |
| 14 | + predictable linear behavior: |
| 15 | + - **String value-shorthand** (hex, `rgb()`, `hsl()`, `okhsl()`, |
| 16 | + `oklch()`): `mode: 'auto'` with snapshotted scaling |
| 17 | + `{ lightLightness: false, darkLightness: [globalConfig.darkLightness[0], 100] }`. |
| 18 | + Light preserves the input exactly; dark Möbius-inverts up to `100`, |
| 19 | + so `glaze.color('#000')` renders as `#fff` in dark mode and |
| 20 | + `glaze.color('#fff')` falls to the dark `lo` floor (default `0.15`). |
| 21 | + The dark `lo` is snapshotted from `globalConfig` at color-creation |
| 22 | + time, matching how an explicit `scaling.darkLightness: [lo, hi]` |
| 23 | + behaves. |
| 24 | + - **Object / tuple value-shorthand** (`{ h, s, l }`, `[r, g, b]`) and |
| 25 | + **structured form**: `mode: 'fixed'` with light preserved and dark |
| 26 | + linearly mapped into `globalConfig.darkLightness` (default `[15, 95]`), |
| 27 | + also snapshotted at create time so later `glaze.configure()` calls |
| 28 | + don't retroactively change already-created tokens. |
| 29 | + - Override per call via the new third positional argument |
| 30 | + `GlazeColorScaling`: `{ lightLightness?: false | [lo, hi]; darkLightness?: false | [lo, hi] }`. |
| 31 | + `false` disables the remap, a tuple sets a custom window. To opt |
| 32 | + string inputs back into the previous fixed-linear default, pass |
| 33 | + `{ mode: 'fixed' }` as the second arg or supply an explicit |
| 34 | + `scaling`. |
| 35 | + |
| 36 | + **Behavior change (minor bump):** |
| 37 | + - String value-shorthand callers will see a Möbius-inverted dark |
| 38 | + variant by default — `glaze.color('#000').resolve().dark.l` is now |
| 39 | + `≈ 1.0`, not `0.15`. To preserve the old fixed-linear behavior pass |
| 40 | + `{ mode: 'fixed' }` as the second argument. |
| 41 | + - Structured callers without an explicit `mode` will see |
| 42 | + `glaze.color({...}).resolve().light.l` match the input lightness |
| 43 | + exactly instead of being remapped to `globalConfig.lightLightness`. |
| 44 | + To preserve the old behavior pass |
| 45 | + `{ lightLightness: globalConfig.lightLightness }` as the second |
| 46 | + argument. |
| 47 | + - The default lightness windows for object / tuple / structured |
| 48 | + inputs are now snapshotted from `globalConfig.darkLightness` at |
| 49 | + color-creation time, matching the existing behavior for string |
| 50 | + inputs. Tokens created before a `glaze.configure()` call no longer |
| 51 | + pick up the new dark window on their next `.resolve()`. To get the |
| 52 | + old "live config" behavior, recreate the token after `configure()`. |
| 53 | + |
| 54 | + **Value shorthand additions:** |
| 55 | + - Accepts hex (`#rgb` / `#rrggbb` / `#rrggbbaa`), the four CSS color |
| 56 | + functions Glaze itself emits (`rgb()`, `hsl()`, `okhsl()`, `oklch()`), |
| 57 | + `OkhslColor` objects (`{ h, s, l }`), and `[r, g, b]` (0–255) tuples |
| 58 | + as the first argument. Every string emitted by `theme.tasty() / .json() / .css()` |
| 59 | + round-trips back through `glaze.color()`. |
| 60 | + - 8-digit hex and `rgba()` / `hsla()` / slash-alpha alpha components are |
| 61 | + parsed and dropped with a `console.warn` (standalone colors have no |
| 62 | + opacity field). |
| 63 | + - `oklch()` chroma now correctly interprets percent values per CSS Color 4 |
| 64 | + (`100% → 0.4`). |
| 65 | + - `OkhslColor` and `[r, g, b]` inputs are validated up front with helpful |
| 66 | + error messages — passing 0–100-scale `s`/`l` throws with a hint to use |
| 67 | + the structured form, and out-of-range RGB tuples throw with the offending |
| 68 | + value in the message. |
| 69 | + |
| 70 | + **Anchor model:** by default, relative `lightness: '+N'` and |
| 71 | + `contrast: <ratio>` are anchored to the literal seed (the value passed |
| 72 | + to `glaze.color()`), so the contrast solver compares against the |
| 73 | + unmapped user-provided color across every variant. Pass |
| 74 | + `overrides.base` (a `GlazeColorToken`) to anchor against another |
| 75 | + color's resolved variant per scheme instead. |
| 76 | + |
| 77 | + **Color pairing via `base`:** `GlazeColorOverrides.base` lets one |
| 78 | + standalone color depend on another. Accepts either a `GlazeColorToken` |
| 79 | + or any `GlazeColorValue` (hex / `rgb()` / `OkhslColor` / `[r, g, b]`); |
| 80 | + raw values are auto-wrapped via `glaze.color(value)` and inherit the |
| 81 | + same string-vs-object defaults. When set: |
| 82 | + - `contrast` is solved per scheme against the base's resolved variant |
| 83 | + (light / dark / lightContrast / darkContrast). |
| 84 | + - Relative `lightness: '+N'` / `'-N'` is anchored to the base's |
| 85 | + lightness per scheme (matches theme behavior for dependent colors). |
| 86 | + - Relative `hue: '+N'` still anchors to the seed (the value passed to |
| 87 | + `glaze.color()`), not the base. |
| 88 | + - `mode` is the per-pair knob — pass `mode: 'fixed'` to disable Möbius |
| 89 | + inversion for the dependent color, `mode: 'auto'` to keep it. |
| 90 | + |
| 91 | + The base token's `.resolve()` is called lazily on first resolve and |
| 92 | + the result is captured by reference, matching existing snapshot |
| 93 | + semantics. Internally, `resolveAllColors` accepts pre-resolved |
| 94 | + external bases and seeds them into the resolution context; |
| 95 | + `validateColorDefs` and `topoSort` treat external base names as leaves. |
| 96 | + |
| 97 | + **`opacity` and `name` on `glaze.color()`:** |
| 98 | + - `GlazeColorOverrides.opacity` (and the same field on |
| 99 | + `GlazeColorInput`) sets a fixed alpha 0–1 that surfaces in every |
| 100 | + scheme variant. Combining with `contrast` is not recommended (perceived |
| 101 | + lightness becomes unpredictable) — `glaze` emits a `console.warn` in |
| 102 | + that case. |
| 103 | + - `GlazeColorOverrides.name` (and the same field on `GlazeColorInput`) |
| 104 | + is a human-readable label that surfaces in error and warning messages |
| 105 | + in place of the internal `"value"` sentinel. Empty / whitespace-only |
| 106 | + names and reserved internal names (`"value"`, `"seed"`, |
| 107 | + `"externalBase"`) are rejected with a clear error. |
| 108 | + |
| 109 | + **Structured form parity:** the `glaze.color({...})` overload now |
| 110 | + accepts `opacity`, `contrast`, `base`, and `name` in addition to the |
| 111 | + existing `hue`, `saturation`, `lightness`, `saturationFactor`, and |
| 112 | + `mode`. `contrast` without `base` synthesizes a hidden static seed |
| 113 | + from the input's normal-mode lightness so the contrast solver always |
| 114 | + has an anchor (mirrors value-form behavior). `hue` (finite), |
| 115 | + `saturation` / `lightness` (0–100), `saturationFactor` (0–1), and |
| 116 | + `opacity` (0–1) are range-checked up front with helpful error |
| 117 | + messages — non-finite or out-of-range values fail at creation rather |
| 118 | + than producing a NaN-laden token. |
| 119 | + |
| 120 | + **Contrast warning:** when the contrast solver cannot meet the |
| 121 | + requested target (e.g. AAA against a mid-grey base — physically |
| 122 | + unreachable), `glaze` emits a single `console.warn` per |
| 123 | + `(name, scheme, target)` triple naming the affected color, scheme, and |
| 124 | + the actual achieved ratio. The token still resolves to the closest |
| 125 | + passing variant. Use the `name` override to make the warning easier to |
| 126 | + trace. |
| 127 | + |
| 128 | + **Persisting standalone colors:** `token.export()` returns a JSON-safe |
| 129 | + snapshot containing the original `value` (or structured input), the |
| 130 | + overrides, and the captured `scaling`. Token-typed `base` is |
| 131 | + recursively serialized; value-typed `base` is preserved as the raw |
| 132 | + value. Pass the result to `glaze.colorFrom(data)` to rehydrate a token |
| 133 | + that resolves byte-for-byte identically to the original — across |
| 134 | + `glaze.configure()` calls and across processes. The captured `scaling` |
| 135 | + snapshots both `lightLightness` and `darkLightness` from `globalConfig` |
| 136 | + at create time, so later `glaze.configure()` calls don't retroactively |
| 137 | + change exported tokens regardless of input form. |
| 138 | + |
| 139 | + **`.css({ name })` export:** new method on the standalone color token |
| 140 | + reaches export parity with `theme.css()`. Existing |
| 141 | + `.token() / .tasty() / .json()` continue to work unchanged. |
| 142 | + |
| 143 | + **`glaze.shadow()` upgrade:** `bg` and `fg` now accept any |
| 144 | + `GlazeColorValue` form — hex, `rgb()` / `hsl()` / `okhsl()` / `oklch()` |
| 145 | + strings, `OkhslColor` objects, or `[r, g, b]` tuples — sharing the same |
| 146 | + parser as `glaze.color()`. |
| 147 | + |
| 148 | + **Internal:** standalone color tokens now memoize the underlying resolve |
| 149 | + across `.resolve() / .token() / .tasty() / .json() / .css()` calls. |
| 150 | + |
| 151 | + **Public type additions:** `GlazeColorValue`, `GlazeColorOverrides`, |
| 152 | + `GlazeColorOverridesExport`, `GlazeColorCssOptions`, |
| 153 | + `GlazeColorScaling`, `GlazeColorTokenExport`, `GlazeColorInputExport`. |
| 154 | + New `glaze.colorFrom(data)` factory and `token.export()` method on |
| 155 | + `GlazeColorToken`. New `hslToSrgb`, `oklabToOkhsl`, and `parseHexAlpha` |
| 156 | + math helpers re-exported from the package root. |
| 157 | + |
3 | 158 | ## 0.9.3 |
4 | 159 |
|
5 | 160 | ### Patch Changes |
|
0 commit comments