@unpunnyfuns/swatchbook-blocks 0.60.9 → 0.62.1
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 +6 -6
- package/dist/index.d.mts +80 -64
- package/dist/index.mjs +239 -507
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -8
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import './style.css';
|
|
2
|
-
import
|
|
2
|
+
import { COLOR_FORMATS } from "@unpunnyfuns/swatchbook-core/color-formats";
|
|
3
|
+
import { formatColor, parseColor } from "@unpunnyfuns/swatchbook-core/format-color";
|
|
3
4
|
import { createContext, memo, useCallback, useContext, useDeferredValue, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
4
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
5
6
|
import { getVariance, listPaths, resolveAllAt } from "@unpunnyfuns/swatchbook-core/graph";
|
|
@@ -7,188 +8,18 @@ import { makeCssVar } from "@unpunnyfuns/swatchbook-core/css-var";
|
|
|
7
8
|
import { SWATCHBOOK_STYLE_ELEMENT_ID, ensureStyleElement } from "@unpunnyfuns/swatchbook-core/style-element";
|
|
8
9
|
import { tupleToName } from "@unpunnyfuns/swatchbook-core/themes";
|
|
9
10
|
import { addons } from "storybook/preview-api";
|
|
10
|
-
import { axes, css, cssVarPrefix, defaultTuple, diagnostics, listing, presets, tokenGraph } from "virtual:swatchbook/tokens";
|
|
11
11
|
import { dataAttr } from "@unpunnyfuns/swatchbook-core/data-attr";
|
|
12
12
|
import { matchPath } from "@unpunnyfuns/swatchbook-core/match-path";
|
|
13
13
|
import { fuzzyFilter } from "@unpunnyfuns/swatchbook-core/fuzzy";
|
|
14
14
|
import cx from "clsx";
|
|
15
|
-
//#region src/
|
|
16
|
-
const COLOR_FORMATS = [
|
|
17
|
-
"hex",
|
|
18
|
-
"rgb",
|
|
19
|
-
"hsl",
|
|
20
|
-
"oklch",
|
|
21
|
-
"raw"
|
|
22
|
-
];
|
|
23
|
-
const DEFAULT_FALLBACK = "—";
|
|
24
|
-
/**
|
|
25
|
-
* Convert Terrazzo's normalized color payload into a display string in the
|
|
26
|
-
* requested format. Pure function — never throws; returns `{ value: '—' }`
|
|
27
|
-
* for unrecognized input so calling blocks don't need try/catch.
|
|
28
|
-
*/
|
|
29
|
-
function formatColor(value, format, fallback = DEFAULT_FALLBACK) {
|
|
30
|
-
const normalized = coerce(value);
|
|
31
|
-
if (!normalized) return {
|
|
32
|
-
value: stringifyFallback(value, fallback),
|
|
33
|
-
outOfGamut: false
|
|
34
|
-
};
|
|
35
|
-
if (format === "raw") return {
|
|
36
|
-
value: compactJson(normalized),
|
|
37
|
-
outOfGamut: false
|
|
38
|
-
};
|
|
39
|
-
const color = toColor(normalized);
|
|
40
|
-
if (!color) return {
|
|
41
|
-
value: stringifyFallback(value, fallback),
|
|
42
|
-
outOfGamut: false
|
|
43
|
-
};
|
|
44
|
-
const alpha = typeof normalized.alpha === "number" ? normalized.alpha : 1;
|
|
45
|
-
if (format === "hex") return formatHex(color, alpha);
|
|
46
|
-
if (format === "rgb") return formatRgb(color, alpha);
|
|
47
|
-
if (format === "hsl") return formatHsl(color, alpha);
|
|
48
|
-
return formatOklch(color, alpha);
|
|
49
|
-
}
|
|
50
|
-
function coerce(value) {
|
|
51
|
-
if (!value || typeof value !== "object") return null;
|
|
52
|
-
const v = value;
|
|
53
|
-
const colorSpace = typeof v.colorSpace === "string" ? v.colorSpace : void 0;
|
|
54
|
-
const components = Array.isArray(v.components) ? v.components : Array.isArray(v.channels) ? v.channels : void 0;
|
|
55
|
-
if (!colorSpace || !components) {
|
|
56
|
-
if (typeof v.hex === "string") return {
|
|
57
|
-
colorSpace: "srgb",
|
|
58
|
-
components: hexToComponents(v.hex)
|
|
59
|
-
};
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
const alpha = typeof v.alpha === "number" ? v.alpha : void 0;
|
|
63
|
-
const hexVal = v["hex"];
|
|
64
|
-
const hex = typeof hexVal === "string" ? hexVal : void 0;
|
|
65
|
-
return {
|
|
66
|
-
colorSpace,
|
|
67
|
-
components,
|
|
68
|
-
...alpha !== void 0 && { alpha },
|
|
69
|
-
...hex !== void 0 && { hex }
|
|
70
|
-
};
|
|
71
|
-
}
|
|
72
|
-
function hexToComponents(hex) {
|
|
73
|
-
const h = hex.replace("#", "");
|
|
74
|
-
const expanded = h.length === 3 || h.length === 4 ? h.split("").map((c) => c + c).join("") : h;
|
|
75
|
-
return [
|
|
76
|
-
parseInt(expanded.slice(0, 2), 16) / 255,
|
|
77
|
-
parseInt(expanded.slice(2, 4), 16) / 255,
|
|
78
|
-
parseInt(expanded.slice(4, 6), 16) / 255
|
|
79
|
-
];
|
|
80
|
-
}
|
|
15
|
+
//#region src/internal/styles.tsx
|
|
81
16
|
/**
|
|
82
|
-
*
|
|
83
|
-
*
|
|
17
|
+
* Chrome-style primitives shared across every block. Kept as JS exports
|
|
18
|
+
* for the inline-style sites that still compose them into per-block style
|
|
19
|
+
* objects (e.g. TokenNavigator's `typePill` that builds on the shared
|
|
20
|
+
* pill base). The pure direct-reference chrome — surface wrapper, caption,
|
|
21
|
+
* empty-state — lives in `styles.css` and is applied via class names.
|
|
84
22
|
*/
|
|
85
|
-
const COLORJS_SPACE_ALIASES = {
|
|
86
|
-
"display-p3": "p3",
|
|
87
|
-
"a98-rgb": "a98rgb",
|
|
88
|
-
"prophoto-rgb": "prophoto"
|
|
89
|
-
};
|
|
90
|
-
function toColor(normalized) {
|
|
91
|
-
const source = normalized.components ?? normalized.channels ?? [];
|
|
92
|
-
const coords = [
|
|
93
|
-
numberOrZero(source[0]),
|
|
94
|
-
numberOrZero(source[1]),
|
|
95
|
-
numberOrZero(source[2])
|
|
96
|
-
];
|
|
97
|
-
const space = COLORJS_SPACE_ALIASES[normalized.colorSpace] ?? normalized.colorSpace;
|
|
98
|
-
try {
|
|
99
|
-
return new Color(space, coords, normalized.alpha ?? 1);
|
|
100
|
-
} catch {
|
|
101
|
-
return null;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
function numberOrZero(n) {
|
|
105
|
-
return typeof n === "number" && !Number.isNaN(n) ? n : 0;
|
|
106
|
-
}
|
|
107
|
-
function coord(color, i) {
|
|
108
|
-
const c = color.coords[i];
|
|
109
|
-
return typeof c === "number" && !Number.isNaN(c) ? c : 0;
|
|
110
|
-
}
|
|
111
|
-
function formatHex(color, alpha) {
|
|
112
|
-
const srgb = color.to("srgb");
|
|
113
|
-
if (!srgb.inGamut("srgb")) return {
|
|
114
|
-
value: formatRgb(color, alpha).value,
|
|
115
|
-
outOfGamut: true
|
|
116
|
-
};
|
|
117
|
-
const r = clamp255(coord(srgb, 0));
|
|
118
|
-
const g = clamp255(coord(srgb, 1));
|
|
119
|
-
const b = clamp255(coord(srgb, 2));
|
|
120
|
-
const base = `#${toHexByte(r)}${toHexByte(g)}${toHexByte(b)}`;
|
|
121
|
-
if (alpha >= 1) return {
|
|
122
|
-
value: base,
|
|
123
|
-
outOfGamut: false
|
|
124
|
-
};
|
|
125
|
-
return {
|
|
126
|
-
value: `${base}${toHexByte(clamp255(alpha))}`,
|
|
127
|
-
outOfGamut: false
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
function formatRgb(color, alpha) {
|
|
131
|
-
const srgb = color.to("srgb");
|
|
132
|
-
const inGamut = srgb.inGamut("srgb");
|
|
133
|
-
const body = `${Math.round(clampUnit(coord(srgb, 0)) * 255)} ${Math.round(clampUnit(coord(srgb, 1)) * 255)} ${Math.round(clampUnit(coord(srgb, 2)) * 255)}`;
|
|
134
|
-
return {
|
|
135
|
-
value: alpha >= 1 ? `rgb(${body})` : `rgb(${body} / ${roundAlpha(alpha)})`,
|
|
136
|
-
outOfGamut: !inGamut
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
function formatHsl(color, alpha) {
|
|
140
|
-
const hsl = color.to("hsl");
|
|
141
|
-
const inGamut = color.to("srgb").inGamut("srgb");
|
|
142
|
-
const body = `${roundHue(coord(hsl, 0))} ${roundPercent(coord(hsl, 1))}% ${roundPercent(coord(hsl, 2))}%`;
|
|
143
|
-
return {
|
|
144
|
-
value: alpha >= 1 ? `hsl(${body})` : `hsl(${body} / ${roundAlpha(alpha)})`,
|
|
145
|
-
outOfGamut: !inGamut
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
function formatOklch(color, alpha) {
|
|
149
|
-
const oklch = color.to("oklch");
|
|
150
|
-
const body = `${roundTo(coord(oklch, 0), 3)} ${roundTo(coord(oklch, 1), 3)} ${roundTo(coord(oklch, 2), 2)}`;
|
|
151
|
-
return {
|
|
152
|
-
value: alpha >= 1 ? `oklch(${body})` : `oklch(${body} / ${roundAlpha(alpha)})`,
|
|
153
|
-
outOfGamut: false
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function clamp255(n) {
|
|
157
|
-
return Math.max(0, Math.min(255, Math.round(n * 255)));
|
|
158
|
-
}
|
|
159
|
-
function clampUnit(n) {
|
|
160
|
-
return Math.max(0, Math.min(1, n));
|
|
161
|
-
}
|
|
162
|
-
function toHexByte(n) {
|
|
163
|
-
return n.toString(16).padStart(2, "0");
|
|
164
|
-
}
|
|
165
|
-
function roundTo(n, digits) {
|
|
166
|
-
const f = 10 ** digits;
|
|
167
|
-
return Math.round(n * f) / f;
|
|
168
|
-
}
|
|
169
|
-
function roundHue(h) {
|
|
170
|
-
return roundTo((h % 360 + 360) % 360, 1);
|
|
171
|
-
}
|
|
172
|
-
function roundPercent(n) {
|
|
173
|
-
return Math.round(n * 10) / 10;
|
|
174
|
-
}
|
|
175
|
-
function roundAlpha(a) {
|
|
176
|
-
return roundTo(a, 3);
|
|
177
|
-
}
|
|
178
|
-
function compactJson(value) {
|
|
179
|
-
const parts = [`"colorSpace":${JSON.stringify(value.colorSpace)}`];
|
|
180
|
-
const components = value.components ?? value.channels;
|
|
181
|
-
if (components) parts.push(`"components":[${components.map((c) => c === null ? "null" : c).join(", ")}]`);
|
|
182
|
-
if (typeof value.alpha === "number" && value.alpha !== 1) parts.push(`"alpha":${value.alpha}`);
|
|
183
|
-
return `{ ${parts.join(", ")} }`;
|
|
184
|
-
}
|
|
185
|
-
function stringifyFallback(value, fallback) {
|
|
186
|
-
if (value == null) return fallback;
|
|
187
|
-
if (typeof value === "string" || typeof value === "number") return String(value);
|
|
188
|
-
return fallback;
|
|
189
|
-
}
|
|
190
|
-
//#endregion
|
|
191
|
-
//#region src/internal/styles.tsx
|
|
192
23
|
const TEXT_MUTED = "var(--swatchbook-text-muted, CanvasText)";
|
|
193
24
|
const SURFACE_RAISED = "var(--swatchbook-surface-raised, Canvas)";
|
|
194
25
|
const SURFACE_MUTED = "var(--swatchbook-surface-muted, rgba(128,128,128,0.15))";
|
|
@@ -196,7 +27,7 @@ const BORDER_FAINT = `1px solid var(--swatchbook-border-default, rgba(128,128,12
|
|
|
196
27
|
const BORDER_STRONG = `1px solid var(--swatchbook-border-default, rgba(128,128,128,0.3))`;
|
|
197
28
|
/**
|
|
198
29
|
* Inner content for a block's "nothing to render" state. Call sites wrap
|
|
199
|
-
* it in their own block wrapper (which already carries `
|
|
30
|
+
* it in their own block wrapper (which already carries `blockWrapperAttrs`), so
|
|
200
31
|
* the message itself just needs the muted type.
|
|
201
32
|
*/
|
|
202
33
|
function EmptyState({ children }) {
|
|
@@ -243,11 +74,6 @@ function ensureSubscribed$1() {
|
|
|
243
74
|
channel.on("updateGlobals", onGlobals);
|
|
244
75
|
channel.on("setGlobals", onGlobals);
|
|
245
76
|
}
|
|
246
|
-
/**
|
|
247
|
-
* Subscribe at module load so the `SET_GLOBALS` emission from preview init
|
|
248
|
-
* lands in our snapshot before any block renders. Running `useSyncExternalStore`'s
|
|
249
|
-
* `subscribe` lazily on first hook call would miss the event in most cases.
|
|
250
|
-
*/
|
|
251
77
|
ensureSubscribed$1();
|
|
252
78
|
function subscribe$1(cb) {
|
|
253
79
|
ensureSubscribed$1();
|
|
@@ -293,7 +119,7 @@ function useActiveTheme() {
|
|
|
293
119
|
* Active axis tuple for the current story/docs render — `Record<axisName,
|
|
294
120
|
* contextName>`. Derived from the same input as {@link ThemeContext}; split
|
|
295
121
|
* out so consumers needing per-axis info (toolbar, panel, tuple-aware
|
|
296
|
-
* blocks) don't have to reparse the composed
|
|
122
|
+
* blocks) don't have to reparse the composed theme name.
|
|
297
123
|
*/
|
|
298
124
|
const AxesContext = createContext({});
|
|
299
125
|
function useActiveAxes() {
|
|
@@ -321,49 +147,66 @@ function useColorFormat() {
|
|
|
321
147
|
/**
|
|
322
148
|
* Live token snapshot backed by the addon's preview dev-time HMR event.
|
|
323
149
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
329
|
-
*
|
|
330
|
-
* the latest snapshot via `useSyncExternalStore`, so hooks that read
|
|
331
|
-
* through this module re-render in place on each token save.
|
|
150
|
+
* The initial snapshot is *injected* by the addon preview via
|
|
151
|
+
* {@link registerTokenSource} rather than imported from the addon's
|
|
152
|
+
* `virtual:swatchbook/tokens` build artifact — so blocks carries no
|
|
153
|
+
* dependency on that module and imports cleanly standalone (outside
|
|
154
|
+
* Storybook, in unit tests, in the docs site). Until something registers
|
|
155
|
+
* a source, blocks render from empty defaults.
|
|
332
156
|
*
|
|
333
|
-
*
|
|
334
|
-
* channel
|
|
335
|
-
*
|
|
157
|
+
* For dev-time updates this module subscribes to `TOKENS_UPDATED_EVENT`
|
|
158
|
+
* on Storybook's channel (which the addon preview re-broadcasts from its
|
|
159
|
+
* own HMR listener) and exposes the latest snapshot via
|
|
160
|
+
* `useSyncExternalStore`, so hooks re-render in place on each token save.
|
|
336
161
|
*/
|
|
337
162
|
const TOKENS_UPDATED_EVENT = "swatchbook/tokens-updated";
|
|
338
163
|
let snapshot = {
|
|
339
|
-
axes,
|
|
340
|
-
presets,
|
|
341
|
-
diagnostics,
|
|
342
|
-
css,
|
|
343
|
-
cssVarPrefix,
|
|
344
|
-
listing:
|
|
345
|
-
tokenGraph
|
|
346
|
-
|
|
164
|
+
axes: [],
|
|
165
|
+
presets: [],
|
|
166
|
+
diagnostics: [],
|
|
167
|
+
css: "",
|
|
168
|
+
cssVarPrefix: "",
|
|
169
|
+
listing: {},
|
|
170
|
+
tokenGraph: {
|
|
171
|
+
nodes: {},
|
|
172
|
+
axes: [],
|
|
173
|
+
axisDefaults: {},
|
|
174
|
+
axisContexts: {}
|
|
175
|
+
},
|
|
176
|
+
defaultTuple: {},
|
|
347
177
|
version: 0
|
|
348
178
|
};
|
|
349
179
|
const listeners = /* @__PURE__ */ new Set();
|
|
350
180
|
let subscribed = false;
|
|
181
|
+
function applyPatch(patch) {
|
|
182
|
+
snapshot = {
|
|
183
|
+
axes: patch.axes ?? snapshot.axes,
|
|
184
|
+
presets: patch.presets ?? snapshot.presets,
|
|
185
|
+
diagnostics: patch.diagnostics ?? snapshot.diagnostics,
|
|
186
|
+
css: patch.css ?? snapshot.css,
|
|
187
|
+
cssVarPrefix: patch.cssVarPrefix ?? snapshot.cssVarPrefix,
|
|
188
|
+
listing: patch.listing ?? snapshot.listing,
|
|
189
|
+
tokenGraph: patch.tokenGraph ?? snapshot.tokenGraph,
|
|
190
|
+
defaultTuple: patch.defaultTuple ?? snapshot.defaultTuple,
|
|
191
|
+
version: snapshot.version + 1
|
|
192
|
+
};
|
|
193
|
+
for (const cb of listeners) cb();
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Seed the initial token snapshot. The addon preview calls this once at
|
|
197
|
+
* init with the build-time `virtual:swatchbook/tokens` data. Keeping the
|
|
198
|
+
* virtual-module read on the addon side (the package that owns it) lets
|
|
199
|
+
* blocks import cleanly without it. No-op fields fall back to the current
|
|
200
|
+
* snapshot, so a partial source is safe.
|
|
201
|
+
*/
|
|
202
|
+
function registerTokenSource(source) {
|
|
203
|
+
applyPatch(source);
|
|
204
|
+
}
|
|
351
205
|
function ensureSubscribed() {
|
|
352
206
|
if (subscribed || typeof window === "undefined") return;
|
|
353
207
|
subscribed = true;
|
|
354
208
|
addons.getChannel().on(TOKENS_UPDATED_EVENT, (payload) => {
|
|
355
|
-
|
|
356
|
-
axes: payload.axes ?? snapshot.axes,
|
|
357
|
-
presets: payload.presets ?? snapshot.presets,
|
|
358
|
-
diagnostics: payload.diagnostics ?? snapshot.diagnostics,
|
|
359
|
-
css: payload.css ?? snapshot.css,
|
|
360
|
-
cssVarPrefix: payload.cssVarPrefix ?? snapshot.cssVarPrefix,
|
|
361
|
-
listing: payload.listing ?? snapshot.listing,
|
|
362
|
-
tokenGraph: payload.tokenGraph ?? snapshot.tokenGraph,
|
|
363
|
-
defaultTuple: payload.defaultTuple ?? snapshot.defaultTuple,
|
|
364
|
-
version: snapshot.version + 1
|
|
365
|
-
};
|
|
366
|
-
for (const cb of listeners) cb();
|
|
209
|
+
applyPatch(payload);
|
|
367
210
|
});
|
|
368
211
|
}
|
|
369
212
|
ensureSubscribed();
|
|
@@ -385,33 +228,24 @@ function useTokenSnapshot() {
|
|
|
385
228
|
}
|
|
386
229
|
//#endregion
|
|
387
230
|
//#region src/internal/use-project.ts
|
|
231
|
+
function computeVarianceByPath(graph) {
|
|
232
|
+
if (!graph) return {};
|
|
233
|
+
const out = {};
|
|
234
|
+
for (const path of listPaths(graph)) out[path] = getVariance(graph, path);
|
|
235
|
+
return out;
|
|
236
|
+
}
|
|
388
237
|
function ensureStylesheet(css) {
|
|
389
238
|
ensureStyleElement(SWATCHBOOK_STYLE_ELEMENT_ID, css);
|
|
390
239
|
}
|
|
391
|
-
function defaultTuple
|
|
240
|
+
function defaultTuple(axes) {
|
|
392
241
|
const out = {};
|
|
393
242
|
for (const axis of axes) out[axis.name] = axis.default;
|
|
394
243
|
return out;
|
|
395
244
|
}
|
|
396
|
-
/**
|
|
397
|
-
* Build a `resolveAt` accessor backed by the token graph. Returns an
|
|
398
|
-
* empty resolver when no graph is present (test stubs, partial
|
|
399
|
-
* snapshots). Stable identity when memoized on `tokenGraph` — the
|
|
400
|
-
* graph is a module-level virtual-module export so its reference stays
|
|
401
|
-
* constant for the lifetime of the iframe.
|
|
402
|
-
*/
|
|
403
245
|
function makeResolveAt(graph) {
|
|
404
246
|
if (!graph) return () => ({});
|
|
405
247
|
return (tuple) => resolveAllAt(graph, tuple);
|
|
406
248
|
}
|
|
407
|
-
/**
|
|
408
|
-
* Build the `resolveAt` accessor for a snapshot. Prefers the
|
|
409
|
-
* snapshot's own `resolveAt` (the addon's preview decorator
|
|
410
|
-
* pre-builds one at module load — see `previewResolveAt` in
|
|
411
|
-
* `packages/addon/src/preview.tsx`), otherwise builds one from
|
|
412
|
-
* `tokenGraph`. Hand-built snapshots can omit `resolveAt`;
|
|
413
|
-
* the graph-backed fallback covers them.
|
|
414
|
-
*/
|
|
415
249
|
function snapshotResolveAt(snapshot) {
|
|
416
250
|
if (snapshot.resolveAt) return snapshot.resolveAt;
|
|
417
251
|
return makeResolveAt(snapshot.tokenGraph);
|
|
@@ -441,12 +275,7 @@ function useProject() {
|
|
|
441
275
|
if (!snapshot) return null;
|
|
442
276
|
return snapshotResolveAt(snapshot);
|
|
443
277
|
}, [tokenGraph, activeTheme]);
|
|
444
|
-
const derivedVarianceByPath = useMemo(() =>
|
|
445
|
-
if (!tokenGraph) return {};
|
|
446
|
-
const out = {};
|
|
447
|
-
for (const path of listPaths(tokenGraph)) out[path] = getVariance(tokenGraph, path);
|
|
448
|
-
return out;
|
|
449
|
-
}, [tokenGraph]);
|
|
278
|
+
const derivedVarianceByPath = useMemo(() => computeVarianceByPath(tokenGraph), [tokenGraph]);
|
|
450
279
|
const providerData = useMemo(() => {
|
|
451
280
|
if (!snapshot || !resolveAt || !axes || !activeAxes) return null;
|
|
452
281
|
return {
|
|
@@ -458,12 +287,6 @@ function useProject() {
|
|
|
458
287
|
cssVarPrefix: cssVarPrefix ?? "",
|
|
459
288
|
listing: listing ?? {},
|
|
460
289
|
varianceByPath: derivedVarianceByPath,
|
|
461
|
-
tokenGraph: tokenGraph ?? {
|
|
462
|
-
nodes: {},
|
|
463
|
-
axes: [],
|
|
464
|
-
axisDefaults: {},
|
|
465
|
-
axisContexts: {}
|
|
466
|
-
},
|
|
467
290
|
resolveAt
|
|
468
291
|
};
|
|
469
292
|
}, [
|
|
@@ -482,37 +305,24 @@ function useProject() {
|
|
|
482
305
|
return providerData ?? fallback;
|
|
483
306
|
}
|
|
484
307
|
function useVirtualModuleFallback(enabled) {
|
|
485
|
-
const
|
|
308
|
+
const contextThemeName = useActiveTheme();
|
|
486
309
|
const contextAxes = useActiveAxes();
|
|
487
310
|
const channelGlobals = useChannelGlobals();
|
|
488
|
-
/**
|
|
489
|
-
* Subscribe to the live token snapshot rather than reading the virtual
|
|
490
|
-
* module's module-level exports directly. Initial values come from
|
|
491
|
-
* `virtual:swatchbook/tokens` at load time; subsequent dev-time edits
|
|
492
|
-
* flow through the addon's HMR channel and update this snapshot in
|
|
493
|
-
* place so blocks re-render without a full preview reload.
|
|
494
|
-
*/
|
|
495
311
|
const tokens = useTokenSnapshot();
|
|
496
312
|
useEffect(() => {
|
|
497
313
|
if (!enabled) return;
|
|
498
314
|
ensureStylesheet(tokens.css);
|
|
499
315
|
}, [enabled, tokens.css]);
|
|
500
316
|
const activeAxes = useMemo(() => {
|
|
501
|
-
return Object.keys(contextAxes).length > 0 ? { ...contextAxes } : channelGlobals.axes ?? defaultTuple
|
|
317
|
+
return Object.keys(contextAxes).length > 0 ? { ...contextAxes } : channelGlobals.axes ?? defaultTuple(tokens.axes);
|
|
502
318
|
}, [
|
|
503
319
|
contextAxes,
|
|
504
320
|
channelGlobals.axes,
|
|
505
321
|
tokens.axes
|
|
506
322
|
]);
|
|
507
|
-
const activeTheme =
|
|
323
|
+
const activeTheme = contextThemeName || tupleToName(tokens.axes, activeAxes);
|
|
508
324
|
const resolveAt = useMemo(() => makeResolveAt(tokens.tokenGraph), [tokens.tokenGraph]);
|
|
509
|
-
const fallbackVarianceByPath = useMemo(() =>
|
|
510
|
-
const graph = tokens.tokenGraph;
|
|
511
|
-
if (!graph) return {};
|
|
512
|
-
const out = {};
|
|
513
|
-
for (const path of listPaths(graph)) out[path] = getVariance(graph, path);
|
|
514
|
-
return out;
|
|
515
|
-
}, [tokens.tokenGraph]);
|
|
325
|
+
const fallbackVarianceByPath = useMemo(() => computeVarianceByPath(tokens.tokenGraph), [tokens.tokenGraph]);
|
|
516
326
|
return useMemo(() => ({
|
|
517
327
|
activeTheme,
|
|
518
328
|
activeAxes,
|
|
@@ -522,7 +332,6 @@ function useVirtualModuleFallback(enabled) {
|
|
|
522
332
|
cssVarPrefix: tokens.cssVarPrefix,
|
|
523
333
|
listing: tokens.listing,
|
|
524
334
|
varianceByPath: fallbackVarianceByPath,
|
|
525
|
-
tokenGraph: tokens.tokenGraph,
|
|
526
335
|
resolveAt
|
|
527
336
|
}), [
|
|
528
337
|
activeTheme,
|
|
@@ -532,7 +341,6 @@ function useVirtualModuleFallback(enabled) {
|
|
|
532
341
|
tokens.cssVarPrefix,
|
|
533
342
|
tokens.listing,
|
|
534
343
|
fallbackVarianceByPath,
|
|
535
|
-
tokens.tokenGraph,
|
|
536
344
|
resolveAt
|
|
537
345
|
]);
|
|
538
346
|
}
|
|
@@ -590,6 +398,32 @@ function BorderSample({ path }) {
|
|
|
590
398
|
});
|
|
591
399
|
}
|
|
592
400
|
//#endregion
|
|
401
|
+
//#region src/internal/composite-sample-format.ts
|
|
402
|
+
/**
|
|
403
|
+
* Display a composite sub-field dimension (shadow offset / blur / spread,
|
|
404
|
+
* border width, …) in the preview tables. Renders `—` for a missing
|
|
405
|
+
* sub-field and falls back to JSON for shapes it doesn't recognize.
|
|
406
|
+
*
|
|
407
|
+
* Distinct from `format-token-value`'s internal `formatDimension`, which
|
|
408
|
+
* formats a token's top-level value and has no `—` placeholder — these are
|
|
409
|
+
* the per-layer sample formatters shared by `ShadowPreview` + `BorderPreview`.
|
|
410
|
+
*/
|
|
411
|
+
function formatDimension$1(raw) {
|
|
412
|
+
if (raw == null) return "—";
|
|
413
|
+
if (typeof raw === "number") return String(raw);
|
|
414
|
+
if (typeof raw === "string") return raw;
|
|
415
|
+
if (typeof raw === "object") {
|
|
416
|
+
const v = raw;
|
|
417
|
+
if (typeof v.value === "number" && typeof v.unit === "string") return `${v.value}${v.unit}`;
|
|
418
|
+
}
|
|
419
|
+
return JSON.stringify(raw);
|
|
420
|
+
}
|
|
421
|
+
/** Display a composite sub-field color via the active format; `—` when absent. */
|
|
422
|
+
function formatSubColor(raw, format) {
|
|
423
|
+
if (raw == null) return "—";
|
|
424
|
+
return formatColor(raw, format).value;
|
|
425
|
+
}
|
|
426
|
+
//#endregion
|
|
593
427
|
//#region src/internal/data-attr.ts
|
|
594
428
|
/**
|
|
595
429
|
* Marker attribute set on every block wrapper. Retained as a stable hook
|
|
@@ -597,14 +431,6 @@ function BorderSample({ path }) {
|
|
|
597
431
|
* override block chrome without relying on hashed class names).
|
|
598
432
|
*/
|
|
599
433
|
const BLOCK_ATTR = "data-swatchbook-block";
|
|
600
|
-
/**
|
|
601
|
-
* Opt-out class that Storybook's `.sbdocs` stylesheet uses to self-exclude
|
|
602
|
-
* on MDX docs pages — every `.sbdocs` house rule is wrapped in
|
|
603
|
-
* `:not(.sb-unstyled, .sb-unstyled *)`, so any descendant of a `.sb-unstyled`
|
|
604
|
-
* container is left alone. Stamped onto every block wrapper so blocks
|
|
605
|
-
* render identically in MDX docs and regular stories without fighting
|
|
606
|
-
* cascade specificity.
|
|
607
|
-
*/
|
|
608
434
|
const WRAPPER_CLASSES = "sb-unstyled sb-block";
|
|
609
435
|
/**
|
|
610
436
|
* Spread helper for the common block wrapper. Returns:
|
|
@@ -622,7 +448,7 @@ const WRAPPER_CLASSES = "sb-unstyled sb-block";
|
|
|
622
448
|
* MDX docs house styles self-exclude the subtree, plus `sb-block`
|
|
623
449
|
* which carries the shared chrome from `internal/styles.css`.
|
|
624
450
|
*/
|
|
625
|
-
function
|
|
451
|
+
function blockWrapperAttrs(prefix, tuple) {
|
|
626
452
|
return {
|
|
627
453
|
...perAxisAttrs(prefix, tuple),
|
|
628
454
|
[BLOCK_ATTR]: "",
|
|
@@ -728,29 +554,14 @@ function toMagnitude(v) {
|
|
|
728
554
|
}
|
|
729
555
|
return NaN;
|
|
730
556
|
}
|
|
731
|
-
/**
|
|
732
|
-
* Coerce a possibly-null/undefined number to 0 — `coords` returns
|
|
733
|
-
* `(number | null)[]` and `noUncheckedIndexedAccess` adds `undefined`
|
|
734
|
-
* on top. `typeof` narrows the union for the comparator below.
|
|
735
|
-
*/
|
|
736
557
|
function safeNumber(v) {
|
|
737
558
|
return typeof v === "number" && Number.isFinite(v) ? v : 0;
|
|
738
559
|
}
|
|
739
560
|
function colorKey(v) {
|
|
740
|
-
|
|
561
|
+
const color = parseColor(v);
|
|
562
|
+
if (!color) return null;
|
|
741
563
|
try {
|
|
742
|
-
const
|
|
743
|
-
let source;
|
|
744
|
-
if (typeof c.hex === "string") source = c.hex;
|
|
745
|
-
else if (typeof c.colorSpace === "string") {
|
|
746
|
-
const channels = Array.isArray(c.components) ? c.components : Array.isArray(c.channels) ? c.channels : void 0;
|
|
747
|
-
if (!channels) return null;
|
|
748
|
-
source = {
|
|
749
|
-
space: c.colorSpace,
|
|
750
|
-
coords: channels
|
|
751
|
-
};
|
|
752
|
-
} else return null;
|
|
753
|
-
const [l, chroma, h] = new Color(source).to("oklch").coords;
|
|
564
|
+
const [l, chroma, h] = color.to("oklch").coords;
|
|
754
565
|
return {
|
|
755
566
|
l: safeNumber(l),
|
|
756
567
|
c: safeNumber(chroma),
|
|
@@ -768,20 +579,6 @@ function toDisplayable(v) {
|
|
|
768
579
|
}
|
|
769
580
|
//#endregion
|
|
770
581
|
//#region src/BorderPreview.tsx
|
|
771
|
-
function formatDimension$2(raw) {
|
|
772
|
-
if (raw == null) return "—";
|
|
773
|
-
if (typeof raw === "number") return String(raw);
|
|
774
|
-
if (typeof raw === "string") return raw;
|
|
775
|
-
if (typeof raw === "object") {
|
|
776
|
-
const v = raw;
|
|
777
|
-
if (typeof v.value === "number" && typeof v.unit === "string") return `${v.value}${v.unit}`;
|
|
778
|
-
}
|
|
779
|
-
return JSON.stringify(raw);
|
|
780
|
-
}
|
|
781
|
-
function formatSubColor$1(raw, format) {
|
|
782
|
-
if (raw == null) return "—";
|
|
783
|
-
return formatColor(raw, format).value;
|
|
784
|
-
}
|
|
785
582
|
function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
786
583
|
const project = useProject();
|
|
787
584
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
@@ -807,14 +604,14 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
807
604
|
]);
|
|
808
605
|
const captionText = caption ?? `${rows.length} border${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
809
606
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
810
|
-
...
|
|
607
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
811
608
|
children: /* @__PURE__ */ jsx("div", {
|
|
812
609
|
className: "sb-block__empty",
|
|
813
610
|
children: "No border tokens match this filter."
|
|
814
611
|
})
|
|
815
612
|
});
|
|
816
613
|
return /* @__PURE__ */ jsxs("div", {
|
|
817
|
-
...
|
|
614
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
818
615
|
children: [/* @__PURE__ */ jsx("div", {
|
|
819
616
|
className: "sb-block__caption",
|
|
820
617
|
children: captionText
|
|
@@ -842,7 +639,7 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
842
639
|
className: "sb-border-preview__breakdown-key",
|
|
843
640
|
children: "width"
|
|
844
641
|
}),
|
|
845
|
-
/* @__PURE__ */ jsx("span", { children: formatDimension$
|
|
642
|
+
/* @__PURE__ */ jsx("span", { children: formatDimension$1(row.value.width) }),
|
|
846
643
|
/* @__PURE__ */ jsx("span", {
|
|
847
644
|
className: "sb-border-preview__breakdown-key",
|
|
848
645
|
children: "style"
|
|
@@ -852,7 +649,7 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
852
649
|
className: "sb-border-preview__breakdown-key",
|
|
853
650
|
children: "color"
|
|
854
651
|
}),
|
|
855
|
-
/* @__PURE__ */ jsx("span", { children: formatSubColor
|
|
652
|
+
/* @__PURE__ */ jsx("span", { children: formatSubColor(row.value.color, colorFormat) })
|
|
856
653
|
]
|
|
857
654
|
})
|
|
858
655
|
]
|
|
@@ -861,10 +658,6 @@ function BorderPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
861
658
|
}
|
|
862
659
|
//#endregion
|
|
863
660
|
//#region src/ColorPalette.tsx
|
|
864
|
-
/**
|
|
865
|
-
* Count segments in the filter before the first glob (`*` / `**`).
|
|
866
|
-
* `color.*` → 2; `color.surface.*` → 3; `color` → 1; undefined → 0.
|
|
867
|
-
*/
|
|
868
661
|
function fixedPrefixLength(filter) {
|
|
869
662
|
if (!filter) return 0;
|
|
870
663
|
const segments = filter.split(".");
|
|
@@ -922,14 +715,14 @@ function ColorPalette({ filter, groupBy, caption, sortBy = "path", sortDir = "as
|
|
|
922
715
|
const totalCount = groups.reduce((acc, [, swatches]) => acc + swatches.length, 0);
|
|
923
716
|
const captionText = caption ?? `${totalCount} color${totalCount === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
924
717
|
if (totalCount === 0) return /* @__PURE__ */ jsx("div", {
|
|
925
|
-
...
|
|
718
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
926
719
|
children: /* @__PURE__ */ jsx("div", {
|
|
927
720
|
className: "sb-block__empty",
|
|
928
721
|
children: "No color tokens match this filter."
|
|
929
722
|
})
|
|
930
723
|
});
|
|
931
724
|
return /* @__PURE__ */ jsxs("div", {
|
|
932
|
-
...
|
|
725
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
933
726
|
children: [/* @__PURE__ */ jsx("div", {
|
|
934
727
|
className: "sb-block__caption",
|
|
935
728
|
children: captionText
|
|
@@ -1115,14 +908,14 @@ function ColorTable({ filter, caption, sortBy = "path", sortDir = "asc", searcha
|
|
|
1115
908
|
const matchSuffix = searchable && query.trim() !== "" ? ` · ${visibleGroups.length} matching "${query.trim()}"` : "";
|
|
1116
909
|
const captionText = caption ?? `${totalTokens} color${totalTokens === 1 ? "" : "s"} across ${groups.length} group${groups.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${matchSuffix} · ${activeTheme}`;
|
|
1117
910
|
if (groups.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1118
|
-
...
|
|
911
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1119
912
|
children: /* @__PURE__ */ jsx("div", {
|
|
1120
913
|
className: "sb-block__empty",
|
|
1121
914
|
children: "No color tokens match this filter."
|
|
1122
915
|
})
|
|
1123
916
|
});
|
|
1124
917
|
return /* @__PURE__ */ jsxs("div", {
|
|
1125
|
-
...
|
|
918
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1126
919
|
children: [searchable && /* @__PURE__ */ jsx("div", {
|
|
1127
920
|
className: "sb-color-table__search",
|
|
1128
921
|
children: /* @__PURE__ */ jsx("input", {
|
|
@@ -1404,13 +1197,6 @@ function ValueCell({ value, label, children }) {
|
|
|
1404
1197
|
]
|
|
1405
1198
|
});
|
|
1406
1199
|
}
|
|
1407
|
-
/**
|
|
1408
|
-
* Pick the value + gamut flag to display in the single Value column based
|
|
1409
|
-
* on the active color-format context. We pre-compute hex/hsl/oklch for the
|
|
1410
|
-
* expanded sub-table regardless; the extras (`rgb`, `raw`) take a fresh
|
|
1411
|
-
* `formatColor` pass. Keeps the hot path fast while staying honest about
|
|
1412
|
-
* out-of-gamut warnings per-format.
|
|
1413
|
-
*/
|
|
1414
1200
|
function pickActiveFormat(raw, colorFormat, hex, hsl, oklch) {
|
|
1415
1201
|
switch (colorFormat) {
|
|
1416
1202
|
case "hex": return {
|
|
@@ -1454,30 +1240,11 @@ function buildVariantDefs(variants) {
|
|
|
1454
1240
|
displayOrder
|
|
1455
1241
|
};
|
|
1456
1242
|
}
|
|
1457
|
-
/**
|
|
1458
|
-
* Position of a label within a group — the `base` entry always sorts first,
|
|
1459
|
-
* then declared labels in the order the caller wrote them in the `variants`
|
|
1460
|
-
* prop. Unknown labels (shouldn't happen in practice) fall to the end.
|
|
1461
|
-
*/
|
|
1462
1243
|
function orderIndex(label, defs) {
|
|
1463
1244
|
if (label === BASE_LABEL) return -1;
|
|
1464
1245
|
const idx = defs.displayOrder.indexOf(label);
|
|
1465
1246
|
return idx >= 0 ? idx : Number.POSITIVE_INFINITY;
|
|
1466
1247
|
}
|
|
1467
|
-
/**
|
|
1468
|
-
* Resolve the variant label + base path for a token, if any. The leaf
|
|
1469
|
-
* (last dot-segment) must either equal the suffix outright (dot-segment
|
|
1470
|
-
* form: `hi.disabled` matches suffix `disabled`) or end in `-<suffix>`
|
|
1471
|
-
* (hyphen-tail form: `hi-d` matches `d`). The leading hyphen is required
|
|
1472
|
-
* for the tail form so suffix `0` can't hit `neutral-900` by character.
|
|
1473
|
-
*
|
|
1474
|
-
* Returned `basePath` is what gets used as the grouping key:
|
|
1475
|
-
* - Dot-segment match → parent path (drop the last dot-segment)
|
|
1476
|
-
* - Hyphen-tail match → same dot-depth, leaf with `-<suffix>` stripped
|
|
1477
|
-
*
|
|
1478
|
-
* Entries in `matchOrder` are pre-sorted longest-first, so `h-dark` wins
|
|
1479
|
-
* over `dark` for a path ending in `-h-dark`.
|
|
1480
|
-
*/
|
|
1481
1248
|
function matchVariant(path, matchOrder) {
|
|
1482
1249
|
if (matchOrder.length === 0) return void 0;
|
|
1483
1250
|
const segments = path.split(".");
|
|
@@ -1552,7 +1319,7 @@ function Diagnostics({ caption } = {}) {
|
|
|
1552
1319
|
const summary = useMemo(() => summarize(diagnostics), [diagnostics]);
|
|
1553
1320
|
const headingText = caption ?? `Diagnostics · ${summary.text}`;
|
|
1554
1321
|
return /* @__PURE__ */ jsx("div", {
|
|
1555
|
-
...
|
|
1322
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1556
1323
|
"data-testid": "diagnostics",
|
|
1557
1324
|
children: /* @__PURE__ */ jsxs("details", {
|
|
1558
1325
|
open: summary.hasErrorsOrWarnings,
|
|
@@ -1583,8 +1350,26 @@ function Diagnostics({ caption } = {}) {
|
|
|
1583
1350
|
});
|
|
1584
1351
|
}
|
|
1585
1352
|
//#endregion
|
|
1353
|
+
//#region src/dimension-scale/dimension-px.ts
|
|
1354
|
+
/**
|
|
1355
|
+
* Convert a DTCG dimension `$value` (`{ value, unit }`) to pixels for the
|
|
1356
|
+
* purpose of deciding whether to cap the rendered size. Returns `NaN` for
|
|
1357
|
+
* units we can't reasonably approximate (ex / ch / %), which the caller
|
|
1358
|
+
* treats as "render at cssVar but don't cap".
|
|
1359
|
+
*/
|
|
1360
|
+
function toPixels(raw) {
|
|
1361
|
+
if (raw == null || typeof raw !== "object") return NaN;
|
|
1362
|
+
const v = raw;
|
|
1363
|
+
if (typeof v.value !== "number" || typeof v.unit !== "string") return NaN;
|
|
1364
|
+
switch (v.unit) {
|
|
1365
|
+
case "px": return v.value;
|
|
1366
|
+
case "rem":
|
|
1367
|
+
case "em": return v.value * 16;
|
|
1368
|
+
default: return NaN;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
//#endregion
|
|
1586
1372
|
//#region src/dimension-scale/DimensionBar.tsx
|
|
1587
|
-
const MAX_RENDER_PX$1 = 480;
|
|
1588
1373
|
const styles$1 = {
|
|
1589
1374
|
bar: {
|
|
1590
1375
|
height: 14,
|
|
@@ -1605,31 +1390,14 @@ const styles$1 = {
|
|
|
1605
1390
|
minHeight: 1
|
|
1606
1391
|
}
|
|
1607
1392
|
};
|
|
1608
|
-
|
|
1609
|
-
* Convert a DTCG dimension `$value` (`{ value, unit }`) to pixels for the
|
|
1610
|
-
* purpose of deciding whether to cap the rendered bar. Returns `NaN` for
|
|
1611
|
-
* units we can't reasonably approximate (ex / ch / %), which the caller
|
|
1612
|
-
* treats as "render at cssVar but don't cap".
|
|
1613
|
-
*/
|
|
1614
|
-
function toPixels$1(raw) {
|
|
1615
|
-
if (raw == null || typeof raw !== "object") return NaN;
|
|
1616
|
-
const v = raw;
|
|
1617
|
-
if (typeof v.value !== "number" || typeof v.unit !== "string") return NaN;
|
|
1618
|
-
switch (v.unit) {
|
|
1619
|
-
case "px": return v.value;
|
|
1620
|
-
case "rem":
|
|
1621
|
-
case "em": return v.value * 16;
|
|
1622
|
-
default: return NaN;
|
|
1623
|
-
}
|
|
1624
|
-
}
|
|
1625
|
-
function DimensionBar({ path, kind = "length" }) {
|
|
1393
|
+
function DimensionBar({ path, visual = "length" }) {
|
|
1626
1394
|
const project = useProject();
|
|
1627
1395
|
const { resolved } = project;
|
|
1628
1396
|
const cssVar = resolveCssVar(path, project);
|
|
1629
1397
|
const token = resolved[path];
|
|
1630
|
-
const pxValue = toPixels
|
|
1631
|
-
const cappedValue = Number.isFinite(pxValue) && pxValue >
|
|
1632
|
-
switch (
|
|
1398
|
+
const pxValue = toPixels(token?.$value);
|
|
1399
|
+
const cappedValue = Number.isFinite(pxValue) && pxValue > 480 ? `480px` : cssVar;
|
|
1400
|
+
switch (visual) {
|
|
1633
1401
|
case "radius": return /* @__PURE__ */ jsx("div", {
|
|
1634
1402
|
style: {
|
|
1635
1403
|
...styles$1.radiusSample,
|
|
@@ -1674,7 +1442,7 @@ function formatTokenValue(value, $type, colorFormat, listingEntry) {
|
|
|
1674
1442
|
switch ($type) {
|
|
1675
1443
|
case "color": return formatColor(value, colorFormat).value;
|
|
1676
1444
|
case "dimension":
|
|
1677
|
-
case "duration": return formatDimension
|
|
1445
|
+
case "duration": return formatDimension(value);
|
|
1678
1446
|
case "fontFamily": return formatFontFamily$1(value);
|
|
1679
1447
|
case "fontWeight":
|
|
1680
1448
|
case "lineHeight":
|
|
@@ -1688,7 +1456,7 @@ function formatTokenValue(value, $type, colorFormat, listingEntry) {
|
|
|
1688
1456
|
default: return formatUnknown(value);
|
|
1689
1457
|
}
|
|
1690
1458
|
}
|
|
1691
|
-
function formatDimension
|
|
1459
|
+
function formatDimension(v) {
|
|
1692
1460
|
if (typeof v === "string" || typeof v === "number") return String(v);
|
|
1693
1461
|
if (v && typeof v === "object") {
|
|
1694
1462
|
const d = v;
|
|
@@ -1717,7 +1485,7 @@ function formatStrokeStyle(v) {
|
|
|
1717
1485
|
if (v && typeof v === "object") {
|
|
1718
1486
|
const s = v;
|
|
1719
1487
|
const parts = ["dashed"];
|
|
1720
|
-
if (Array.isArray(s.dashArray)) parts.push(s.dashArray.map((n) => formatDimension
|
|
1488
|
+
if (Array.isArray(s.dashArray)) parts.push(s.dashArray.map((n) => formatDimension(n)).join(" "));
|
|
1721
1489
|
if (typeof s.lineCap === "string") parts.push(s.lineCap);
|
|
1722
1490
|
return parts.join(" · ");
|
|
1723
1491
|
}
|
|
@@ -1728,10 +1496,10 @@ function formatShadow(v, colorFormat) {
|
|
|
1728
1496
|
if (!layer || typeof layer !== "object") return formatUnknown(layer);
|
|
1729
1497
|
const s = layer;
|
|
1730
1498
|
const pieces = [
|
|
1731
|
-
formatDimension
|
|
1732
|
-
formatDimension
|
|
1733
|
-
formatDimension
|
|
1734
|
-
formatDimension
|
|
1499
|
+
formatDimension(s.offsetX),
|
|
1500
|
+
formatDimension(s.offsetY),
|
|
1501
|
+
formatDimension(s.blur),
|
|
1502
|
+
formatDimension(s.spread),
|
|
1735
1503
|
formatColor(s.color, colorFormat).value
|
|
1736
1504
|
].filter((p) => p !== "");
|
|
1737
1505
|
if (s.inset) pieces.push("inset");
|
|
@@ -1742,7 +1510,7 @@ function formatBorder(v, colorFormat) {
|
|
|
1742
1510
|
if (!v || typeof v !== "object") return formatUnknown(v);
|
|
1743
1511
|
const b = v;
|
|
1744
1512
|
return [
|
|
1745
|
-
formatDimension
|
|
1513
|
+
formatDimension(b.width),
|
|
1746
1514
|
formatPrimitive$1(b.style),
|
|
1747
1515
|
formatColor(b.color, colorFormat).value
|
|
1748
1516
|
].filter((p) => p !== "").join(" ");
|
|
@@ -1758,19 +1526,7 @@ function formatUnknown(v) {
|
|
|
1758
1526
|
}
|
|
1759
1527
|
//#endregion
|
|
1760
1528
|
//#region src/DimensionScale.tsx
|
|
1761
|
-
|
|
1762
|
-
function toPixels(raw) {
|
|
1763
|
-
if (raw == null || typeof raw !== "object") return NaN;
|
|
1764
|
-
const v = raw;
|
|
1765
|
-
if (typeof v.value !== "number" || typeof v.unit !== "string") return NaN;
|
|
1766
|
-
switch (v.unit) {
|
|
1767
|
-
case "px": return v.value;
|
|
1768
|
-
case "rem":
|
|
1769
|
-
case "em": return v.value * 16;
|
|
1770
|
-
default: return NaN;
|
|
1771
|
-
}
|
|
1772
|
-
}
|
|
1773
|
-
function DimensionScale({ filter, kind = "length", caption, sortBy = "value", sortDir = "asc" }) {
|
|
1529
|
+
function DimensionScale({ filter, visual = "length", caption, sortBy = "value", sortDir = "asc" }) {
|
|
1774
1530
|
const project = useProject();
|
|
1775
1531
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
1776
1532
|
const rows = useMemo(() => {
|
|
@@ -1787,7 +1543,7 @@ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", so
|
|
|
1787
1543
|
cssVar: resolveCssVar(path, project),
|
|
1788
1544
|
displayValue: formatTokenValue(token.$value, token.$type, "raw", project.listing[path]),
|
|
1789
1545
|
pxValue,
|
|
1790
|
-
capped: Number.isFinite(pxValue) && pxValue >
|
|
1546
|
+
capped: Number.isFinite(pxValue) && pxValue > 480
|
|
1791
1547
|
};
|
|
1792
1548
|
});
|
|
1793
1549
|
}, [
|
|
@@ -1799,14 +1555,14 @@ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", so
|
|
|
1799
1555
|
]);
|
|
1800
1556
|
const captionText = caption ?? `${rows.length} dimension${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
1801
1557
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1802
|
-
...
|
|
1558
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1803
1559
|
children: /* @__PURE__ */ jsx("div", {
|
|
1804
1560
|
className: "sb-block__empty",
|
|
1805
1561
|
children: "No dimension tokens match this filter."
|
|
1806
1562
|
})
|
|
1807
1563
|
});
|
|
1808
1564
|
return /* @__PURE__ */ jsxs("div", {
|
|
1809
|
-
...
|
|
1565
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1810
1566
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1811
1567
|
className: "sb-block__caption",
|
|
1812
1568
|
children: captionText
|
|
@@ -1827,12 +1583,12 @@ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", so
|
|
|
1827
1583
|
className: "sb-dimension-scale__visual-cell",
|
|
1828
1584
|
children: [/* @__PURE__ */ jsx(DimensionBar, {
|
|
1829
1585
|
path: row.path,
|
|
1830
|
-
|
|
1586
|
+
visual
|
|
1831
1587
|
}), row.capped && /* @__PURE__ */ jsxs("span", {
|
|
1832
1588
|
className: "sb-dimension-scale__cap",
|
|
1833
1589
|
children: [
|
|
1834
1590
|
"capped at ",
|
|
1835
|
-
|
|
1591
|
+
480,
|
|
1836
1592
|
"px"
|
|
1837
1593
|
]
|
|
1838
1594
|
})]
|
|
@@ -1846,13 +1602,13 @@ function DimensionScale({ filter, kind = "length", caption, sortBy = "value", so
|
|
|
1846
1602
|
});
|
|
1847
1603
|
}
|
|
1848
1604
|
//#endregion
|
|
1849
|
-
//#region src/
|
|
1605
|
+
//#region src/FontFamilyPreview.tsx
|
|
1850
1606
|
function stackString(raw) {
|
|
1851
1607
|
if (typeof raw === "string") return raw;
|
|
1852
1608
|
if (Array.isArray(raw)) return raw.map(String).join(", ");
|
|
1853
1609
|
return "";
|
|
1854
1610
|
}
|
|
1855
|
-
function
|
|
1611
|
+
function FontFamilyPreview({ filter, sample = "The quick brown fox jumps over the lazy dog.", caption, sortBy = "path", sortDir = "asc" }) {
|
|
1856
1612
|
const project = useProject();
|
|
1857
1613
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
1858
1614
|
const rows = useMemo(() => {
|
|
@@ -1876,14 +1632,14 @@ function FontFamilySample({ filter, sample = "The quick brown fox jumps over the
|
|
|
1876
1632
|
]);
|
|
1877
1633
|
const captionText = caption ?? `${rows.length} fontFamily token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontFamily" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
1878
1634
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1879
|
-
...
|
|
1635
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1880
1636
|
children: /* @__PURE__ */ jsx("div", {
|
|
1881
1637
|
className: "sb-block__empty",
|
|
1882
1638
|
children: "No fontFamily tokens match this filter."
|
|
1883
1639
|
})
|
|
1884
1640
|
});
|
|
1885
1641
|
return /* @__PURE__ */ jsxs("div", {
|
|
1886
|
-
...
|
|
1642
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1887
1643
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1888
1644
|
className: "sb-block__caption",
|
|
1889
1645
|
children: captionText
|
|
@@ -1963,14 +1719,14 @@ function FontWeightScale({ filter, sample = "Aa", caption, sortBy = "value", sor
|
|
|
1963
1719
|
]);
|
|
1964
1720
|
const captionText = caption ?? `${rows.length} fontWeight token${rows.length === 1 ? "" : "s"}${filter && filter !== "fontWeight" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
1965
1721
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
1966
|
-
...
|
|
1722
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1967
1723
|
children: /* @__PURE__ */ jsx("div", {
|
|
1968
1724
|
className: "sb-block__empty",
|
|
1969
1725
|
children: "No fontWeight tokens match this filter."
|
|
1970
1726
|
})
|
|
1971
1727
|
});
|
|
1972
1728
|
return /* @__PURE__ */ jsxs("div", {
|
|
1973
|
-
...
|
|
1729
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
1974
1730
|
children: [/* @__PURE__ */ jsx("div", {
|
|
1975
1731
|
className: "sb-block__caption",
|
|
1976
1732
|
children: captionText
|
|
@@ -2006,14 +1762,10 @@ function asStops(raw) {
|
|
|
2006
1762
|
if (!Array.isArray(raw)) return [];
|
|
2007
1763
|
return raw;
|
|
2008
1764
|
}
|
|
2009
|
-
const pct = (n) => `${(n * 100).toFixed(3)}%`;
|
|
2010
1765
|
function stopCssColor(stop) {
|
|
2011
|
-
const color = stop.color;
|
|
2012
|
-
if (!color
|
|
2013
|
-
|
|
2014
|
-
if (r === void 0 || g === void 0 || b === void 0) return "transparent";
|
|
2015
|
-
const alpha = color.alpha ?? 1;
|
|
2016
|
-
return alpha === 1 ? `rgb(${pct(r)} ${pct(g)} ${pct(b)})` : `rgb(${pct(r)} ${pct(g)} ${pct(b)} / ${alpha})`;
|
|
1766
|
+
const color = parseColor(stop.color);
|
|
1767
|
+
if (!color) return "transparent";
|
|
1768
|
+
return color.toString();
|
|
2017
1769
|
}
|
|
2018
1770
|
function stopKey(path, stop, fallback) {
|
|
2019
1771
|
return `${path}|${stop.position ?? fallback}|${stopCssColor(stop)}`;
|
|
@@ -2047,14 +1799,14 @@ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" })
|
|
|
2047
1799
|
]);
|
|
2048
1800
|
const captionText = caption ?? `${rows.length} gradient${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2049
1801
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2050
|
-
...
|
|
1802
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2051
1803
|
children: /* @__PURE__ */ jsx("div", {
|
|
2052
1804
|
className: "sb-block__empty",
|
|
2053
1805
|
children: "No gradient tokens match this filter."
|
|
2054
1806
|
})
|
|
2055
1807
|
});
|
|
2056
1808
|
return /* @__PURE__ */ jsxs("div", {
|
|
2057
|
-
...
|
|
1809
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2058
1810
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2059
1811
|
className: "sb-block__caption",
|
|
2060
1812
|
children: captionText
|
|
@@ -2104,14 +1856,6 @@ function GradientPalette({ filter, caption, sortBy = "path", sortDir = "asc" })
|
|
|
2104
1856
|
}
|
|
2105
1857
|
//#endregion
|
|
2106
1858
|
//#region src/internal/prefers-reduced-motion.ts
|
|
2107
|
-
/**
|
|
2108
|
-
* True when rendering inside Chromatic's snapshot runner. Chromatic's
|
|
2109
|
-
* browser ships a recognisable user-agent string; checked here so
|
|
2110
|
-
* motion-looping components can fall back to their static state for
|
|
2111
|
-
* deterministic snapshots. Per-component detection rather than the
|
|
2112
|
-
* global `chromatic.prefersReducedMotion: true` parameter — that
|
|
2113
|
-
* parameter is incompatible with Chromatic's verification parser.
|
|
2114
|
-
*/
|
|
2115
1859
|
function isChromatic() {
|
|
2116
1860
|
if (typeof navigator === "undefined") return false;
|
|
2117
1861
|
return navigator.userAgent.includes("Chromatic");
|
|
@@ -2332,14 +2076,14 @@ function MotionPreview({ filter, caption }) {
|
|
|
2332
2076
|
]);
|
|
2333
2077
|
const captionText = caption ?? `${rows.length} motion token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2334
2078
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2335
|
-
...
|
|
2079
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2336
2080
|
children: /* @__PURE__ */ jsx("div", {
|
|
2337
2081
|
className: "sb-block__empty",
|
|
2338
2082
|
children: "No motion tokens match this filter."
|
|
2339
2083
|
})
|
|
2340
2084
|
});
|
|
2341
2085
|
return /* @__PURE__ */ jsxs("div", {
|
|
2342
|
-
...
|
|
2086
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2343
2087
|
children: [
|
|
2344
2088
|
/* @__PURE__ */ jsx("div", {
|
|
2345
2089
|
className: "sb-block__caption",
|
|
@@ -2442,7 +2186,7 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2442
2186
|
]);
|
|
2443
2187
|
const captionText = caption ?? `${rows.length} opacity token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2444
2188
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2445
|
-
...
|
|
2189
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2446
2190
|
children: /* @__PURE__ */ jsx("div", {
|
|
2447
2191
|
className: "sb-block__empty",
|
|
2448
2192
|
children: "No opacity tokens match this filter."
|
|
@@ -2450,7 +2194,7 @@ function OpacityScale({ filter, type = "number", sampleColor = "color.accent.bg"
|
|
|
2450
2194
|
});
|
|
2451
2195
|
const sampleColorVar = resolveCssVar(sampleColor, project);
|
|
2452
2196
|
return /* @__PURE__ */ jsxs("div", {
|
|
2453
|
-
...
|
|
2197
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2454
2198
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2455
2199
|
className: "sb-block__caption",
|
|
2456
2200
|
children: captionText
|
|
@@ -2534,27 +2278,13 @@ function ShadowSample({ path }) {
|
|
|
2534
2278
|
}
|
|
2535
2279
|
//#endregion
|
|
2536
2280
|
//#region src/ShadowPreview.tsx
|
|
2537
|
-
function formatDimension(raw) {
|
|
2538
|
-
if (raw == null) return "—";
|
|
2539
|
-
if (typeof raw === "number") return String(raw);
|
|
2540
|
-
if (typeof raw === "string") return raw;
|
|
2541
|
-
if (typeof raw === "object") {
|
|
2542
|
-
const v = raw;
|
|
2543
|
-
if (typeof v.value === "number" && typeof v.unit === "string") return `${v.value}${v.unit}`;
|
|
2544
|
-
}
|
|
2545
|
-
return JSON.stringify(raw);
|
|
2546
|
-
}
|
|
2547
|
-
function formatSubColor(raw, format) {
|
|
2548
|
-
if (raw == null) return "—";
|
|
2549
|
-
return formatColor(raw, format).value;
|
|
2550
|
-
}
|
|
2551
2281
|
function asLayers(raw) {
|
|
2552
2282
|
if (Array.isArray(raw)) return raw;
|
|
2553
2283
|
if (raw && typeof raw === "object") return [raw];
|
|
2554
2284
|
return [];
|
|
2555
2285
|
}
|
|
2556
2286
|
function layerKey(path, layer, fallback) {
|
|
2557
|
-
return `${path}|${`${formatDimension(layer.offsetX)},${formatDimension(layer.offsetY)}`}|${formatDimension(layer.blur)}|${formatDimension(layer.spread)}|${fallback}`;
|
|
2287
|
+
return `${path}|${`${formatDimension$1(layer.offsetX)},${formatDimension$1(layer.offsetY)}`}|${formatDimension$1(layer.blur)}|${formatDimension$1(layer.spread)}|${fallback}`;
|
|
2558
2288
|
}
|
|
2559
2289
|
function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
2560
2290
|
const project = useProject();
|
|
@@ -2581,14 +2311,14 @@ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
2581
2311
|
]);
|
|
2582
2312
|
const captionText = caption ?? `${rows.length} shadow${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2583
2313
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2584
|
-
...
|
|
2314
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2585
2315
|
children: /* @__PURE__ */ jsx("div", {
|
|
2586
2316
|
className: "sb-block__empty",
|
|
2587
2317
|
children: "No shadow tokens match this filter."
|
|
2588
2318
|
})
|
|
2589
2319
|
});
|
|
2590
2320
|
return /* @__PURE__ */ jsxs("div", {
|
|
2591
|
-
...
|
|
2321
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2592
2322
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2593
2323
|
className: "sb-block__caption",
|
|
2594
2324
|
children: captionText
|
|
@@ -2625,9 +2355,9 @@ function ShadowPreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
|
2625
2355
|
function renderLayer(layer, format) {
|
|
2626
2356
|
if (!layer) return [];
|
|
2627
2357
|
const entries = [
|
|
2628
|
-
["offset", `${formatDimension(layer.offsetX)} ${formatDimension(layer.offsetY)}`],
|
|
2629
|
-
["blur", formatDimension(layer.blur)],
|
|
2630
|
-
["spread", formatDimension(layer.spread)],
|
|
2358
|
+
["offset", `${formatDimension$1(layer.offsetX)} ${formatDimension$1(layer.offsetY)}`],
|
|
2359
|
+
["blur", formatDimension$1(layer.blur)],
|
|
2360
|
+
["spread", formatDimension$1(layer.spread)],
|
|
2631
2361
|
["color", formatSubColor(layer.color, format)]
|
|
2632
2362
|
];
|
|
2633
2363
|
if (layer.inset) entries.push(["inset", String(layer.inset)]);
|
|
@@ -2654,7 +2384,7 @@ function Layer({ layer, index, total, colorFormat }) {
|
|
|
2654
2384
|
});
|
|
2655
2385
|
}
|
|
2656
2386
|
//#endregion
|
|
2657
|
-
//#region src/
|
|
2387
|
+
//#region src/StrokeStylePreview.tsx
|
|
2658
2388
|
const STRING_STYLES = new Set([
|
|
2659
2389
|
"solid",
|
|
2660
2390
|
"dashed",
|
|
@@ -2669,7 +2399,7 @@ function extractCssStyle(value) {
|
|
|
2669
2399
|
if (typeof value === "string" && STRING_STYLES.has(value)) return value;
|
|
2670
2400
|
return null;
|
|
2671
2401
|
}
|
|
2672
|
-
function
|
|
2402
|
+
function StrokeStylePreview({ filter, caption, sortBy = "path", sortDir = "asc" }) {
|
|
2673
2403
|
const project = useProject();
|
|
2674
2404
|
const { resolved, activeTheme, activeAxes, cssVarPrefix } = project;
|
|
2675
2405
|
const rows = useMemo(() => {
|
|
@@ -2694,14 +2424,14 @@ function StrokeStyleSample({ filter, caption, sortBy = "path", sortDir = "asc" }
|
|
|
2694
2424
|
]);
|
|
2695
2425
|
const captionText = caption ?? `${rows.length} strokeStyle token${rows.length === 1 ? "" : "s"}${filter && filter !== "strokeStyle" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
2696
2426
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
2697
|
-
...
|
|
2427
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2698
2428
|
children: /* @__PURE__ */ jsx("div", {
|
|
2699
2429
|
className: "sb-block__empty",
|
|
2700
2430
|
children: "No strokeStyle tokens match this filter."
|
|
2701
2431
|
})
|
|
2702
2432
|
});
|
|
2703
2433
|
return /* @__PURE__ */ jsxs("div", {
|
|
2704
|
-
...
|
|
2434
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
2705
2435
|
children: [/* @__PURE__ */ jsx("div", {
|
|
2706
2436
|
className: "sb-block__caption",
|
|
2707
2437
|
children: captionText
|
|
@@ -2833,15 +2563,13 @@ function AliasedByRow({ node, depth }) {
|
|
|
2833
2563
|
function buildAliasedByTree(rootPath, resolved) {
|
|
2834
2564
|
const direct = resolved[rootPath]?.aliasedBy;
|
|
2835
2565
|
if (!direct || direct.length === 0) return [];
|
|
2836
|
-
|
|
2837
|
-
return sortPaths(direct).map((p) => walk(p, resolved, visited, 1));
|
|
2566
|
+
return sortPaths(direct).map((p) => walk(p, resolved, new Set([rootPath]), 1));
|
|
2838
2567
|
}
|
|
2839
|
-
function walk(path, resolved,
|
|
2840
|
-
if (
|
|
2568
|
+
function walk(path, resolved, ancestors, depth) {
|
|
2569
|
+
if (ancestors.has(path)) return {
|
|
2841
2570
|
path,
|
|
2842
2571
|
children: []
|
|
2843
2572
|
};
|
|
2844
|
-
visited.add(path);
|
|
2845
2573
|
const parents = resolved[path]?.aliasedBy;
|
|
2846
2574
|
if (!parents || parents.length === 0) return {
|
|
2847
2575
|
path,
|
|
@@ -2852,9 +2580,10 @@ function walk(path, resolved, visited, depth) {
|
|
|
2852
2580
|
children: [],
|
|
2853
2581
|
truncated: true
|
|
2854
2582
|
};
|
|
2583
|
+
const childAncestors = new Set(ancestors).add(path);
|
|
2855
2584
|
return {
|
|
2856
2585
|
path,
|
|
2857
|
-
children: sortPaths(parents).map((p) => walk(p, resolved,
|
|
2586
|
+
children: sortPaths(parents).map((p) => walk(p, resolved, childAncestors, depth + 1))
|
|
2858
2587
|
};
|
|
2859
2588
|
}
|
|
2860
2589
|
function sortPaths(paths) {
|
|
@@ -3252,12 +2981,6 @@ function formatDimensionValue(v) {
|
|
|
3252
2981
|
}
|
|
3253
2982
|
return JSON.stringify(v);
|
|
3254
2983
|
}
|
|
3255
|
-
/**
|
|
3256
|
-
* Route sub-value colors through `formatColor` so they honor the active
|
|
3257
|
-
* color-format dropdown, just like the standalone `<ColorPalette />` and
|
|
3258
|
-
* `<TokenDetail />` top-line do. Returns `null` for a missing field so
|
|
3259
|
-
* the key/value row drops out entirely.
|
|
3260
|
-
*/
|
|
3261
2984
|
function formatColorSubValue(v, format) {
|
|
3262
2985
|
if (v == null) return null;
|
|
3263
2986
|
return formatColor(v, format).value;
|
|
@@ -3270,11 +2993,6 @@ function pickArrayAliases(v) {
|
|
|
3270
2993
|
if (!Array.isArray(v)) return void 0;
|
|
3271
2994
|
return v;
|
|
3272
2995
|
}
|
|
3273
|
-
/**
|
|
3274
|
-
* Walk the alias chain starting from an immediate sub-value alias target.
|
|
3275
|
-
* `aliasTarget` is the path the sub-value directly references; the target
|
|
3276
|
-
* token's own `aliasChain` continues the walk to the primitive.
|
|
3277
|
-
*/
|
|
3278
2996
|
function subValueChain(aliasTarget, resolved) {
|
|
3279
2997
|
if (!aliasTarget) return void 0;
|
|
3280
2998
|
const tail = (resolved?.[aliasTarget])?.aliasChain;
|
|
@@ -3294,6 +3012,35 @@ function gradientStopKey(stop, fallback) {
|
|
|
3294
3012
|
return `stop|${stop.position ?? fallback}|${JSON.stringify(stop.color)}`;
|
|
3295
3013
|
}
|
|
3296
3014
|
//#endregion
|
|
3015
|
+
//#region src/token-detail/transition-duration.ts
|
|
3016
|
+
/**
|
|
3017
|
+
* Numeric duration (ms) the motion preview should animate over for a given
|
|
3018
|
+
* token type, read from its resolved `$value`. Lets the sample's toggle loop
|
|
3019
|
+
* match the token's real duration instead of a fixed cadence — a long token
|
|
3020
|
+
* (say 2s) otherwise reverses mid-move under the old hardcoded interval.
|
|
3021
|
+
* Returns `undefined` for types that carry no duration, so the caller can
|
|
3022
|
+
* fall back to its default.
|
|
3023
|
+
*/
|
|
3024
|
+
function transitionDurationMs(type, rawValue) {
|
|
3025
|
+
if (type === "cubicBezier") return 800;
|
|
3026
|
+
if (type === "duration") return parseDurationMs(rawValue);
|
|
3027
|
+
if (type === "transition" && rawValue !== null && typeof rawValue === "object") return parseDurationMs(rawValue.duration);
|
|
3028
|
+
}
|
|
3029
|
+
function parseDurationMs(v) {
|
|
3030
|
+
if (typeof v === "number") return v;
|
|
3031
|
+
if (typeof v === "string") {
|
|
3032
|
+
const m = /^([\d.]+)(ms|s)?$/.exec(v.trim());
|
|
3033
|
+
if (!m?.[1]) return void 0;
|
|
3034
|
+
const n = Number(m[1]);
|
|
3035
|
+
if (Number.isNaN(n)) return void 0;
|
|
3036
|
+
return m[2] === "s" ? n * 1e3 : n;
|
|
3037
|
+
}
|
|
3038
|
+
if (v !== null && typeof v === "object") {
|
|
3039
|
+
const d = v;
|
|
3040
|
+
if (typeof d.value === "number") return d.unit === "s" ? d.value * 1e3 : d.value;
|
|
3041
|
+
}
|
|
3042
|
+
}
|
|
3043
|
+
//#endregion
|
|
3297
3044
|
//#region src/token-detail/CompositePreview.tsx
|
|
3298
3045
|
const PANGRAM = "Sphinx of black quartz, judge my vow.";
|
|
3299
3046
|
const STROKE_STYLE_STRINGS = new Set([
|
|
@@ -3340,7 +3087,10 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
|
|
|
3340
3087
|
style: { border: cssVar },
|
|
3341
3088
|
"aria-hidden": true
|
|
3342
3089
|
});
|
|
3343
|
-
if (type === "transition") return /* @__PURE__ */ jsx(TransitionSample, {
|
|
3090
|
+
if (type === "transition") return /* @__PURE__ */ jsx(TransitionSample, {
|
|
3091
|
+
transition: cssVar,
|
|
3092
|
+
durationMs: transitionDurationMs(type, rawValue)
|
|
3093
|
+
});
|
|
3344
3094
|
if (type === "dimension") return /* @__PURE__ */ jsx("div", {
|
|
3345
3095
|
className: "sb-token-detail__dimension-track",
|
|
3346
3096
|
children: /* @__PURE__ */ jsx("div", {
|
|
@@ -3349,7 +3099,10 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
|
|
|
3349
3099
|
"aria-hidden": true
|
|
3350
3100
|
})
|
|
3351
3101
|
});
|
|
3352
|
-
if (type === "duration") return /* @__PURE__ */ jsx(TransitionSample, {
|
|
3102
|
+
if (type === "duration") return /* @__PURE__ */ jsx(TransitionSample, {
|
|
3103
|
+
transition: `left ${cssVar} ease`,
|
|
3104
|
+
durationMs: transitionDurationMs(type, rawValue)
|
|
3105
|
+
});
|
|
3353
3106
|
if (type === "fontFamily") return /* @__PURE__ */ jsx("div", {
|
|
3354
3107
|
className: "sb-token-detail__font-family-sample",
|
|
3355
3108
|
style: { fontFamily: cssVar },
|
|
@@ -3360,13 +3113,16 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
|
|
|
3360
3113
|
style: { fontWeight: cssVarAsNumber(cssVar) },
|
|
3361
3114
|
children: "Aa"
|
|
3362
3115
|
});
|
|
3363
|
-
if (type === "cubicBezier") return /* @__PURE__ */ jsx(TransitionSample, {
|
|
3116
|
+
if (type === "cubicBezier") return /* @__PURE__ */ jsx(TransitionSample, {
|
|
3117
|
+
transition: `left 800ms ${cssVar}`,
|
|
3118
|
+
durationMs: transitionDurationMs(type, rawValue)
|
|
3119
|
+
});
|
|
3364
3120
|
if (type === "gradient") return /* @__PURE__ */ jsx("div", {
|
|
3365
3121
|
className: "sb-token-detail__gradient-sample",
|
|
3366
3122
|
style: { background: `linear-gradient(to right, ${cssVar})` },
|
|
3367
3123
|
"aria-hidden": true
|
|
3368
3124
|
});
|
|
3369
|
-
if (type === "strokeStyle") return /* @__PURE__ */ jsx(StrokeStylePreview, { value: rawValue });
|
|
3125
|
+
if (type === "strokeStyle") return /* @__PURE__ */ jsx(StrokeStylePreview$1, { value: rawValue });
|
|
3370
3126
|
if (type === "color") return /* @__PURE__ */ jsxs("div", {
|
|
3371
3127
|
className: "sb-token-detail__color-swatch-row",
|
|
3372
3128
|
"aria-hidden": true,
|
|
@@ -3380,7 +3136,7 @@ function CompositePreviewContent({ type, cssVar, rawValue }) {
|
|
|
3380
3136
|
});
|
|
3381
3137
|
return null;
|
|
3382
3138
|
}
|
|
3383
|
-
function StrokeStylePreview({ value }) {
|
|
3139
|
+
function StrokeStylePreview$1({ value }) {
|
|
3384
3140
|
if (typeof value === "string" && STROKE_STYLE_STRINGS.has(value)) return /* @__PURE__ */ jsx("div", {
|
|
3385
3141
|
className: "sb-token-detail__stroke-style-line",
|
|
3386
3142
|
style: { borderTopStyle: value },
|
|
@@ -3431,20 +3187,23 @@ function asDashLengths(raw) {
|
|
|
3431
3187
|
}
|
|
3432
3188
|
return out;
|
|
3433
3189
|
}
|
|
3434
|
-
|
|
3190
|
+
const DEFAULT_LOOP_MS = 1200;
|
|
3191
|
+
const MOTION_HOLD_MS = 400;
|
|
3192
|
+
function TransitionSample({ transition, durationMs }) {
|
|
3435
3193
|
const reduced = usePrefersReducedMotion();
|
|
3436
3194
|
const [phase, setPhase] = useState(0);
|
|
3437
3195
|
useEffect(() => {
|
|
3438
3196
|
if (reduced) return;
|
|
3197
|
+
const loopMs = durationMs === void 0 ? DEFAULT_LOOP_MS : durationMs + MOTION_HOLD_MS;
|
|
3439
3198
|
const id = requestAnimationFrame(() => setPhase(1));
|
|
3440
3199
|
const loop = window.setInterval(() => {
|
|
3441
3200
|
setPhase((p) => p === 0 ? 1 : 0);
|
|
3442
|
-
},
|
|
3201
|
+
}, loopMs);
|
|
3443
3202
|
return () => {
|
|
3444
3203
|
cancelAnimationFrame(id);
|
|
3445
3204
|
window.clearInterval(loop);
|
|
3446
3205
|
};
|
|
3447
|
-
}, [reduced]);
|
|
3206
|
+
}, [reduced, durationMs]);
|
|
3448
3207
|
if (reduced) return /* @__PURE__ */ jsx("div", {
|
|
3449
3208
|
className: "sb-token-detail__reduced-motion",
|
|
3450
3209
|
children: "Animation suppressed by `prefers-reduced-motion: reduce`."
|
|
@@ -3579,11 +3338,6 @@ function TokenHeader({ path, heading }) {
|
|
|
3579
3338
|
}
|
|
3580
3339
|
//#endregion
|
|
3581
3340
|
//#region src/token-detail/TokenUsageSnippet.tsx
|
|
3582
|
-
/**
|
|
3583
|
-
* DTCG `$type`s with a single canonical CSS property. Types whose value is
|
|
3584
|
-
* applied across many properties (`dimension`, `number`, `strokeStyle`,
|
|
3585
|
-
* `typography`) are intentionally absent — they fall back to a commented hint.
|
|
3586
|
-
*/
|
|
3587
3341
|
const CSS_PROPERTY_BY_TYPE = {
|
|
3588
3342
|
color: "color",
|
|
3589
3343
|
shadow: "box-shadow",
|
|
@@ -3624,10 +3378,11 @@ function TokenUsageSnippet({ path }) {
|
|
|
3624
3378
|
function TokenDetail({ path, heading }) {
|
|
3625
3379
|
const { token, cssVar, activeTheme, activeAxes, cssVarPrefix } = useTokenDetailData(path);
|
|
3626
3380
|
const colorFormat = useColorFormat();
|
|
3627
|
-
const
|
|
3381
|
+
const { listing } = useProject();
|
|
3382
|
+
const wrapperAttrs = blockWrapperAttrs(cssVarPrefix, activeAxes);
|
|
3628
3383
|
if (!token) return /* @__PURE__ */ jsx("div", {
|
|
3629
|
-
...
|
|
3630
|
-
className: cx(
|
|
3384
|
+
...wrapperAttrs,
|
|
3385
|
+
className: cx(wrapperAttrs["className"], "sb-token-detail"),
|
|
3631
3386
|
children: /* @__PURE__ */ jsxs("div", {
|
|
3632
3387
|
className: "sb-token-detail__missing",
|
|
3633
3388
|
children: [
|
|
@@ -3639,14 +3394,13 @@ function TokenDetail({ path, heading }) {
|
|
|
3639
3394
|
]
|
|
3640
3395
|
})
|
|
3641
3396
|
});
|
|
3642
|
-
const { listing } = useProject();
|
|
3643
3397
|
const isColor = token.$type === "color";
|
|
3644
3398
|
const gamut = isColor ? formatColor(token.$value, colorFormat) : null;
|
|
3645
3399
|
const value = formatTokenValue(token.$value, token.$type, colorFormat, listing[path]);
|
|
3646
3400
|
const outOfGamut = gamut?.outOfGamut ?? false;
|
|
3647
3401
|
return /* @__PURE__ */ jsxs("div", {
|
|
3648
|
-
...
|
|
3649
|
-
className: cx(
|
|
3402
|
+
...wrapperAttrs,
|
|
3403
|
+
className: cx(wrapperAttrs["className"], "sb-token-detail"),
|
|
3650
3404
|
children: [
|
|
3651
3405
|
/* @__PURE__ */ jsx(TokenHeader, {
|
|
3652
3406
|
path,
|
|
@@ -3689,12 +3443,6 @@ function TokenDetail({ path, heading }) {
|
|
|
3689
3443
|
}
|
|
3690
3444
|
//#endregion
|
|
3691
3445
|
//#region src/internal/DetailOverlay.tsx
|
|
3692
|
-
/**
|
|
3693
|
-
* Selector for elements the trap considers focus stops. Mirrors the
|
|
3694
|
-
* "tabbable" set most focus-trap libraries use; the `:not(...)` clauses
|
|
3695
|
-
* skip the panel wrapper itself (we focus it manually on mount via its
|
|
3696
|
-
* own ref) and any explicitly-detabbed descendants.
|
|
3697
|
-
*/
|
|
3698
3446
|
const FOCUSABLE_SELECTOR = [
|
|
3699
3447
|
"a[href]",
|
|
3700
3448
|
"button:not([disabled])",
|
|
@@ -3734,11 +3482,6 @@ function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
|
|
|
3734
3482
|
window.addEventListener("keydown", onKey);
|
|
3735
3483
|
return () => window.removeEventListener("keydown", onKey);
|
|
3736
3484
|
}, [onClose]);
|
|
3737
|
-
/**
|
|
3738
|
-
* Wrap Tab inside the panel: from the last focusable, jump to the first;
|
|
3739
|
-
* from the first (or from the panel itself), Shift+Tab jumps to the last.
|
|
3740
|
-
* Defers to the browser otherwise.
|
|
3741
|
-
*/
|
|
3742
3485
|
const onPanelKeyDown = (e) => {
|
|
3743
3486
|
if (e.key !== "Tab") return;
|
|
3744
3487
|
const panel = panelRef.current;
|
|
@@ -3787,12 +3530,6 @@ function DetailOverlay({ path, onClose, testId = "swatchbook-overlay" }) {
|
|
|
3787
3530
|
})
|
|
3788
3531
|
});
|
|
3789
3532
|
}
|
|
3790
|
-
/**
|
|
3791
|
-
* Walk up from `node` to the direct child of `<body>` that contains it.
|
|
3792
|
-
* Returns `null` when the node isn't attached to the document (mid-mount,
|
|
3793
|
-
* post-unmount). Used to identify which top-level branch to *not* mark
|
|
3794
|
-
* inert when the overlay opens.
|
|
3795
|
-
*/
|
|
3796
3533
|
function findBodyChildContaining(node) {
|
|
3797
3534
|
let cursor = node;
|
|
3798
3535
|
while (cursor && cursor.parentElement !== document.body) cursor = cursor.parentElement;
|
|
@@ -3886,11 +3623,6 @@ function flattenVisible(nodes, expanded, parentPath, out) {
|
|
|
3886
3623
|
if (node.kind === "group" && expanded.has(node.path)) flattenVisible(node.children, expanded, node.path, out);
|
|
3887
3624
|
}
|
|
3888
3625
|
}
|
|
3889
|
-
/**
|
|
3890
|
-
* Return a pruned copy of the tree keeping only leaves whose path is in
|
|
3891
|
-
* `matches`, plus the groups on the way to them. Every surviving group's
|
|
3892
|
-
* path is added to `expandOut` so callers can force those groups open.
|
|
3893
|
-
*/
|
|
3894
3626
|
function pruneTreeForMatches(nodes, matches, expandOut) {
|
|
3895
3627
|
const out = [];
|
|
3896
3628
|
for (const node of nodes) if (node.kind === "leaf") {
|
|
@@ -4092,11 +3824,11 @@ function TokenNavigator({ root, type, initiallyExpanded = 1, searchable = true,
|
|
|
4092
3824
|
return n;
|
|
4093
3825
|
}, [visibleTree, searchExpanded]);
|
|
4094
3826
|
if (tree.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4095
|
-
...
|
|
3827
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4096
3828
|
children: /* @__PURE__ */ jsx(EmptyState, { children: root ? `No tokens under "${root}"${typeFilter ? ` matching ${typeLabel.slice(3)}` : ""}.` : typeFilter ? `No tokens matching ${typeLabel.slice(3)} in the active theme.` : "No tokens in the active theme." })
|
|
4097
3829
|
});
|
|
4098
3830
|
return /* @__PURE__ */ jsxs("div", {
|
|
4099
|
-
...
|
|
3831
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4100
3832
|
children: [
|
|
4101
3833
|
searchable && /* @__PURE__ */ jsx("div", {
|
|
4102
3834
|
className: "sb-token-navigator__search",
|
|
@@ -4288,7 +4020,7 @@ const LeafPreview = memo(function LeafPreview({ path, token }) {
|
|
|
4288
4020
|
className: "sb-token-navigator__preview-dimension",
|
|
4289
4021
|
children: /* @__PURE__ */ jsx(DimensionBar, {
|
|
4290
4022
|
path,
|
|
4291
|
-
|
|
4023
|
+
visual: "length"
|
|
4292
4024
|
})
|
|
4293
4025
|
})]
|
|
4294
4026
|
});
|
|
@@ -4378,14 +4110,14 @@ function TokenTable({ filter, type, caption, sortBy = "path", sortDir = "asc", s
|
|
|
4378
4110
|
const matchSuffix = searchable && query.trim() !== "" ? ` · ${visibleRows.length} matching "${query.trim()}"` : "";
|
|
4379
4111
|
const captionText = caption ?? `${rows.length} token${rows.length === 1 ? "" : "s"}${filter ? ` matching \`${filter}\`` : ""}${type ? ` · $type=${type}` : ""}${matchSuffix} · ${activeTheme}`;
|
|
4380
4112
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4381
|
-
...
|
|
4113
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4382
4114
|
children: /* @__PURE__ */ jsx("div", {
|
|
4383
4115
|
className: "sb-block__empty",
|
|
4384
4116
|
children: "No tokens match this filter."
|
|
4385
4117
|
})
|
|
4386
4118
|
});
|
|
4387
4119
|
return /* @__PURE__ */ jsxs("div", {
|
|
4388
|
-
...
|
|
4120
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4389
4121
|
children: [
|
|
4390
4122
|
searchable && /* @__PURE__ */ jsx("div", {
|
|
4391
4123
|
className: "sb-token-table__search",
|
|
@@ -4557,14 +4289,14 @@ function TypographyScale({ filter, sample = "The quick brown fox jumps over the
|
|
|
4557
4289
|
]);
|
|
4558
4290
|
const captionText = caption ?? `${rows.length} typography token${rows.length === 1 ? "" : "s"}${filter && filter !== "typography" ? ` matching \`${filter}\`` : ""} · ${activeTheme}`;
|
|
4559
4291
|
if (rows.length === 0) return /* @__PURE__ */ jsx("div", {
|
|
4560
|
-
...
|
|
4292
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4561
4293
|
children: /* @__PURE__ */ jsx("div", {
|
|
4562
4294
|
className: "sb-block__empty",
|
|
4563
4295
|
children: "No typography tokens match this filter."
|
|
4564
4296
|
})
|
|
4565
4297
|
});
|
|
4566
4298
|
return /* @__PURE__ */ jsxs("div", {
|
|
4567
|
-
...
|
|
4299
|
+
...blockWrapperAttrs(cssVarPrefix, activeAxes),
|
|
4568
4300
|
children: [/* @__PURE__ */ jsx("div", {
|
|
4569
4301
|
className: "sb-block__caption",
|
|
4570
4302
|
children: captionText
|
|
@@ -4587,6 +4319,6 @@ function TypographyScale({ filter, sample = "The quick brown fox jumps over the
|
|
|
4587
4319
|
});
|
|
4588
4320
|
}
|
|
4589
4321
|
//#endregion
|
|
4590
|
-
export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, ColorTable, CompositeBreakdown, CompositePreview, ConsumerOutput, Diagnostics, DimensionBar, DimensionScale,
|
|
4322
|
+
export { AliasChain, AliasedBy, AxesContext, AxisVariance, BorderPreview, BorderSample, COLOR_FORMATS, ColorFormatContext, ColorPalette, ColorTable, CompositeBreakdown, CompositePreview, ConsumerOutput, Diagnostics, DimensionBar, DimensionScale, FontFamilyPreview, FontWeightScale, GradientPalette, MotionPreview, MotionSample, OpacityScale, ShadowPreview, ShadowSample, StrokeStylePreview, SwatchbookContext, SwatchbookProvider, TOKENS_UPDATED_EVENT, ThemeContext, TokenDetail, TokenHeader, TokenNavigator, TokenTable, TokenUsageSnippet, TypographyScale, formatColor, registerTokenSource, useActiveAxes, useActiveTheme, useChannelGlobals, useColorFormat, useOptionalSwatchbookData, useSwatchbookData, useTokenSnapshot };
|
|
4591
4323
|
|
|
4592
4324
|
//# sourceMappingURL=index.mjs.map
|