@marianmeres/design-tokens 1.11.0 → 1.13.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/AGENTS.md +46 -25
- package/API.md +192 -7
- package/README.md +83 -4
- package/dist/color.d.ts +97 -0
- package/dist/color.js +535 -0
- package/dist/css/amber-olive-safari.css +99 -6
- package/dist/css/blue-orange.css +99 -6
- package/dist/css/cyan-red.css +99 -6
- package/dist/css/cyan-slate.css +99 -6
- package/dist/css/disco.css +99 -6
- package/dist/css/electric-lemonade.css +99 -6
- package/dist/css/emerald-amber-forest.css +99 -6
- package/dist/css/emerald-pink.css +99 -6
- package/dist/css/fuchsia-emerald.css +99 -6
- package/dist/css/gray.css +99 -6
- package/dist/css/hummingbird.css +99 -6
- package/dist/css/indigo-amber.css +99 -6
- package/dist/css/lime-fuchsia-neon.css +99 -6
- package/dist/css/mauve-lime-electric.css +99 -6
- package/dist/css/mauve-teal.css +99 -6
- package/dist/css/mauve.css +99 -6
- package/dist/css/mist-indigo-fjord.css +99 -6
- package/dist/css/mist-violet-aurora.css +99 -6
- package/dist/css/mist.css +99 -6
- package/dist/css/monokai-cyan.css +99 -6
- package/dist/css/monokai-green.css +99 -6
- package/dist/css/monokai-pink.css +99 -6
- package/dist/css/nebula.css +99 -6
- package/dist/css/old-glory.css +284 -0
- package/dist/css/olive-amber-safari.css +99 -6
- package/dist/css/olive.css +99 -6
- package/dist/css/orange-pink-sunset.css +99 -6
- package/dist/css/peacock.css +99 -6
- package/dist/css/pink-emerald.css +99 -6
- package/dist/css/pink-teal.css +99 -6
- package/dist/css/purple-yellow.css +99 -6
- package/dist/css/rainbow.css +99 -6
- package/dist/css/red-blue.css +99 -6
- package/dist/css/red-cyan.css +99 -6
- package/dist/css/red-sky-slate.css +99 -6
- package/dist/css/red-sky.css +99 -6
- package/dist/css/rose-teal.css +99 -6
- package/dist/css/sky-amber.css +99 -6
- package/dist/css/slate-cyan.css +99 -6
- package/dist/css/slate-teal-ocean.css +99 -6
- package/dist/css/stone-orange-earth.css +99 -6
- package/dist/css/stone.css +99 -6
- package/dist/css/taupe-olive-clay.css +99 -6
- package/dist/css/taupe-rose-blush.css +99 -6
- package/dist/css/taupe.css +99 -6
- package/dist/css/teal-rose.css +99 -6
- package/dist/css/velvet.css +99 -6
- package/dist/css/violet-lime.css +99 -6
- package/dist/css/violet-rose-dusk.css +99 -6
- package/dist/css/watermelon.css +99 -6
- package/dist/css/zinc.css +99 -6
- package/dist/generate.d.ts +104 -3
- package/dist/generate.js +134 -30
- package/dist/mod.d.ts +26 -1
- package/dist/mod.js +16 -1
- package/dist/reboot-bridge.d.ts +14 -6
- package/dist/reboot-bridge.js +32 -10
- package/dist/themes/amber-olive-safari.js +3 -2
- package/dist/themes/blue-orange.js +3 -2
- package/dist/themes/cyan-red.js +3 -2
- package/dist/themes/cyan-slate.js +3 -2
- package/dist/themes/disco.js +3 -2
- package/dist/themes/electric-lemonade.js +3 -2
- package/dist/themes/emerald-amber-forest.js +3 -2
- package/dist/themes/emerald-pink.js +3 -2
- package/dist/themes/fuchsia-emerald.js +3 -2
- package/dist/themes/gray.js +3 -2
- package/dist/themes/hummingbird.js +3 -2
- package/dist/themes/indigo-amber.js +3 -2
- package/dist/themes/lime-fuchsia-neon.js +3 -2
- package/dist/themes/mauve-lime-electric.js +3 -2
- package/dist/themes/mauve-teal.js +3 -2
- package/dist/themes/mauve.js +3 -2
- package/dist/themes/mist-indigo-fjord.js +3 -2
- package/dist/themes/mist-violet-aurora.js +3 -2
- package/dist/themes/mist.js +3 -2
- package/dist/themes/mod.d.ts +3 -1
- package/dist/themes/mod.js +5 -0
- package/dist/themes/monokai-cyan.js +3 -2
- package/dist/themes/monokai-green.js +3 -2
- package/dist/themes/monokai-pink.js +3 -2
- package/dist/themes/nebula.js +3 -2
- package/dist/themes/old-glory.d.ts +4 -0
- package/dist/themes/old-glory.js +119 -0
- package/dist/themes/olive-amber-safari.js +3 -2
- package/dist/themes/olive.js +3 -2
- package/dist/themes/orange-pink-sunset.js +3 -2
- package/dist/themes/peacock.js +3 -2
- package/dist/themes/pink-emerald.js +3 -2
- package/dist/themes/pink-teal.js +3 -2
- package/dist/themes/purple-yellow.js +3 -2
- package/dist/themes/rainbow.js +3 -2
- package/dist/themes/red-blue.js +3 -2
- package/dist/themes/red-cyan.js +3 -2
- package/dist/themes/red-sky-slate.js +3 -2
- package/dist/themes/red-sky.js +3 -2
- package/dist/themes/rose-teal.js +3 -2
- package/dist/themes/sky-amber.js +3 -2
- package/dist/themes/slate-cyan.js +3 -2
- package/dist/themes/slate-teal-ocean.js +3 -2
- package/dist/themes/stone-orange-earth.js +3 -2
- package/dist/themes/stone.js +3 -2
- package/dist/themes/taupe-olive-clay.js +3 -2
- package/dist/themes/taupe-rose-blush.js +3 -2
- package/dist/themes/taupe.js +3 -2
- package/dist/themes/teal-rose.js +3 -2
- package/dist/themes/velvet.js +3 -2
- package/dist/themes/violet-lime.js +3 -2
- package/dist/themes/violet-rose-dusk.js +3 -2
- package/dist/themes/watermelon.js +3 -2
- package/dist/themes/zinc.js +3 -2
- package/package.json +1 -1
package/AGENTS.md
CHANGED
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
|
|
16
16
|
CSS custom property generator for design token systems. Takes a structured
|
|
17
17
|
`ThemeSchema` (light + optional dark mode) and produces CSS `--custom-property`
|
|
18
|
-
declarations with automatic hover/active state derivation via `color-mix()
|
|
18
|
+
declarations with automatic hover/active state derivation via `color-mix()`, plus a
|
|
19
|
+
precomputed `@supports` fallback so the output still renders on engines that predate
|
|
20
|
+
`color-mix()` (Chrome/Edge < 111, Safari < 16.2, Firefox < 113).
|
|
19
21
|
|
|
20
22
|
Entry points:
|
|
21
23
|
|
|
@@ -30,13 +32,14 @@ Entry points:
|
|
|
30
32
|
/src
|
|
31
33
|
mod.ts — main entry: re-exports functions, types, colors
|
|
32
34
|
types.ts — core type definitions (ThemeSchema, TokenSchema, etc.)
|
|
33
|
-
generate.ts — token generation + CSS output (generateCssTokens, toCssString, generateThemeCss)
|
|
35
|
+
generate.ts — token generation + CSS output (generateCssTokens, toCssString, generateThemeCss) + static fallback resolution
|
|
36
|
+
color.ts — color parsing, sRGB<->OKLab, spec-accurate color-mix() evaluation, rgba() authoring helper
|
|
34
37
|
utils.ts — hex color parsing (hexToRgb, hexToRgbTriplet)
|
|
35
38
|
colors.ts — Tailwind color palette (v3 + v4.2 additions: taupe, mauve, mist, olive), as const
|
|
36
39
|
reboot-bridge.ts — Bootstrap Reboot --bs-* variable mapping
|
|
37
40
|
/themes
|
|
38
41
|
mod.ts — auto-generated barrel + runtime registry (via scripts/build-themes-mod.ts)
|
|
39
|
-
*.ts — individual theme files (default export: ThemeSchema)
|
|
42
|
+
*.ts — individual theme files (default export: ThemeSchema; `ring` uses rgba(), never color-mix)
|
|
40
43
|
/css — pre-built CSS files (generated by scripts/build-css.ts; filenames = kebab-case theme names)
|
|
41
44
|
/tests — test files (deno test --allow-read)
|
|
42
45
|
/scripts
|
|
@@ -51,13 +54,20 @@ Entry points:
|
|
|
51
54
|
|
|
52
55
|
### Functions (from `./src/mod.ts`)
|
|
53
56
|
|
|
54
|
-
| Function | Purpose
|
|
55
|
-
| --------------------------------------------- |
|
|
56
|
-
| `generateCssTokens(schema, prefix, options?)` | Generate token key-value pairs. Third arg: `GenerateOptions` object or `"light"`/`"dark"` shorthand
|
|
57
|
-
| `toCssString(tokens, selector?)` | Convert tokens to a CSS declaration block (groups by base color name)
|
|
58
|
-
| `generateThemeCss(schema, prefix, options?)` | Generate complete CSS for light + optional dark mode; supports `cssLayer` wrapping and `prettierIgnore` pragma
|
|
59
|
-
| `
|
|
60
|
-
| `
|
|
57
|
+
| Function | Purpose |
|
|
58
|
+
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
|
|
59
|
+
| `generateCssTokens(schema, prefix, options?)` | Generate token key-value pairs. Third arg: `GenerateOptions` object or `"light"`/`"dark"` shorthand |
|
|
60
|
+
| `toCssString(tokens, selector?)` | Convert tokens to a CSS declaration block (groups by base color name) |
|
|
61
|
+
| `generateThemeCss(schema, prefix, options?)` | Generate complete CSS for light + optional dark mode; supports `fallback`, `cssLayer` wrapping and `prettierIgnore` pragma |
|
|
62
|
+
| `resolveStaticTokens(tokens)` | Evaluate every `color-mix()` in a record to a literal color |
|
|
63
|
+
| `resolveStaticOverrides(tokens)` | Only the tokens that carried a `color-mix()`, precomputed — use to audit legacy exposure |
|
|
64
|
+
| `staticFallbackCss(entries)` | Build the `@supports not (...)` block backing up a set of `{selector, tokens}` blocks |
|
|
65
|
+
| `rgba(color, alpha)` | `color-mix()`-free translucent tint; use this in theme schemas |
|
|
66
|
+
| `parseColor(value)` / `formatColor(color)` | CSS color ↔ `Rgba` (hex, rgb, hsl, oklab, oklch, keywords) |
|
|
67
|
+
| `mixColors(space, c1, p1, c2, p2)` | Spec-accurate `color-mix()` math (premultiplied, CSS Color 5 percentages) |
|
|
68
|
+
| `evaluateColorExpression(expr, lookup?)` | Resolve a `var()`/`color-mix()` expression to a literal; `null` when not statically resolvable |
|
|
69
|
+
| `hexToRgb(hex)` | Parse hex to `[r, g, b]` tuple |
|
|
70
|
+
| `hexToRgbTriplet(hex)` | Convert hex to `"r, g, b"` string |
|
|
61
71
|
|
|
62
72
|
### Functions (from `./reboot`)
|
|
63
73
|
|
|
@@ -74,18 +84,22 @@ Entry points:
|
|
|
74
84
|
|
|
75
85
|
### Key Types
|
|
76
86
|
|
|
77
|
-
| Type | Purpose
|
|
78
|
-
| ----------------------- |
|
|
79
|
-
| `ThemeSchema` | `{ light: TokenSchema; dark?: TokenSchema }`
|
|
80
|
-
| `TokenSchema` | Intent colors, role paired/single colors
|
|
81
|
-
| `ColorPair` | `{ DEFAULT, foreground, hover?, active?, foregroundHover?, foregroundActive? }`
|
|
82
|
-
| `ColorValue` | `{ DEFAULT, hover?, active? }`
|
|
83
|
-
| `SingleColor` | `string \| ColorValue`
|
|
84
|
-
| `GeneratedTokens` | `Record<string, string>`
|
|
85
|
-
| `GenerateOptions` | `{ mode?, deriveStates?, surfaceForegroundContrast? }`
|
|
86
|
-
| `GenerateThemeOptions` | `Omit<GenerateOptions, "mode"> & { cssLayer?, prettierIgnore? }`
|
|
87
|
-
| `
|
|
88
|
-
| `
|
|
87
|
+
| Type | Purpose |
|
|
88
|
+
| ----------------------- | ----------------------------------------------------------------------------------------- |
|
|
89
|
+
| `ThemeSchema` | `{ light: TokenSchema; dark?: TokenSchema }` |
|
|
90
|
+
| `TokenSchema` | Intent colors, role paired/single colors |
|
|
91
|
+
| `ColorPair` | `{ DEFAULT, foreground, hover?, active?, foregroundHover?, foregroundActive? }` |
|
|
92
|
+
| `ColorValue` | `{ DEFAULT, hover?, active? }` |
|
|
93
|
+
| `SingleColor` | `string \| ColorValue` |
|
|
94
|
+
| `GeneratedTokens` | `Record<string, string>` |
|
|
95
|
+
| `GenerateOptions` | `{ mode?, deriveStates?, fallback?, surfaceForegroundContrast? }` |
|
|
96
|
+
| `GenerateThemeOptions` | `Omit<GenerateOptions, "mode" \| "fallback"> & { fallback?, cssLayer?, prettierIgnore? }` |
|
|
97
|
+
| `TokenFallback` | `"none" \| "static"` — record-level legacy strategy |
|
|
98
|
+
| `ThemeFallback` | `TokenFallback \| "supports"` — stylesheet-level, default `"supports"` |
|
|
99
|
+
| `Rgba` | `{ r, g, b, a }`, all `0..1` |
|
|
100
|
+
| `MixSpace` | `"srgb" \| "srgb-linear" \| "oklab"` |
|
|
101
|
+
| `RolePairedKey` | Required: `"background" \| "muted" \| "surface"` |
|
|
102
|
+
| `RolePairedOptionalKey` | Optional: `"surface-1"` (all bundled themes ship it) |
|
|
89
103
|
|
|
90
104
|
### Constants
|
|
91
105
|
|
|
@@ -102,11 +116,14 @@ Entry points:
|
|
|
102
116
|
2. Token naming: `--{prefix}color-{key}`, `--{prefix}color-{key}-hover`, etc.
|
|
103
117
|
3. Prefix is auto-normalized — `"my"` and `"my-"` both produce `--my-color-*`. Empty string produces unprefixed.
|
|
104
118
|
4. Hover/active auto-derived via `color-mix(in oklab, ...)` with a per-bucket strategy: **intent** colors (saturated brand) mix toward `black` / `white` 10%/20% (mode-aware); **role** colors (paired + single) mix toward `var(--{prefix}color-foreground)` 10%/20% for a softer, theme-tinted shift. `oklab` (rectangular) is used rather than `oklch` (polar) because polar hue interpolation collapses to a powerless hue on low-chroma colors mixed toward black/white, painting as a red/mauve shift on near-neutral themes. Disable with `deriveStates: false`.
|
|
105
|
-
5. Surface-intent tokens derived via `color-mix(in srgb, ...)` from intent + background. Foreground contrast defaults to 50% — tune via `surfaceForegroundContrast`. Note `surface-{intent}-border` mixes the intent color 30% **toward `background`** — it is the
|
|
119
|
+
5. Surface-intent tokens derived via `color-mix(in srgb, ...)` from intent + background. Foreground contrast defaults to 50% — tune via `surfaceForegroundContrast`. Note `surface-{intent}-border` mixes the intent color 30% **toward `background`** — it is the _outer_ edge of an intent-tinted surface seen against the page, **not** an inner divider drawn on top of a surface.
|
|
106
120
|
6. Theme files use `export default` with `ThemeSchema` type annotation. All bundled themes include an optional `surface-1` paired role (extra elevation).
|
|
107
121
|
7. Formatting: tabs, 90-char line width, 4-space indent width (`deno fmt`)
|
|
108
122
|
8. MCP handlers must return `Promise<string>` — framework contract from `jsr:@marianmeres/mcp-server/types`.
|
|
109
|
-
9.
|
|
123
|
+
9. **`color-mix()` is a browser requirement, and as a token value it has no consumer-side remedy.** `color-mix()` needs Chrome/Edge 111+, Safari 16.2+, Firefox 113+. In a normal declaration that's harmless (parse-time rejection → earlier declaration wins). As a _custom property value_ the declaration parses everywhere and fails only at substitution, making the using declaration invalid at computed-value time → `unset` (transparent background / inherited text). Neither an earlier declaration nor `var(--token, #fallback)` helps, because the property is defined; its value is just unusable. Hence `generateThemeCss` defaults to `fallback: "supports"`, emitting precomputed literals in a trailing `@supports not (color: color-mix(in oklab, red, blue))` block. **The guard must stay a negated declaration test** — a bare `@supports (color-mix(...))` is the `<general-enclosed>` production, which the spec requires to evaluate to false everywhere, silently handing the fallback to modern engines too.
|
|
124
|
+
10. **`deriveStates` is a design knob, not a compatibility one.** It flattens hover/active; it does NOT remove `color-mix()` (surface-`{intent}` is derived regardless, and author-supplied values pass through). Use `fallback: "static"` for `color-mix()`-free output.
|
|
125
|
+
11. **Theme schemas must not contain `color-mix()`.** Use `rgba(color, alpha)` — exactly equivalent to `color-mix(in srgb, <color> <α>%, transparent)` because mixing with `transparent` is premultiplied, but with no browser requirement. A `color-mix()` in a schema propagates into that token's derived states too.
|
|
126
|
+
12. **Layering semantics for dividers/hairlines**: `border` is calibrated against the **page background / muted** layer (≈ background + one step) and is hand-authored per theme — it may deliberately equal a `surface` step (e.g. dark themes where `border` == `surface` == the same neutral), so it is **not** a safe divider drawn _on top of_ a `surface`. For an on-surface hairline/divider use `surface-1` (one curated step from `surface` in both modes). There is intentionally no dedicated `surface-border` token — `surface-1` covers the case, and the name would collide with the outer-edge meaning of `surface-{intent}-border` (see #5).
|
|
110
127
|
|
|
111
128
|
## Adding a New Theme
|
|
112
129
|
|
|
@@ -124,8 +141,12 @@ Entry points:
|
|
|
124
141
|
- [ ] Follow `deno fmt` conventions
|
|
125
142
|
- [ ] Ensure new exports have JSDoc + explicit return types
|
|
126
143
|
- [ ] If changing generator output (CSS text), note as potential BC change
|
|
144
|
+
- [ ] If changing derivation or color math, run `deno task css:build` and confirm the diff is what you expect
|
|
145
|
+
- [ ] Never emit `color-mix()` without a fallback path — tests assert the `@supports` block covers every `color-mix()` token exactly
|
|
127
146
|
|
|
128
147
|
## Known Limitations
|
|
129
148
|
|
|
130
149
|
- **JSR does not support directory exports** → pre-built CSS is npm-only.
|
|
131
|
-
- **Reboot `-rgb` companions** are omitted for tokens whose values are `color-mix()` expressions (auto-derived hovers).
|
|
150
|
+
- **Reboot `-rgb` companions** are omitted for tokens whose values are `color-mix()` expressions (auto-derived hovers). Pass `fallback: "static"` (or provide explicit hex `hover` / `active`) if you need full alpha-blend support through Reboot's `rgba(var(--bs-…-rgb), α)` rules.
|
|
151
|
+
- **Static fallback coverage is not total.** `evaluateColorExpression` handles `srgb` / `srgb-linear` / `oklab` interpolation over hex, `rgb()`, `hsl()`, `oklab()`, `oklch()` and the basic keywords. Author-supplied `color-mix(in oklch/hsl/lab, …)`, `currentColor`, and undefined variables are deliberately left unevaluated rather than approximated — those tokens are simply absent from the `@supports` block.
|
|
152
|
+
- **`cssLayer` has its own baseline**: `@layer` needs Chrome 99+, Safari 15.4+, Firefox 97+. Older engines drop the whole block, leaving tokens _undefined_ — which at least makes consumer-side `var(--token, #fallback)` fire.
|
package/API.md
CHANGED
|
@@ -12,7 +12,8 @@ Generate CSS token key-value pairs from a single-mode token schema.
|
|
|
12
12
|
- `prefix` (`string`) — CSS variable prefix. Auto-normalized: `"my"` and `"my-"` are equivalent. `""` produces unprefixed tokens.
|
|
13
13
|
- `options` (`GenerateOptions | "light" | "dark"`, optional) — A mode string for quick switching, or an options object. Default: `{}`
|
|
14
14
|
- `mode` (`"light" | "dark"`, optional) — Controls two things: (a) the `surface-{intent}-foreground` contrast mix (toward `black` in light, `white` in dark), and (b) the direction of **intent** hover/active derivation (toward `black` in light, `white` in dark). **Role** hover/active is mode-independent — it always mixes toward `--{prefix}color-foreground`, which itself flips per mode. Default: `"light"`
|
|
15
|
-
- `deriveStates` (`boolean`, optional) — When `false`, missing hover/active fall back to DEFAULT instead of being derived via `color-mix()
|
|
15
|
+
- `deriveStates` (`boolean`, optional) — When `false`, missing hover/active fall back to DEFAULT instead of being derived via `color-mix()` — i.e. flat tokens with no visual state change. This is a _design_ knob, **not** a compatibility one: it does not remove `color-mix()` from the output, because `surface-{intent}` tokens are derived regardless and author-supplied `color-mix()` values pass through verbatim. Use `fallback` for that. Default: `true`
|
|
16
|
+
- `fallback` (`TokenFallback`, optional) — `"static"` evaluates every `color-mix()` at build time so the output contains none; `"none"` emits them as-is. Default: `"none"`
|
|
16
17
|
- `surfaceForegroundContrast` (`number`, optional) — Mix percentage (0–100) used to derive the `surface-{intent}-foreground` token. Higher values increase contrast against the tinted surface background. Default: `50`
|
|
17
18
|
|
|
18
19
|
**Returns:** `GeneratedTokens` — Key-value record of token names to CSS values (keys without the `--` prefix)
|
|
@@ -70,10 +71,11 @@ Generate complete CSS for a theme with light mode and optional dark mode.
|
|
|
70
71
|
- `schema` (`ThemeSchema`) — Theme with required `light` and optional `dark` token schemas
|
|
71
72
|
- `prefix` (`string`) — CSS variable prefix (trailing dash optional)
|
|
72
73
|
- `options` (`GenerateThemeOptions`, optional) — Accepts all `GenerateOptions` (except `mode`, which is set per section) plus:
|
|
73
|
-
- `
|
|
74
|
+
- `fallback` (`ThemeFallback`, optional) — How to serve engines without `color-mix()` (Chrome/Edge < 111, Safari < 16.2, Firefox < 113). `"supports"` emits normal output plus a trailing `@supports not (color: color-mix(in oklab, red, blue))` block holding precomputed literals; `"static"` precomputes everything and emits no `color-mix()` at all; `"none"` is the raw output. Default: `"supports"`
|
|
75
|
+
- `cssLayer` (`string`, optional) — Wrap the output in `@layer {name} { ... }`. Useful for CSS cascade control. Note `@layer` itself needs Chrome 99+, Safari 15.4+, Firefox 97+.
|
|
74
76
|
- `prettierIgnore` (`boolean`, optional) — Prepend a `/* prettier-ignore */` comment so Prettier preserves the generated whitespace alignment when the output is written to a file that later gets formatted. Default: `false`.
|
|
75
77
|
|
|
76
|
-
**Returns:** `string` — Complete CSS with `:root` for light and `:root.dark` for dark mode, optionally wrapped in `@layer`.
|
|
78
|
+
**Returns:** `string` — Complete CSS with `:root` for light and `:root.dark` for dark mode, a trailing `@supports not (...)` fallback block unless disabled, optionally wrapped in `@layer`.
|
|
77
79
|
|
|
78
80
|
**Example:**
|
|
79
81
|
|
|
@@ -132,12 +134,141 @@ hexToRgbTriplet("#2563eb"); // → "37, 99, 235"
|
|
|
132
134
|
|
|
133
135
|
---
|
|
134
136
|
|
|
137
|
+
### `rgba(color, alpha)`
|
|
138
|
+
|
|
139
|
+
Build an `rgba()` string from a color and an alpha value — the `color-mix()`-free way to write a translucent tint in a schema.
|
|
140
|
+
|
|
141
|
+
Exactly equivalent to `color-mix(in srgb, <color> <alpha*100>%, transparent)`: mixing with `transparent` interpolates premultiplied, so only alpha changes. Unlike the `color-mix()` form it carries no browser requirement.
|
|
142
|
+
|
|
143
|
+
**Parameters:**
|
|
144
|
+
|
|
145
|
+
- `color` (`string`) — Any color `parseColor` understands
|
|
146
|
+
- `alpha` (`number`) — Alpha in `0..1`
|
|
147
|
+
|
|
148
|
+
**Returns:** `string` — `rgba(r, g, b, a)`, or `#rrggbb` when the result is opaque
|
|
149
|
+
|
|
150
|
+
**Throws:** `TypeError` if `color` cannot be parsed
|
|
151
|
+
|
|
152
|
+
**Example:**
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
import { colors, rgba } from "@marianmeres/design-tokens";
|
|
156
|
+
|
|
157
|
+
rgba(colors.blue[600], 0.25); // → "rgba(37, 99, 235, 0.25)"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
### `parseColor(value)`
|
|
163
|
+
|
|
164
|
+
Parse a CSS color into an `Rgba`. Supports hex (3/4/6/8 digit), `rgb()`/`rgba()`, `hsl()`/`hsla()`, `oklab()`, `oklch()`, and the basic color keywords, in both legacy comma and modern space/slash syntax.
|
|
165
|
+
|
|
166
|
+
Values that depend on inherited or computed state (`currentColor`, `var()`, system colors) return `null` — they are not statically resolvable.
|
|
167
|
+
|
|
168
|
+
**Returns:** `Rgba | null`
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
### `formatColor(color)`
|
|
173
|
+
|
|
174
|
+
Serialize an `Rgba` to the most compatible CSS form: `#rrggbb` when opaque, legacy `rgba(r, g, b, a)` otherwise. Out-of-gamut components are clamped.
|
|
175
|
+
|
|
176
|
+
**Returns:** `string`
|
|
177
|
+
|
|
178
|
+
---
|
|
179
|
+
|
|
180
|
+
### `mixColors(space, c1, p1, c2, p2)`
|
|
181
|
+
|
|
182
|
+
Mix two colors the way `color-mix()` does, following CSS Color 5 percentage normalization (an omitted percentage is `100 - other`; both omitted is 50/50; a sum below 100 scales the result alpha) and premultiplied-alpha interpolation.
|
|
183
|
+
|
|
184
|
+
**Parameters:**
|
|
185
|
+
|
|
186
|
+
- `space` (`MixSpace`) — `"srgb"`, `"srgb-linear"`, or `"oklab"`
|
|
187
|
+
- `c1`, `c2` (`Rgba`) — The colors
|
|
188
|
+
- `p1`, `p2` (`number | null`) — Percentages `0..100`, or `null` when omitted
|
|
189
|
+
|
|
190
|
+
**Returns:** `Rgba | null` — `null` when both percentages are zero (invalid per spec)
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### `evaluateColorExpression(expr, lookup?)`
|
|
195
|
+
|
|
196
|
+
Evaluate a CSS color expression to a literal color, resolving `var()` references through `lookup` and computing nested `color-mix()` calls.
|
|
197
|
+
|
|
198
|
+
Returns `null` when any operand is not statically resolvable — an undefined variable with no fallback, an unsupported interpolation space (`oklch`, `hsl`, `lab`, …), `currentColor`, or a reference cycle. Treat `null` as "leave the original expression alone": approximating a mix that isn't modelled exactly would be worse than omitting the fallback.
|
|
199
|
+
|
|
200
|
+
**Parameters:**
|
|
201
|
+
|
|
202
|
+
- `expr` (`string`) — e.g. `color-mix(in oklab, var(--x), black 10%)`
|
|
203
|
+
- `lookup` (`VarLookup`, optional) — Resolves custom property names (without `--`) to their declared values
|
|
204
|
+
|
|
205
|
+
**Returns:** `Rgba | null`
|
|
206
|
+
|
|
207
|
+
**Example:**
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { evaluateColorExpression, formatColor } from "@marianmeres/design-tokens";
|
|
211
|
+
|
|
212
|
+
const tokens: Record<string, string> = { "app-color-primary": "#27272a" };
|
|
213
|
+
const c = evaluateColorExpression(
|
|
214
|
+
"color-mix(in oklab, var(--app-color-primary), black 10%)",
|
|
215
|
+
(name) => tokens[name],
|
|
216
|
+
);
|
|
217
|
+
formatColor(c!); // → "#202023"
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### `resolveStaticTokens(tokens)`
|
|
223
|
+
|
|
224
|
+
Evaluate every `color-mix()` expression in a token record at build time, returning a record of the same shape with literal colors in their place.
|
|
225
|
+
|
|
226
|
+
Only values containing `color-mix()` are rewritten — plain `var()` indirection is preserved, since it needs no fallback and flattening it would defeat the Reboot bridge. Values whose operands cannot be resolved statically pass through unchanged.
|
|
227
|
+
|
|
228
|
+
**Returns:** `GeneratedTokens`
|
|
229
|
+
|
|
230
|
+
---
|
|
231
|
+
|
|
232
|
+
### `resolveStaticOverrides(tokens)`
|
|
233
|
+
|
|
234
|
+
The subset of `tokens` whose `color-mix()` values could be precomputed, mapped to their literal equivalents. Tokens without `color-mix()`, and those whose operands are not statically resolvable, are absent.
|
|
235
|
+
|
|
236
|
+
Useful on its own to audit which tokens carry a `color-mix()` browser requirement, and which of those the generator can cover.
|
|
237
|
+
|
|
238
|
+
**Returns:** `GeneratedTokens`
|
|
239
|
+
|
|
240
|
+
**Example:**
|
|
241
|
+
|
|
242
|
+
```typescript
|
|
243
|
+
import { generateCssTokens, resolveStaticOverrides } from "@marianmeres/design-tokens";
|
|
244
|
+
import { zinc } from "@marianmeres/design-tokens/themes";
|
|
245
|
+
|
|
246
|
+
const tokens = generateCssTokens(zinc.light, "app-");
|
|
247
|
+
Object.keys(resolveStaticOverrides(tokens)); // every token needing color-mix support
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
### `staticFallbackCss(entries)`
|
|
253
|
+
|
|
254
|
+
Build the `@supports not (color: color-mix(in oklab, red, blue))` block holding precomputed literals for every `color-mix()`-valued token in `entries`. Emit it _after_ the blocks it backs up — `@supports` adds no specificity, so source order is what makes it win.
|
|
255
|
+
|
|
256
|
+
**Parameters:**
|
|
257
|
+
|
|
258
|
+
- `entries` (`{ selector: string; tokens: GeneratedTokens }[]`) — Selector / token-record pairs, in the order they are emitted
|
|
259
|
+
|
|
260
|
+
**Returns:** `string | null` — The CSS block, or `null` when nothing needs a fallback
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
135
264
|
### `generateRebootBridge(tokens, prefix)`
|
|
136
265
|
|
|
137
266
|
_Import from `@marianmeres/design-tokens/reboot`_
|
|
138
267
|
|
|
139
268
|
Map design tokens to Bootstrap Reboot `--bs-*` CSS variables. Automatically generates `-rgb` triplet companions for hex color values.
|
|
140
269
|
|
|
270
|
+
Bootstrap derives alpha variants via `rgba(var(--bs-foo-rgb), α)`, which needs a raw `r, g, b` triplet rather than a `var()` reference — so companions are omitted for tokens whose value is a `color-mix()` expression. Generating the tokens with `fallback: "static"` makes every value a literal and closes that gap.
|
|
271
|
+
|
|
141
272
|
**Parameters:**
|
|
142
273
|
|
|
143
274
|
- `tokens` (`GeneratedTokens`) — Generated token pairs (from `generateCssTokens`)
|
|
@@ -159,9 +290,9 @@ Generate complete themed CSS including both design tokens AND the Bootstrap Rebo
|
|
|
159
290
|
|
|
160
291
|
- `schema` (`ThemeSchema`) — Theme schema with light and optional dark modes
|
|
161
292
|
- `prefix` (`string`) — CSS variable prefix (trailing dash optional)
|
|
162
|
-
- `options` (`Omit<GenerateThemeOptions, "cssLayer">`, optional) — Forwarded to `generateCssTokens` for each mode (see `generateCssTokens`)
|
|
293
|
+
- `options` (`Omit<GenerateThemeOptions, "cssLayer">`, optional) — Forwarded to `generateCssTokens` for each mode (see `generateCssTokens`). `fallback` defaults to `"supports"`, as in `generateThemeCss`; `"static"` additionally completes the `--bs-*-rgb` companions.
|
|
163
294
|
|
|
164
|
-
**Returns:** `string` — Combined CSS with token variables and `--bs-*` bridge variables
|
|
295
|
+
**Returns:** `string` — Combined CSS with token variables and `--bs-*` bridge variables, plus a trailing `@supports not (...)` fallback block unless disabled
|
|
165
296
|
|
|
166
297
|
**Example:**
|
|
167
298
|
|
|
@@ -278,6 +409,7 @@ Either a plain CSS color string or an object with optional hover/active states.
|
|
|
278
409
|
type GenerateOptions = {
|
|
279
410
|
mode?: "light" | "dark";
|
|
280
411
|
deriveStates?: boolean;
|
|
412
|
+
fallback?: TokenFallback;
|
|
281
413
|
surfaceForegroundContrast?: number;
|
|
282
414
|
};
|
|
283
415
|
```
|
|
@@ -289,13 +421,66 @@ Options passed to `generateCssTokens`. See the function docs above for field mea
|
|
|
289
421
|
### `GenerateThemeOptions`
|
|
290
422
|
|
|
291
423
|
```typescript
|
|
292
|
-
type GenerateThemeOptions = Omit<GenerateOptions, "mode"> & {
|
|
424
|
+
type GenerateThemeOptions = Omit<GenerateOptions, "mode" | "fallback"> & {
|
|
425
|
+
fallback?: ThemeFallback;
|
|
293
426
|
cssLayer?: string;
|
|
294
427
|
prettierIgnore?: boolean;
|
|
295
428
|
};
|
|
296
429
|
```
|
|
297
430
|
|
|
298
|
-
Options passed to `generateThemeCss`. Omits `mode` (set automatically per section) and adds `cssLayer` for optional `@layer` wrapping plus `prettierIgnore` to prepend a `/* prettier-ignore */` pragma for build pipelines that pass the output through Prettier.
|
|
431
|
+
Options passed to `generateThemeCss`. Omits `mode` (set automatically per section), widens `fallback` to include `"supports"`, and adds `cssLayer` for optional `@layer` wrapping plus `prettierIgnore` to prepend a `/* prettier-ignore */` pragma for build pipelines that pass the output through Prettier.
|
|
432
|
+
|
|
433
|
+
---
|
|
434
|
+
|
|
435
|
+
### `TokenFallback`
|
|
436
|
+
|
|
437
|
+
```typescript
|
|
438
|
+
type TokenFallback = "none" | "static";
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
Legacy-engine strategy for a token _record_. `"none"` emits `color-mix()` expressions as-is; `"static"` evaluates them at build time so the record contains only literal colors.
|
|
442
|
+
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### `ThemeFallback`
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
type ThemeFallback = TokenFallback | "supports";
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Legacy-engine strategy for a _stylesheet_. Adds `"supports"`: emit the `color-mix()` values normally, then repeat the affected tokens as precomputed literals inside `@supports not (color: color-mix(in oklab, red, blue))`. Modern engines are unaffected — the base block is byte-for-byte identical to `"none"` — while older ones get working colors.
|
|
452
|
+
|
|
453
|
+
The guard is a _negated declaration test_ on purpose. A bare `@supports (color-mix(...))` is the `<general-enclosed>` production, which the spec requires to evaluate to false in every browser, so it would hand the fallback to modern engines too.
|
|
454
|
+
|
|
455
|
+
---
|
|
456
|
+
|
|
457
|
+
### `Rgba`
|
|
458
|
+
|
|
459
|
+
```typescript
|
|
460
|
+
type Rgba = { r: number; g: number; b: number; a: number };
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
A color with red/green/blue components in `0..1` (gamma-encoded sRGB) and alpha in `0..1`.
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
### `MixSpace`
|
|
468
|
+
|
|
469
|
+
```typescript
|
|
470
|
+
type MixSpace = "srgb" | "srgb-linear" | "oklab";
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
Interpolation color spaces understood by `mixColors`. Deliberately narrow — these are the spaces the generator emits. Author-supplied `color-mix()` in any other space (`oklch`, `hsl`, `lab`, …) is left unevaluated rather than approximated.
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
### `VarLookup`
|
|
478
|
+
|
|
479
|
+
```typescript
|
|
480
|
+
type VarLookup = (name: string) => string | undefined;
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
Resolves a custom property name (without the leading `--`) to its declared value.
|
|
299
484
|
|
|
300
485
|
---
|
|
301
486
|
|
package/README.md
CHANGED
|
@@ -10,6 +10,15 @@ bridge that maps tokens to `--bs-*` variables. Same mental model as
|
|
|
10
10
|
[@marianmeres/stuic](https://github.com/marianmeres/stuic)'s token system, but
|
|
11
11
|
framework-agnostic — no Tailwind dependency.
|
|
12
12
|
|
|
13
|
+
## Browser support
|
|
14
|
+
|
|
15
|
+
Generated themes work everywhere. Derived tokens use `color-mix()`, which needs
|
|
16
|
+
**Chrome/Edge 111+, Safari 16.2+, Firefox 113+** (all early-to-mid 2023) — so
|
|
17
|
+
`generateThemeCss` also emits a precomputed fallback for older engines by default. See
|
|
18
|
+
[Legacy browsers](#legacy-browsers) if you want to change that, and
|
|
19
|
+
[Why the fallback matters](#why-the-fallback-matters) for why it is not something a
|
|
20
|
+
consumer could patch downstream.
|
|
21
|
+
|
|
13
22
|
## Install
|
|
14
23
|
|
|
15
24
|
```sh
|
|
@@ -52,7 +61,7 @@ const theme: ThemeSchema = {
|
|
|
52
61
|
// *on* a surface. Use `surface-1` for on-surface dividers.
|
|
53
62
|
border: { DEFAULT: "#d4d4d4" },
|
|
54
63
|
input: { DEFAULT: "#ffffff" },
|
|
55
|
-
ring: "
|
|
64
|
+
ring: "rgba(37, 99, 235, 0.25)",
|
|
56
65
|
},
|
|
57
66
|
},
|
|
58
67
|
},
|
|
@@ -72,6 +81,9 @@ All bundled themes are available as pre-built CSS files with the `stuic-` prefix
|
|
|
72
81
|
<link rel="stylesheet" href="node_modules/@marianmeres/design-tokens/css/mauve-teal.css">
|
|
73
82
|
```
|
|
74
83
|
|
|
84
|
+
These are built with the default `fallback: "supports"`, so they render correctly on
|
|
85
|
+
pre-2023 engines without any work on your side.
|
|
86
|
+
|
|
75
87
|
Or via bundler import:
|
|
76
88
|
|
|
77
89
|
```ts
|
|
@@ -83,7 +95,7 @@ import "@marianmeres/design-tokens/css/mauve-teal.css";
|
|
|
83
95
|
Use the bundled `colors` map (Tailwind palette including v4.2 additions) to avoid looking up hex values:
|
|
84
96
|
|
|
85
97
|
```ts
|
|
86
|
-
import { colors, generateThemeCss } from "@marianmeres/design-tokens";
|
|
98
|
+
import { colors, generateThemeCss, rgba } from "@marianmeres/design-tokens";
|
|
87
99
|
import type { ThemeSchema } from "@marianmeres/design-tokens";
|
|
88
100
|
|
|
89
101
|
const myTheme: ThemeSchema = {
|
|
@@ -119,7 +131,7 @@ const myTheme: ThemeSchema = {
|
|
|
119
131
|
foreground: colors.slate[900],
|
|
120
132
|
border: { DEFAULT: colors.slate[300] },
|
|
121
133
|
input: { DEFAULT: colors.slate[50], hover: colors.slate[100] },
|
|
122
|
-
ring:
|
|
134
|
+
ring: rgba(colors.blue[600], 0.25),
|
|
123
135
|
},
|
|
124
136
|
},
|
|
125
137
|
},
|
|
@@ -133,6 +145,11 @@ const css = generateThemeCss(myTheme, "my-");
|
|
|
133
145
|
Hover/active states are auto-derived via `color-mix(in oklab, ...)` when omitted. You can
|
|
134
146
|
provide explicit values to override the derivation.
|
|
135
147
|
|
|
148
|
+
The `rgba()` helper is the `color-mix()`-free way to write a translucent tint. It is
|
|
149
|
+
exactly equivalent to `color-mix(in srgb, <color> 25%, transparent)` — mixing with
|
|
150
|
+
`transparent` is premultiplied, so only alpha changes — but it needs no modern-browser
|
|
151
|
+
support and it keeps your schema free of expressions the generator has to work around.
|
|
152
|
+
|
|
136
153
|
## Bundled themes
|
|
137
154
|
|
|
138
155
|
```ts
|
|
@@ -159,12 +176,69 @@ if (theme) {
|
|
|
159
176
|
|
|
160
177
|
```ts
|
|
161
178
|
generateThemeCss(zinc, "app-", {
|
|
162
|
-
|
|
179
|
+
fallback: "supports", // legacy-browser strategy (default) — see below
|
|
180
|
+
deriveStates: false, // flat hover/active instead of derived states
|
|
163
181
|
surfaceForegroundContrast: 70, // stronger contrast on surface-{intent}-foreground
|
|
164
182
|
cssLayer: "tokens", // wrap output in @layer tokens { ... }
|
|
165
183
|
});
|
|
166
184
|
```
|
|
167
185
|
|
|
186
|
+
Note that `deriveStates: false` is a _design_ knob — it makes hover/active flat, but does
|
|
187
|
+
not remove `color-mix()` from the output (`surface-{intent}` tokens are derived regardless,
|
|
188
|
+
and author-supplied `color-mix()` values pass through). Use `fallback` for that.
|
|
189
|
+
|
|
190
|
+
## Legacy browsers
|
|
191
|
+
|
|
192
|
+
`color-mix()` needs Chrome/Edge 111+, Safari 16.2+, Firefox 113+. The `fallback` option
|
|
193
|
+
controls what older engines get:
|
|
194
|
+
|
|
195
|
+
| `fallback` | Output |
|
|
196
|
+
| --------------------- | ------------------------------------------------------------------------------------ |
|
|
197
|
+
| `"supports"`(default) | Normal output, plus a trailing `@supports not (...)` block with precomputed literals |
|
|
198
|
+
| `"static"` | Every `color-mix()` evaluated at build time — the output contains none at all |
|
|
199
|
+
| `"none"` | `color-mix()` only; breaks on older engines |
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
/* fallback: "supports" */
|
|
203
|
+
:root {
|
|
204
|
+
--app-color-primary: #27272a;
|
|
205
|
+
--app-color-primary-hover: color-mix(in oklab, var(--app-color-primary), black 10%);
|
|
206
|
+
}
|
|
207
|
+
@supports not (color: color-mix(in oklab, red, blue)) {
|
|
208
|
+
:root {
|
|
209
|
+
--app-color-primary-hover: #202023;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
`"supports"` is the default because it is purely additive — the `:root` block is
|
|
215
|
+
byte-for-byte what `"none"` produces, so modern rendering cannot shift — at a cost of
|
|
216
|
+
roughly +28% output size (about +0.6 KB gzipped per theme).
|
|
217
|
+
|
|
218
|
+
Pick `"static"` if you want the smallest output (it is ~35% _smaller_ than `"none"`, since
|
|
219
|
+
hex literals are shorter than mix expressions) and you don't need derived tokens to track
|
|
220
|
+
runtime overrides of their base color. It also completes the Reboot bridge's `--bs-*-rgb`
|
|
221
|
+
companions, which can't exist while token values are expressions.
|
|
222
|
+
|
|
223
|
+
Values the generator cannot evaluate at build time — an author-supplied `oklch`/`hsl`
|
|
224
|
+
interpolation space, `currentColor`, an undefined variable — are left as-is rather than
|
|
225
|
+
approximated, so they are simply absent from the fallback block.
|
|
226
|
+
|
|
227
|
+
### Why the fallback matters
|
|
228
|
+
|
|
229
|
+
`color-mix()` in an ordinary declaration is harmless on old browsers: the declaration is
|
|
230
|
+
rejected at parse time and an earlier one wins, which is the standard progressive-
|
|
231
|
+
enhancement pattern. As a **token value** it behaves differently. Custom properties accept
|
|
232
|
+
almost any token sequence, so the declaration parses everywhere and only fails at
|
|
233
|
+
substitution — making the _using_ declaration invalid at computed-value time, which
|
|
234
|
+
resolves the property to `unset`: transparent for a background, inherited for `color`.
|
|
235
|
+
|
|
236
|
+
That failure is unreachable from consumer CSS. It happens after the cascade has picked a
|
|
237
|
+
winner, so an earlier declaration is not a fallback, and `var(--token, #333)` doesn't fire
|
|
238
|
+
either — the property is defined, its value is just unusable. The result is a control that
|
|
239
|
+
keeps its size, position, and hit area and simply stops being visible. Hence the fix lives
|
|
240
|
+
here rather than in consumer stylesheets.
|
|
241
|
+
|
|
168
242
|
## Reboot bridge
|
|
169
243
|
|
|
170
244
|
Maps design tokens to Bootstrap Reboot's `--bs-*` CSS variables:
|
|
@@ -177,6 +251,11 @@ const css = generateThemedCss(zinc, "app-");
|
|
|
177
251
|
// → :root { --app-color-primary: ...; --bs-body-bg: ...; --bs-link-color: ...; }
|
|
178
252
|
```
|
|
179
253
|
|
|
254
|
+
Takes the same `fallback` option, defaulting to `"supports"`. Bootstrap derives alpha
|
|
255
|
+
variants via `rgba(var(--bs-foo-rgb), α)`, which needs a raw `r, g, b` triplet — so
|
|
256
|
+
`--bs-*-rgb` companions are only emitted for tokens that resolve to a literal color. Pass
|
|
257
|
+
`fallback: "static"` if you need them all.
|
|
258
|
+
|
|
180
259
|
## API
|
|
181
260
|
|
|
182
261
|
See [API.md](API.md) for the complete API documentation.
|
package/dist/color.d.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Color parsing and mixing math.
|
|
3
|
+
*
|
|
4
|
+
* Exists so the generator can evaluate its own `color-mix()` expressions at
|
|
5
|
+
* build time and emit literal colors for engines that predate `color-mix()`
|
|
6
|
+
* (Chrome/Edge < 111, Safari < 16.2, Firefox < 113). See the `fallback` option
|
|
7
|
+
* in {@link ./generate.ts}.
|
|
8
|
+
*
|
|
9
|
+
* Everything here mirrors CSS Color 4/5 semantics: premultiplied-alpha
|
|
10
|
+
* interpolation, the `color-mix()` percentage normalization rules, and the
|
|
11
|
+
* OKLab transfer functions.
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* A color with red/green/blue components in `0..1` (gamma-encoded sRGB) and
|
|
17
|
+
* alpha in `0..1`.
|
|
18
|
+
*/
|
|
19
|
+
export type Rgba = {
|
|
20
|
+
r: number;
|
|
21
|
+
g: number;
|
|
22
|
+
b: number;
|
|
23
|
+
a: number;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Interpolation color spaces understood by {@link mixColors}.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately narrow — these are the spaces the generator emits. Author-
|
|
29
|
+
* supplied `color-mix()` in any other space (`oklch`, `hsl`, `lab`, …) is left
|
|
30
|
+
* unevaluated rather than approximated; see {@link evaluateColorExpression}.
|
|
31
|
+
*/
|
|
32
|
+
export type MixSpace = "srgb" | "srgb-linear" | "oklab";
|
|
33
|
+
/**
|
|
34
|
+
* Parse a CSS color into {@link Rgba}. Returns null for anything not
|
|
35
|
+
* statically resolvable.
|
|
36
|
+
*
|
|
37
|
+
* Supported: hex (3/4/6/8 digit), `rgb()`/`rgba()`, `hsl()`/`hsla()`,
|
|
38
|
+
* `oklab()`, `oklch()`, and the basic color keywords. Values that depend on
|
|
39
|
+
* inherited or computed state (`currentColor`, `var()`, system colors) are
|
|
40
|
+
* intentionally NOT supported here — see {@link evaluateColorExpression} for
|
|
41
|
+
* `var()` resolution.
|
|
42
|
+
*/
|
|
43
|
+
export declare function parseColor(value: string): Rgba | null;
|
|
44
|
+
/**
|
|
45
|
+
* Serialize to the most compatible CSS form: `#rrggbb` when fully opaque,
|
|
46
|
+
* legacy `rgba(r, g, b, a)` otherwise. Legacy `rgba()` is deliberate — the
|
|
47
|
+
* whole point of the static fallback is reaching engines that predate the
|
|
48
|
+
* modern space-separated syntax.
|
|
49
|
+
*/
|
|
50
|
+
export declare function formatColor(color: Rgba): string;
|
|
51
|
+
/**
|
|
52
|
+
* Mix two colors the way `color-mix()` does.
|
|
53
|
+
*
|
|
54
|
+
* Percentages follow CSS Color 5 normalization: an omitted percentage is
|
|
55
|
+
* `100 - other`, both omitted is 50/50, and when the two sum to less than 100
|
|
56
|
+
* the result's alpha is scaled by that sum. Interpolation is premultiplied by
|
|
57
|
+
* alpha, which is why mixing an opaque color with `transparent` preserves the
|
|
58
|
+
* color and only lowers alpha.
|
|
59
|
+
*
|
|
60
|
+
* @param space - Interpolation space
|
|
61
|
+
* @param c1 - First color
|
|
62
|
+
* @param p1 - First percentage (0-100), or null when omitted
|
|
63
|
+
* @param c2 - Second color
|
|
64
|
+
* @param p2 - Second percentage (0-100), or null when omitted
|
|
65
|
+
* @returns The mixed color, or null if both percentages are zero (invalid)
|
|
66
|
+
*/
|
|
67
|
+
export declare function mixColors(space: MixSpace, c1: Rgba, p1: number | null, c2: Rgba, p2: number | null): Rgba | null;
|
|
68
|
+
/** Look up the value of a custom property by its name, without the leading `--`. */
|
|
69
|
+
export type VarLookup = (name: string) => string | undefined;
|
|
70
|
+
/**
|
|
71
|
+
* Evaluate a CSS color expression to a literal color, resolving `var()`
|
|
72
|
+
* references through `lookup` and computing nested `color-mix()` calls.
|
|
73
|
+
*
|
|
74
|
+
* Returns null when any operand is not statically resolvable — an undefined
|
|
75
|
+
* variable with no fallback, an unsupported interpolation space (`oklch`,
|
|
76
|
+
* `hsl`, `lab`, …), `currentColor`, a reference cycle, and so on. Callers
|
|
77
|
+
* should treat null as "leave the original expression alone" rather than as an
|
|
78
|
+
* error; approximating a mix we don't model exactly would be worse than
|
|
79
|
+
* omitting the fallback.
|
|
80
|
+
*
|
|
81
|
+
* @param expr - The expression, e.g. `color-mix(in oklab, var(--x), black 10%)`
|
|
82
|
+
* @param lookup - Resolves custom property names to their declared values
|
|
83
|
+
*/
|
|
84
|
+
export declare function evaluateColorExpression(expr: string, lookup?: VarLookup): Rgba | null;
|
|
85
|
+
/**
|
|
86
|
+
* Build an `rgba()` string from an opaque color and an alpha value.
|
|
87
|
+
*
|
|
88
|
+
* Provided for theme authors: `rgba(colors.blue[600], 0.25)` is exactly
|
|
89
|
+
* equivalent to `color-mix(in srgb, <blue-600> 25%, transparent)` (mixing with
|
|
90
|
+
* `transparent` is premultiplied, so only alpha changes) but carries no
|
|
91
|
+
* `color-mix()` browser requirement.
|
|
92
|
+
*
|
|
93
|
+
* @param color - Any color {@link parseColor} understands
|
|
94
|
+
* @param alpha - Alpha in `0..1`
|
|
95
|
+
* @throws If `color` cannot be parsed
|
|
96
|
+
*/
|
|
97
|
+
export declare function rgba(color: string, alpha: number): string;
|