@ttoss/fsl-theme 2.0.0 → 2.1.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/README.md +41 -30
- package/dist/{Types-BkaxtsMv.d.cts → Types-a_sfpbha.d.cts} +23 -11
- package/dist/{Types-BkaxtsMv.d.mts → Types-a_sfpbha.d.mts} +23 -11
- package/dist/{baseBundle-R7WyjyTw.mjs → baseBundle-CXdxj_re.mjs} +1 -1
- package/dist/{baseBundle-DdRSa5mf.cjs → baseBundle-a9LDsqYy.cjs} +1 -1
- package/dist/{createTheme--UFgVlEn.cjs → createTheme-B7H0xSVp.cjs} +95 -17
- package/dist/{createTheme-B6tVIikC.mjs → createTheme-Bm9LVQ4w.mjs} +96 -18
- package/dist/css.cjs +4 -3
- package/dist/css.d.cts +21 -2
- package/dist/css.d.mts +21 -2
- package/dist/css.mjs +4 -3
- package/dist/dataviz/index.cjs +2 -2
- package/dist/dataviz/index.d.cts +1 -1
- package/dist/dataviz/index.d.mts +1 -1
- package/dist/dataviz/index.mjs +1 -1
- package/dist/dtcg.d.cts +1 -1
- package/dist/dtcg.d.mts +1 -1
- package/dist/{index-7YL90Xqs.d.cts → index-BmZQVd3G.d.cts} +1 -1
- package/dist/{index-BmCTIOzh.d.mts → index-XZsRrMTF.d.mts} +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.mts +3 -3
- package/dist/index.mjs +1 -1
- package/dist/{react-C8JrZf-b.d.mts → react-BwsjWKmo.d.mts} +46 -15
- package/dist/{react-DLB8nrXQ.cjs → react-DQErMGJz.cjs} +90 -18
- package/dist/{react-Dt7vGAVG.d.cts → react-Drr-QSuX.d.cts} +46 -15
- package/dist/react.cjs +3 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.mts +1 -1
- package/dist/react.mjs +92 -18
- package/dist/runtime-entry.cjs +1 -1
- package/dist/runtime-entry.mjs +1 -1
- package/dist/{ssrScript-MF5SIONH.cjs → ssrScript-D73WcrCM.cjs} +51 -3
- package/dist/{ssrScript-DNLlLDps.mjs → ssrScript-zDK1dqXr.mjs} +51 -3
- package/dist/themes/bruttal.cjs +2 -2
- package/dist/themes/bruttal.d.cts +1 -1
- package/dist/themes/bruttal.d.mts +1 -1
- package/dist/themes/bruttal.mjs +2 -2
- package/dist/{toCssVars-C-9yIZDR.mjs → toCssVars-CWTlp9St.mjs} +38 -3
- package/dist/{toCssVars-BseqmMcT.cjs → toCssVars-D0BR23cP.cjs} +38 -3
- package/dist/vars.cjs +7 -7
- package/dist/vars.d.cts +6 -6
- package/dist/vars.d.mts +6 -6
- package/dist/vars.mjs +7 -7
- package/dist/{withDataviz-CYRf4exT.cjs → withDataviz-DrUtw8cc.cjs} +1 -1
- package/dist/{withDataviz-BvpZJnFx.mjs → withDataviz-Ln3sEwb7.mjs} +1 -1
- package/llms.txt +6 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -34,38 +34,39 @@ Components consume only semantic tokens. Core tokens never change between light
|
|
|
34
34
|
|
|
35
35
|
One entry per semantic family. Use `vars.*` for typed CSS variable references; use the `semantic.*` path shape below when naming tokens in TypeScript or in discussion.
|
|
36
36
|
|
|
37
|
-
| Family | Path shape
|
|
38
|
-
| --------- |
|
|
39
|
-
| colors | `semantic.colors.{ux}.{role}.{dimension}.{state}`
|
|
40
|
-
| spacing | `semantic.spacing.inset.{control,surface}.{sm,md,lg}`
|
|
41
|
-
| | `semantic.spacing.gap.{stack,inline}.{xs,sm,md,lg,xl}`
|
|
42
|
-
| | `semantic.spacing.gutter.{page,section}`
|
|
43
|
-
| | `semantic.spacing.separation.interactive.min`
|
|
44
|
-
| text | `semantic.text.{display,headline,title,body,label
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
| | `semantic.sizing.
|
|
48
|
-
| | `semantic.sizing.
|
|
49
|
-
| | `semantic.sizing.
|
|
50
|
-
| | `semantic.sizing.
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
54
|
-
|
|
|
55
|
-
|
|
|
56
|
-
|
|
|
57
|
-
|
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
37
|
+
| Family | Path shape | Leaf value |
|
|
38
|
+
| --------- | ---------------------------------------------------------------- | ----------------------- |
|
|
39
|
+
| colors | `semantic.colors.{ux}.{role}.{dimension}.{state}` | CSS color |
|
|
40
|
+
| spacing | `semantic.spacing.inset.{control,surface}.{sm,md,lg}` | CSS length |
|
|
41
|
+
| | `semantic.spacing.gap.{stack,inline}.{xs,sm,md,lg,xl}` | CSS length |
|
|
42
|
+
| | `semantic.spacing.gutter.{page,section}` | CSS length / `clamp()` |
|
|
43
|
+
| | `semantic.spacing.separation.interactive.min` | CSS length |
|
|
44
|
+
| text | `semantic.text.{display,headline,title,body,label}.{lg,md,sm}` | TextStyle object |
|
|
45
|
+
| | `semantic.text.code.{md,sm}` | TextStyle object |
|
|
46
|
+
| sizing | `semantic.sizing.hit.{min,base,prominent}` | CSS length |
|
|
47
|
+
| | `semantic.sizing.icon.{sm,md,lg}` | CSS length |
|
|
48
|
+
| | `semantic.sizing.identity.{sm,md,lg,xl}` | CSS length |
|
|
49
|
+
| | `semantic.sizing.measure.reading` | CSS `ch` / `clamp()` |
|
|
50
|
+
| | `semantic.sizing.surface.maxWidth` | CSS length |
|
|
51
|
+
| | `semantic.sizing.viewport.{height,width}.full` | CSS dvh/dvw |
|
|
52
|
+
| radii | `semantic.radii.{control,surface,round}` | CSS length |
|
|
53
|
+
| border | `semantic.border.divider` | `{width, style}` |
|
|
54
|
+
| | `semantic.border.outline.{surface,control,selected}` | `{width, style}` |
|
|
55
|
+
| focus | `semantic.focus.ring` | `{width, style, color}` |
|
|
56
|
+
| elevation | `semantic.elevation.surface.{flat,raised,overlay,blocking}` | CSS box-shadow |
|
|
57
|
+
| | `semantic.elevation.tonal.{raised,overlay,blocking}` | CSS color (optional) |
|
|
58
|
+
| opacity | `semantic.opacity.{scrim,loading,disabled}` | number in (0, 1) |
|
|
59
|
+
| overlay | `semantic.overlay.scrim` | CSS color with alpha |
|
|
60
|
+
| motion | `semantic.motion.{feedback,emphasis,decorative}` | `{duration, easing}` |
|
|
61
|
+
| | `semantic.motion.transition.{enter,exit}` | `{duration, easing}` |
|
|
62
|
+
| zIndex | `semantic.zIndex.layer.{base,sticky,overlay,blocking,transient}` | integer |
|
|
62
63
|
|
|
63
64
|
**Colors axes** (`semantic.colors.{ux}.{role}.{dimension}.{state}`):
|
|
64
65
|
|
|
65
66
|
- `ux` — FSL Entity Kind: `action` · `input` · `navigation` · `feedback` · `informational`
|
|
66
67
|
- `role` — Evaluation: `primary` · `secondary` · `accent` · `muted` · `positive` · `caution` · `negative`
|
|
67
68
|
- `dimension` — `background` · `border` · `text`
|
|
68
|
-
- `state` — `default` · `hover` · `active` · `focused` · `disabled` · `selected` · `pressed`
|
|
69
|
+
- `state` — `default` · `hover` · `active` · `focused` · `disabled` · `selected` · `pressed` · `expanded` · `checked` · `indeterminate` · `current` · `visited` · `droptarget` · `invalid` (legality varies per `ux` — see the colors family spec)
|
|
69
70
|
|
|
70
71
|
## Pick a token in 60s
|
|
71
72
|
|
|
@@ -220,7 +221,7 @@ export const App = () => (
|
|
|
220
221
|
);
|
|
221
222
|
```
|
|
222
223
|
|
|
223
|
-
`ThemeProvider` injects CSS Custom Properties via React 19 style hoisting
|
|
224
|
+
`ThemeProvider` injects CSS Custom Properties via React 19 style hoisting, persists mode to localStorage, and keeps open tabs in sync via the `storage` event. The react entry point ships with `'use client'`, so it can be imported directly from a Next.js App Router layout.
|
|
224
225
|
|
|
225
226
|
### Hooks
|
|
226
227
|
|
|
@@ -242,7 +243,10 @@ import { useResolvedTokens } from '@ttoss/fsl-theme/react';
|
|
|
242
243
|
|
|
243
244
|
// Non-CSS environments (React Native, canvas) — resolved raw values
|
|
244
245
|
const resolved = useResolvedTokens();
|
|
245
|
-
// resolved['semantic.colors.action.primary.background.default'] → '#
|
|
246
|
+
// resolved['semantic.colors.action.primary.background.default'] → '#020617'
|
|
247
|
+
// ⚠ CSS-coupled tokens (spacing steps, fluid text sizes, hit/viewport sizing —
|
|
248
|
+
// see model.md §8) stay as CSS expressions (var()/calc()/clamp()/cqi) and are
|
|
249
|
+
// not usable outside a CSS engine. Colors and scalar tokens resolve fully.
|
|
246
250
|
```
|
|
247
251
|
|
|
248
252
|
### Consuming tokens
|
|
@@ -376,16 +380,23 @@ import { getThemeStylesContent } from '@ttoss/fsl-theme/css';
|
|
|
376
380
|
|
|
377
381
|
const css = getThemeStylesContent(myTheme);
|
|
378
382
|
// → :root { --tt-* } + :root[data-tt-mode="dark"] { --tt-* (overrides) }
|
|
383
|
+
// + @media (prefers-color-scheme: dark) fallback for no-JS / pre-hydration
|
|
384
|
+
|
|
385
|
+
// Fixed light/dark default (dark only via explicit toggle)? Skip the OS fallback:
|
|
386
|
+
const lightFirstCss = getThemeStylesContent(myTheme, undefined, {
|
|
387
|
+
systemModeFallback: false,
|
|
388
|
+
});
|
|
389
|
+
// <ThemeProvider> / <ThemeHead> derive this automatically from `defaultMode`.
|
|
379
390
|
```
|
|
380
391
|
|
|
381
392
|
## Storybook / micro-frontends
|
|
382
393
|
|
|
383
|
-
Anchor theme attributes to a specific element instead of `<html
|
|
394
|
+
Anchor theme attributes to a specific element instead of `<html>`. Always pair `root` with a `themeId` — without one, the CSS targets `:root` on `<html>` while the attributes land on the element, and the alternate mode would never apply (a dev warning fires on this combination):
|
|
384
395
|
|
|
385
396
|
```tsx
|
|
386
397
|
const rootRef = React.useRef<HTMLDivElement>(null);
|
|
387
398
|
<div ref={rootRef}>
|
|
388
|
-
<ThemeProvider theme={myTheme} root={rootRef
|
|
399
|
+
<ThemeProvider theme={myTheme} themeId="story" root={rootRef}>
|
|
389
400
|
<Story />
|
|
390
401
|
</ThemeProvider>
|
|
391
402
|
</div>;
|
|
@@ -366,14 +366,18 @@ interface ActionColorStates extends BaseColorStates {
|
|
|
366
366
|
/** Disclosure trigger or menu button is currently open. Use for buttons that own an open popup, menu, or panel. */
|
|
367
367
|
expanded?: CoreColorRef;
|
|
368
368
|
}
|
|
369
|
-
/** `input` context: adds `checked`, `indeterminate`, `pressed`, `expanded
|
|
369
|
+
/** `input` context: adds `checked`, `indeterminate`, `pressed`, `expanded`,
|
|
370
|
+
* and `invalid` for form controls.
|
|
370
371
|
*
|
|
371
|
-
* Validation
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
372
|
+
* Validation doctrine (ADR-017, mirrors `@ttoss/fsl-ui` taxonomy): validation
|
|
373
|
+
* outcome is a **runtime State** (`invalid`), never an authorial Evaluation —
|
|
374
|
+
* evaluation lives in the author's pen, state lives in the user's data.
|
|
375
|
+
* The control itself flips to `input.{role}.*.invalid` when React Aria's
|
|
376
|
+
* `isInvalid` (or a form library) says so; adjacent *display* parts
|
|
377
|
+
* (validationMessage, icon) still consume the `input.negative.*` role — they
|
|
378
|
+
* report valence, the control carries state. The mirror distinction to
|
|
379
|
+
* Lexicon §10.5 (`negative` ≠ `destructive`) is `invalid` (State) ≠
|
|
380
|
+
* `negative` (Evaluation) — Lexicon §10.15.
|
|
377
381
|
*
|
|
378
382
|
* Structural Role → token mapping (FSL Lexicon §2): a part declared with
|
|
379
383
|
* Structural Role `validationMessage` consumes `input.negative.text.*` for
|
|
@@ -393,6 +397,14 @@ interface InputColorStates extends BaseColorStates {
|
|
|
393
397
|
pressed?: CoreColorRef;
|
|
394
398
|
/** Combobox / select / disclosure-style input is currently open. */
|
|
395
399
|
expanded?: CoreColorRef;
|
|
400
|
+
/**
|
|
401
|
+
* Runtime validation outcome — the control's value failed validation.
|
|
402
|
+
* Use when React Aria's `isInvalid` (or a form library) asserts failure.
|
|
403
|
+
* Pair with `input.negative.text.*` on the validationMessage; do not use
|
|
404
|
+
* the `negative` role on the control itself — that is authorial valence
|
|
405
|
+
* (Lexicon §10.15).
|
|
406
|
+
*/
|
|
407
|
+
invalid?: CoreColorRef;
|
|
396
408
|
}
|
|
397
409
|
/** `navigation` context: adds `selected`, `current`, `visited`, `expanded`. */
|
|
398
410
|
interface NavigationColorStates extends BaseColorStates {
|
|
@@ -407,10 +419,10 @@ interface NavigationColorStates extends BaseColorStates {
|
|
|
407
419
|
}
|
|
408
420
|
/** `informational` context: adds `selected`, `visited`, `expanded`.
|
|
409
421
|
*
|
|
410
|
-
* `expanded` covers in-place disclosure on presentational surfaces
|
|
411
|
-
* collapsible panels, expandable cards). `Disclosure`
|
|
412
|
-
*
|
|
413
|
-
*
|
|
422
|
+
* `expanded` covers in-place disclosure on presentational surfaces
|
|
423
|
+
* (accordions, collapsible panels, expandable cards). Note: `Disclosure`
|
|
424
|
+
* Entity Kinds project to `navigation` (ADR-001); `expanded` here serves
|
|
425
|
+
* presentational surfaces that disclose without being Disclosure entities. */
|
|
414
426
|
interface InformationalColorStates extends BaseColorStates {
|
|
415
427
|
/** Presentational element is **one of many** and the user picked it (selectable list row, focused card in a deck). */
|
|
416
428
|
selected?: CoreColorRef;
|
|
@@ -366,14 +366,18 @@ interface ActionColorStates extends BaseColorStates {
|
|
|
366
366
|
/** Disclosure trigger or menu button is currently open. Use for buttons that own an open popup, menu, or panel. */
|
|
367
367
|
expanded?: CoreColorRef;
|
|
368
368
|
}
|
|
369
|
-
/** `input` context: adds `checked`, `indeterminate`, `pressed`, `expanded
|
|
369
|
+
/** `input` context: adds `checked`, `indeterminate`, `pressed`, `expanded`,
|
|
370
|
+
* and `invalid` for form controls.
|
|
370
371
|
*
|
|
371
|
-
* Validation
|
|
372
|
-
*
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
372
|
+
* Validation doctrine (ADR-017, mirrors `@ttoss/fsl-ui` taxonomy): validation
|
|
373
|
+
* outcome is a **runtime State** (`invalid`), never an authorial Evaluation —
|
|
374
|
+
* evaluation lives in the author's pen, state lives in the user's data.
|
|
375
|
+
* The control itself flips to `input.{role}.*.invalid` when React Aria's
|
|
376
|
+
* `isInvalid` (or a form library) says so; adjacent *display* parts
|
|
377
|
+
* (validationMessage, icon) still consume the `input.negative.*` role — they
|
|
378
|
+
* report valence, the control carries state. The mirror distinction to
|
|
379
|
+
* Lexicon §10.5 (`negative` ≠ `destructive`) is `invalid` (State) ≠
|
|
380
|
+
* `negative` (Evaluation) — Lexicon §10.15.
|
|
377
381
|
*
|
|
378
382
|
* Structural Role → token mapping (FSL Lexicon §2): a part declared with
|
|
379
383
|
* Structural Role `validationMessage` consumes `input.negative.text.*` for
|
|
@@ -393,6 +397,14 @@ interface InputColorStates extends BaseColorStates {
|
|
|
393
397
|
pressed?: CoreColorRef;
|
|
394
398
|
/** Combobox / select / disclosure-style input is currently open. */
|
|
395
399
|
expanded?: CoreColorRef;
|
|
400
|
+
/**
|
|
401
|
+
* Runtime validation outcome — the control's value failed validation.
|
|
402
|
+
* Use when React Aria's `isInvalid` (or a form library) asserts failure.
|
|
403
|
+
* Pair with `input.negative.text.*` on the validationMessage; do not use
|
|
404
|
+
* the `negative` role on the control itself — that is authorial valence
|
|
405
|
+
* (Lexicon §10.15).
|
|
406
|
+
*/
|
|
407
|
+
invalid?: CoreColorRef;
|
|
396
408
|
}
|
|
397
409
|
/** `navigation` context: adds `selected`, `current`, `visited`, `expanded`. */
|
|
398
410
|
interface NavigationColorStates extends BaseColorStates {
|
|
@@ -407,10 +419,10 @@ interface NavigationColorStates extends BaseColorStates {
|
|
|
407
419
|
}
|
|
408
420
|
/** `informational` context: adds `selected`, `visited`, `expanded`.
|
|
409
421
|
*
|
|
410
|
-
* `expanded` covers in-place disclosure on presentational surfaces
|
|
411
|
-
* collapsible panels, expandable cards). `Disclosure`
|
|
412
|
-
*
|
|
413
|
-
*
|
|
422
|
+
* `expanded` covers in-place disclosure on presentational surfaces
|
|
423
|
+
* (accordions, collapsible panels, expandable cards). Note: `Disclosure`
|
|
424
|
+
* Entity Kinds project to `navigation` (ADR-001); `expanded` here serves
|
|
425
|
+
* presentational surfaces that disclose without being Disclosure entities. */
|
|
414
426
|
interface InformationalColorStates extends BaseColorStates {
|
|
415
427
|
/** Presentational element is **one of many** and the user picked it (selectable list row, focused card in a deck). */
|
|
416
428
|
selected?: CoreColorRef;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Powered by @ttoss/config. https://ttoss.dev/docs/modules/packages/config/ */
|
|
2
|
-
import { a as darkAlternate, i as baseTheme, n as createTheme, r as baseBrief } from "./createTheme-
|
|
2
|
+
import { a as darkAlternate, i as baseTheme, n as createTheme, r as baseBrief } from "./createTheme-Bm9LVQ4w.mjs";
|
|
3
3
|
|
|
4
4
|
//#region src/baseBundle.ts
|
|
5
5
|
/**
|
|
@@ -66,6 +66,7 @@ var baseTheme = {
|
|
|
66
66
|
100: "#fee2e2",
|
|
67
67
|
300: "#fca5a5",
|
|
68
68
|
500: "#ef4444",
|
|
69
|
+
600: "#dc2626",
|
|
69
70
|
700: "#b91c1c",
|
|
70
71
|
900: "#7f1d1d"
|
|
71
72
|
},
|
|
@@ -391,7 +392,7 @@ var baseTheme = {
|
|
|
391
392
|
},
|
|
392
393
|
negative: {
|
|
393
394
|
background: {
|
|
394
|
-
default: "{core.colors.red.
|
|
395
|
+
default: "{core.colors.red.600}",
|
|
395
396
|
hover: "{core.colors.red.700}",
|
|
396
397
|
active: "{core.colors.red.900}",
|
|
397
398
|
disabled: "{core.colors.neutral.200}",
|
|
@@ -400,7 +401,7 @@ var baseTheme = {
|
|
|
400
401
|
expanded: "{core.colors.red.700}"
|
|
401
402
|
},
|
|
402
403
|
border: {
|
|
403
|
-
default: "{core.colors.red.
|
|
404
|
+
default: "{core.colors.red.600}",
|
|
404
405
|
hover: "{core.colors.red.700}",
|
|
405
406
|
active: "{core.colors.red.900}",
|
|
406
407
|
focused: "{core.colors.red.700}",
|
|
@@ -455,7 +456,8 @@ var baseTheme = {
|
|
|
455
456
|
checked: "{core.colors.brand.500}",
|
|
456
457
|
indeterminate: "{core.colors.brand.300}",
|
|
457
458
|
pressed: "{core.colors.neutral.100}",
|
|
458
|
-
expanded: "{core.colors.neutral.50}"
|
|
459
|
+
expanded: "{core.colors.neutral.50}",
|
|
460
|
+
invalid: "{core.colors.neutral.0}"
|
|
459
461
|
},
|
|
460
462
|
border: {
|
|
461
463
|
default: "{core.colors.neutral.300}",
|
|
@@ -467,12 +469,14 @@ var baseTheme = {
|
|
|
467
469
|
checked: "{core.colors.brand.500}",
|
|
468
470
|
indeterminate: "{core.colors.brand.300}",
|
|
469
471
|
pressed: "{core.colors.neutral.500}",
|
|
470
|
-
expanded: "{core.colors.brand.500}"
|
|
472
|
+
expanded: "{core.colors.brand.500}",
|
|
473
|
+
invalid: "{core.colors.red.600}"
|
|
471
474
|
},
|
|
472
475
|
text: {
|
|
473
476
|
default: "{core.colors.neutral.900}",
|
|
474
477
|
disabled: "{core.colors.neutral.500}",
|
|
475
478
|
checked: "{core.colors.neutral.0}",
|
|
479
|
+
invalid: "{core.colors.neutral.900}",
|
|
476
480
|
indeterminate: "{core.colors.neutral.900}"
|
|
477
481
|
}
|
|
478
482
|
},
|
|
@@ -1355,17 +1359,20 @@ var darkAlternate = {
|
|
|
1355
1359
|
selected: "{core.colors.brand.700}",
|
|
1356
1360
|
droptarget: "{core.colors.neutral.700}",
|
|
1357
1361
|
pressed: "{core.colors.neutral.500}",
|
|
1358
|
-
expanded: "{core.colors.neutral.500}"
|
|
1362
|
+
expanded: "{core.colors.neutral.500}",
|
|
1363
|
+
invalid: "{core.colors.neutral.700}"
|
|
1359
1364
|
},
|
|
1360
1365
|
border: {
|
|
1361
1366
|
default: "{core.colors.neutral.500}",
|
|
1362
1367
|
hover: "{core.colors.neutral.300}",
|
|
1363
1368
|
focused: "{core.colors.brand.500}",
|
|
1364
|
-
disabled: "{core.colors.neutral.700}"
|
|
1369
|
+
disabled: "{core.colors.neutral.700}",
|
|
1370
|
+
invalid: "{core.colors.red.300}"
|
|
1365
1371
|
},
|
|
1366
1372
|
text: {
|
|
1367
1373
|
default: "{core.colors.neutral.0}",
|
|
1368
|
-
disabled: "{core.colors.neutral.500}"
|
|
1374
|
+
disabled: "{core.colors.neutral.500}",
|
|
1375
|
+
invalid: "{core.colors.neutral.0}"
|
|
1369
1376
|
}
|
|
1370
1377
|
},
|
|
1371
1378
|
secondary: {
|
|
@@ -1789,10 +1796,17 @@ var levenshtein = (a, b) => {
|
|
|
1789
1796
|
* Validate that every `{ref}` in the merged theme points to an existing path.
|
|
1790
1797
|
* Emits `console.warn` for each broken reference with a "did you mean?" suggestion.
|
|
1791
1798
|
*
|
|
1799
|
+
* `onlyOwnerKeys` restricts which token paths are *checked* (resolution still
|
|
1800
|
+
* looks at the whole tree) — used to validate just the leaves an alternate
|
|
1801
|
+
* introduces without re-warning refs already reported for the base.
|
|
1802
|
+
*
|
|
1792
1803
|
* **DEV-only** — callers gate this behind `process.env.NODE_ENV !== 'production'`
|
|
1793
1804
|
* so bundlers tree-shake the entire call in production builds.
|
|
1794
1805
|
*/
|
|
1795
|
-
var validateRefs = theme => {
|
|
1806
|
+
var validateRefs = (theme, options = {}) => {
|
|
1807
|
+
const {
|
|
1808
|
+
onlyOwnerKeys
|
|
1809
|
+
} = options;
|
|
1796
1810
|
const {
|
|
1797
1811
|
core,
|
|
1798
1812
|
semantic
|
|
@@ -1817,6 +1831,7 @@ var validateRefs = theme => {
|
|
|
1817
1831
|
return "";
|
|
1818
1832
|
};
|
|
1819
1833
|
for (const [ownerKey, value] of Object.entries(all)) {
|
|
1834
|
+
if (onlyOwnerKeys && !onlyOwnerKeys.has(ownerKey)) continue;
|
|
1820
1835
|
if (typeof value !== "string" || !value.includes("{")) continue;
|
|
1821
1836
|
let match;
|
|
1822
1837
|
const re = new RegExp(require_helpers.COMPOUND_REF_RE.source, require_helpers.COMPOUND_REF_RE.flags);
|
|
@@ -1835,6 +1850,41 @@ var validateRefs = theme => {
|
|
|
1835
1850
|
//#endregion
|
|
1836
1851
|
//#region src/createTheme.ts
|
|
1837
1852
|
/**
|
|
1853
|
+
* Deep-clone to break shared references between base and result.
|
|
1854
|
+
* Tokens are primitives (strings, numbers) — `structuredClone` is safe and
|
|
1855
|
+
* more correct than a JSON round-trip (preserves `undefined`, `NaN`, etc.).
|
|
1856
|
+
*
|
|
1857
|
+
* Falls back to a JSON round-trip when `structuredClone` is unavailable
|
|
1858
|
+
* (e.g. Jest's jsdom environment, older embedded runtimes) so importing the
|
|
1859
|
+
* package never throws at module-evaluation time. Token trees are plain
|
|
1860
|
+
* string/number leaves, so the fallback is lossless for valid themes.
|
|
1861
|
+
*/
|
|
1862
|
+
var cloneTokens = value => {
|
|
1863
|
+
if (typeof structuredClone === "function") return structuredClone(value);
|
|
1864
|
+
return JSON.parse(JSON.stringify(value));
|
|
1865
|
+
};
|
|
1866
|
+
/**
|
|
1867
|
+
* DEV-only: validate the refs an alternate introduces against the built base.
|
|
1868
|
+
* The alternate is authored by hand (semantic-only remaps), so a typo'd
|
|
1869
|
+
* dark-mode ref must warn at theme creation instead of silently emitting a
|
|
1870
|
+
* broken CSS var in production.
|
|
1871
|
+
*
|
|
1872
|
+
* Only the leaves the alternate itself provides are checked — base refs were
|
|
1873
|
+
* already validated by `buildTheme`, so they would otherwise warn twice.
|
|
1874
|
+
*/
|
|
1875
|
+
var validateAlternateRefs = ({
|
|
1876
|
+
base,
|
|
1877
|
+
alternate
|
|
1878
|
+
}) => {
|
|
1879
|
+
const alternateKeys = new Set(Object.keys(require_helpers.flattenObject(alternate.semantic, "semantic")));
|
|
1880
|
+
validateRefs({
|
|
1881
|
+
core: base.core,
|
|
1882
|
+
semantic: require_helpers.deepMerge(base.semantic, alternate.semantic)
|
|
1883
|
+
}, {
|
|
1884
|
+
onlyOwnerKeys: alternateKeys
|
|
1885
|
+
});
|
|
1886
|
+
};
|
|
1887
|
+
/**
|
|
1838
1888
|
* Creates a fully resolved `ThemeTokens` by merging partial overrides into a base.
|
|
1839
1889
|
* Internal engine — consumers should use `createTheme` which returns `ThemeBundle`.
|
|
1840
1890
|
*
|
|
@@ -1846,13 +1896,28 @@ var buildTheme = ({
|
|
|
1846
1896
|
} = {}) => {
|
|
1847
1897
|
const merged = require_helpers.deepMerge(base, overrides);
|
|
1848
1898
|
if (process.env.NODE_ENV !== "production") validateRefs(merged);
|
|
1849
|
-
return
|
|
1899
|
+
return cloneTokens(merged);
|
|
1850
1900
|
};
|
|
1851
1901
|
var resolveAlternate = (alternate, parentBundle) => {
|
|
1852
1902
|
if (alternate === null) return void 0;
|
|
1853
1903
|
if (alternate !== void 0) return alternate;
|
|
1854
1904
|
return parentBundle?.alternate ?? darkAlternate;
|
|
1855
1905
|
};
|
|
1906
|
+
/** Resolve every `createTheme` input against its `extends` parent + defaults. */
|
|
1907
|
+
var resolveBundleInputs = ({
|
|
1908
|
+
parentBundle,
|
|
1909
|
+
baseMode,
|
|
1910
|
+
base,
|
|
1911
|
+
alternate,
|
|
1912
|
+
brief
|
|
1913
|
+
}) => {
|
|
1914
|
+
return {
|
|
1915
|
+
resolvedBase: base ?? parentBundle?.base,
|
|
1916
|
+
resolvedBaseMode: baseMode ?? parentBundle?.baseMode ?? "light",
|
|
1917
|
+
resolvedAlternate: resolveAlternate(alternate, parentBundle),
|
|
1918
|
+
resolvedMeta: brief ?? parentBundle?.meta
|
|
1919
|
+
};
|
|
1920
|
+
};
|
|
1856
1921
|
/**
|
|
1857
1922
|
* Creates a theme bundle with an optional alternate color mode.
|
|
1858
1923
|
*
|
|
@@ -1903,16 +1968,29 @@ var createTheme = ({
|
|
|
1903
1968
|
alternate,
|
|
1904
1969
|
brief
|
|
1905
1970
|
} = {}) => {
|
|
1906
|
-
const
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1971
|
+
const {
|
|
1972
|
+
resolvedBase,
|
|
1973
|
+
resolvedBaseMode,
|
|
1974
|
+
resolvedAlternate,
|
|
1975
|
+
resolvedMeta
|
|
1976
|
+
} = resolveBundleInputs({
|
|
1977
|
+
parentBundle,
|
|
1978
|
+
baseMode,
|
|
1979
|
+
base,
|
|
1980
|
+
alternate,
|
|
1981
|
+
brief
|
|
1982
|
+
});
|
|
1983
|
+
const builtBase = buildTheme({
|
|
1984
|
+
base: resolvedBase,
|
|
1985
|
+
overrides
|
|
1986
|
+
});
|
|
1987
|
+
if (process.env.NODE_ENV !== "production" && resolvedAlternate) validateAlternateRefs({
|
|
1988
|
+
base: builtBase,
|
|
1989
|
+
alternate: resolvedAlternate
|
|
1990
|
+
});
|
|
1910
1991
|
return {
|
|
1911
1992
|
baseMode: resolvedBaseMode,
|
|
1912
|
-
base:
|
|
1913
|
-
base: resolvedBase,
|
|
1914
|
-
overrides
|
|
1915
|
-
}),
|
|
1993
|
+
base: builtBase,
|
|
1916
1994
|
alternate: resolvedAlternate,
|
|
1917
1995
|
...(resolvedMeta ? {
|
|
1918
1996
|
meta: resolvedMeta
|