@unpunnyfuns/swatchbook-addon 0.59.1 → 0.60.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/dist/hooks/index.mjs +3 -3
- package/dist/hooks/index.mjs.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/preset.d.mts +1 -3
- package/dist/preset.mjs +7 -13
- package/dist/preset.mjs.map +1 -1
- package/dist/{preview-C3BkGw7M.mjs → preview-EthytSmK.mjs} +10 -18
- package/dist/preview-EthytSmK.mjs.map +1 -0
- package/dist/preview.mjs +1 -1
- package/package.json +4 -4
- package/dist/preview-C3BkGw7M.mjs.map +0 -1
package/dist/hooks/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolveAllAt } from "@unpunnyfuns/swatchbook-core/graph";
|
|
2
|
+
import { cssVarPrefix, defaultTuple, tokenGraph } from "virtual:swatchbook/tokens";
|
|
3
3
|
import { useActiveAxes, useOptionalSwatchbookData } from "@unpunnyfuns/swatchbook-blocks";
|
|
4
4
|
import { makeCssVar } from "@unpunnyfuns/swatchbook-core/css-var";
|
|
5
5
|
//#region src/hooks/use-token.ts
|
|
@@ -10,7 +10,7 @@ import { makeCssVar } from "@unpunnyfuns/swatchbook-core/css-var";
|
|
|
10
10
|
* file so the hook can be called outside the addon's preview wrapper
|
|
11
11
|
* (autodocs / MDX renders).
|
|
12
12
|
*/
|
|
13
|
-
const fallbackResolveAt =
|
|
13
|
+
const fallbackResolveAt = (tuple) => resolveAllAt(tokenGraph, tuple);
|
|
14
14
|
/**
|
|
15
15
|
* Read a DTCG token for the currently active theme. Re-reads on theme
|
|
16
16
|
* switch via the addon's `SwatchbookContext`. Returns `{ value, cssVar,
|
package/dist/hooks/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["virtualTokenGraph","virtualCssVarPrefix","virtualDefaultTuple"],"sources":["../../src/hooks/use-token.ts"],"sourcesContent":["import {\n cssVarPrefix as virtualCssVarPrefix,\n defaultTuple as virtualDefaultTuple,\n tokenGraph as virtualTokenGraph,\n} from 'virtual:swatchbook/tokens';\nimport { resolveAllAt } from '@unpunnyfuns/swatchbook-core/graph';\nimport { makeCssVar } from '@unpunnyfuns/swatchbook-core/css-var';\nimport { useActiveAxes, useOptionalSwatchbookData } from '@unpunnyfuns/swatchbook-blocks';\n\n/**\n * Module-scope `resolveAt` for the no-provider fallback path. Built\n * once from the stable virtual-module exports — mirrors what the\n * preview decorator does for its `previewResolveAt` but lives in this\n * file so the hook can be called outside the addon's preview wrapper\n * (autodocs / MDX renders).\n */\nconst fallbackResolveAt = (tuple: Record<string, string>) => resolveAllAt(virtualTokenGraph, tuple);\n\n/**\n * Consumers augment this interface (via the addon's generated\n * `.swatchbook/tokens.d.ts`) to narrow {@link useToken}'s first parameter\n * to their project's actual token paths. Without augmentation it's empty\n * and {@link TokenPath} falls back to `string`.\n */\nexport interface SwatchbookTokenMap {}\n\ntype KnownPath = keyof SwatchbookTokenMap;\n\n/** Union of known token paths, or `string` when the addon codegen hasn't run. */\nexport type TokenPath = [KnownPath] extends [never] ? string : KnownPath;\n\nexport interface TokenInfo {\n /** The resolved DTCG `$value`. Shape varies by `$type`. */\n value: unknown;\n /** `var(--prefix-token-path)` reference, ready to drop into any CSS value. */\n cssVar: string;\n /** DTCG `$type` of the token, if known. */\n type?: string;\n /** Optional DTCG `$description`. */\n description?: string;\n}\n\n/**\n * Read a DTCG token for the currently active theme. Re-reads on theme\n * switch via the addon's `SwatchbookContext`. Returns `{ value, cssVar,\n * type, description }`.\n *\n * Typed paths appear automatically once `.swatchbook/tokens.d.ts` is\n * generated (happens on first storybook start/build). Until then\n * `TokenPath` is `string`.\n *\n * Safe to call in autodocs / MDX renders — uses plain React context, not\n * Storybook's preview-only hooks. Reads from the addon-provided\n * `SwatchbookContext` when present (preferred — uses the lifted\n * `resolveAt` accessor and the live active tuple); falls back to the\n * virtual module's eager `permutationsResolved` lookup keyed by the\n * default permutation name when no provider is mounted.\n */\nexport function useToken(path: TokenPath): TokenInfo {\n const snapshot = useOptionalSwatchbookData();\n const contextAxes = useActiveAxes();\n const hasContextAxes = Object.keys(contextAxes).length > 0;\n\n const prefix = snapshot?.cssVarPrefix ?? virtualCssVarPrefix;\n const resolver = snapshot?.resolveAt ?? fallbackResolveAt;\n const tuple = hasContextAxes\n ? (contextAxes as Record<string, string>)\n : (snapshot?.defaultTuple ?? virtualDefaultTuple);\n const token = resolver(tuple)[path] as\n | { $value?: unknown; $type?: string; $description?: string }\n | undefined;\n\n const info: TokenInfo = {\n value: token?.$value,\n cssVar: makeCssVar(path, prefix),\n };\n if (token?.$type) info.type = token.$type;\n if (token?.$description) info.description = token.$description;\n return info;\n}\n"],"mappings":";;;;;;;;;;;;AAgBA,MAAM,qBAAqB,UAAkC,aAAaA,YAAmB,MAAM;;;;;;;;;;;;;;;;;AA0CnG,SAAgB,SAAS,MAA4B;CACnD,MAAM,WAAW,2BAA2B;CAC5C,MAAM,cAAc,eAAe;CACnC,MAAM,iBAAiB,OAAO,KAAK,YAAY,CAAC,SAAS;CAEzD,MAAM,SAAS,UAAU,gBAAgBC;CAKzC,MAAM,SAJW,UAAU,aAAa,mBAC1B,iBACT,cACA,UAAU,gBAAgBC,aACF,CAAC;CAI9B,MAAM,OAAkB;EACtB,OAAO,OAAO;EACd,QAAQ,WAAW,MAAM,OAAO;EACjC;AACD,KAAI,OAAO,MAAO,MAAK,OAAO,MAAM;AACpC,KAAI,OAAO,aAAc,MAAK,cAAc,MAAM;AAClD,QAAO"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { i as preview_exports } from "./preview-
|
|
1
|
+
import { i as preview_exports } from "./preview-EthytSmK.mjs";
|
|
2
2
|
import { c as PARAM_KEY, h as VIRTUAL_MODULE_ID, m as TOOL_ID, n as AXES_GLOBAL_KEY, r as COLOR_FORMAT_GLOBAL_KEY, t as ADDON_ID } from "./constants-B31xFInv.mjs";
|
|
3
3
|
import { definePreviewAddon } from "storybook/internal/csf";
|
|
4
4
|
export * from "@unpunnyfuns/swatchbook-blocks";
|
package/dist/preset.d.mts
CHANGED
|
@@ -14,10 +14,8 @@ interface PresetOptions extends AddonOptions {
|
|
|
14
14
|
* `useToken()` autocompletes against the loaded project.
|
|
15
15
|
*/
|
|
16
16
|
declare function viteFinal(viteConfig: InlineConfig, options: PresetOptions): Promise<InlineConfig>;
|
|
17
|
-
/** Storybook appends this module into the manager bundle so our toolbar tool registers. */
|
|
18
|
-
declare function managerEntries(entry?: string[]): string[];
|
|
19
17
|
/** @internal Exported for tests; not part of the public API. */
|
|
20
18
|
declare function renderTokenTypes(project: Project): string;
|
|
21
19
|
//#endregion
|
|
22
|
-
export {
|
|
20
|
+
export { renderTokenTypes, viteFinal };
|
|
23
21
|
//# sourceMappingURL=preset.d.mts.map
|
package/dist/preset.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { d as RESOLVED_VIRTUAL_MODULE_ID, i as HMR_EVENT, u as RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID } from "./constants-B31xFInv.mjs";
|
|
2
|
+
import { listPaths } from "@unpunnyfuns/swatchbook-core/graph";
|
|
2
3
|
import { enumerateThemes } from "@unpunnyfuns/swatchbook-core/themes";
|
|
3
4
|
import { mkdir, writeFile } from "node:fs/promises";
|
|
4
5
|
import { basename, dirname, isAbsolute, resolve } from "node:path";
|
|
5
|
-
import {
|
|
6
|
+
import { pathToFileURL } from "node:url";
|
|
6
7
|
import { emitAxisProjectedCss, loadProject } from "@unpunnyfuns/swatchbook-core";
|
|
7
8
|
import { createJiti } from "jiti";
|
|
8
9
|
import { snapshotForWire } from "@unpunnyfuns/swatchbook-core/snapshot-for-wire";
|
|
@@ -68,10 +69,8 @@ function swatchbookTokensPlugin({ config, cwd, integrations = [], initialProject
|
|
|
68
69
|
`export const css = ${JSON.stringify(snap.css)};`,
|
|
69
70
|
`export const cssVarPrefix = ${JSON.stringify(snap.cssVarPrefix)};`,
|
|
70
71
|
`export const listing = ${JSON.stringify(snap.listing)};`,
|
|
71
|
-
`export const
|
|
72
|
-
`export const
|
|
73
|
-
`export const varianceByPath = ${JSON.stringify(snap.varianceByPath)};`,
|
|
74
|
-
`export const defaultTuple = ${JSON.stringify(snap.defaultTuple)};`
|
|
72
|
+
`export const defaultTuple = ${JSON.stringify(snap.defaultTuple)};`,
|
|
73
|
+
`export const tokenGraph = ${JSON.stringify(snap.tokenGraph)};`
|
|
75
74
|
].join("\n");
|
|
76
75
|
},
|
|
77
76
|
async configureServer(server) {
|
|
@@ -90,7 +89,7 @@ function swatchbookTokensPlugin({ config, cwd, integrations = [], initialProject
|
|
|
90
89
|
(async () => {
|
|
91
90
|
await refresh();
|
|
92
91
|
if (!project) return;
|
|
93
|
-
const tokenCount = project.
|
|
92
|
+
const tokenCount = [...listPaths(project.tokenGraph)].length;
|
|
94
93
|
const diagCount = project.diagnostics.length;
|
|
95
94
|
server.config.logger.info(`\x1b[36m[swatchbook]\x1b[0m tokens reloaded — ${tokenCount} tokens, ${diagCount} diagnostic${diagCount === 1 ? "" : "s"}`, {
|
|
96
95
|
clear: false,
|
|
@@ -176,11 +175,6 @@ async function viteFinal(viteConfig, options) {
|
|
|
176
175
|
plugins
|
|
177
176
|
};
|
|
178
177
|
}
|
|
179
|
-
/** Storybook appends this module into the manager bundle so our toolbar tool registers. */
|
|
180
|
-
function managerEntries(entry = []) {
|
|
181
|
-
const managerUrl = import.meta.resolve("@unpunnyfuns/swatchbook-addon/manager");
|
|
182
|
-
return [...entry, fileURLToPath(managerUrl)];
|
|
183
|
-
}
|
|
184
178
|
async function resolveConfig(options) {
|
|
185
179
|
const projectRoot = resolve(options.configDir, "..");
|
|
186
180
|
if (options.config) return {
|
|
@@ -205,7 +199,7 @@ async function writeTokenCodegen(project, options) {
|
|
|
205
199
|
}
|
|
206
200
|
/** @internal Exported for tests; not part of the public API. */
|
|
207
201
|
function renderTokenTypes(project) {
|
|
208
|
-
const tokenEntries = [...project.
|
|
202
|
+
const tokenEntries = [...listPaths(project.tokenGraph)].toSorted().map((p) => ` ${JSON.stringify(p)}: string;`);
|
|
209
203
|
const themeNames = project.axes.length === 0 ? [] : enumerateThemes(project).map((t) => t.name);
|
|
210
204
|
const themeUnion = themeNames.length > 0 ? themeNames.map((n) => JSON.stringify(n)).join(" | ") : "string";
|
|
211
205
|
return [
|
|
@@ -221,6 +215,6 @@ function renderTokenTypes(project) {
|
|
|
221
215
|
].join("\n");
|
|
222
216
|
}
|
|
223
217
|
//#endregion
|
|
224
|
-
export {
|
|
218
|
+
export { renderTokenTypes, viteFinal };
|
|
225
219
|
|
|
226
220
|
//# sourceMappingURL=preset.mjs.map
|
package/dist/preset.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"preset.mjs","names":["fsWatch"],"sources":["../src/virtual/plugin.ts","../src/preset.ts"],"sourcesContent":["import type { Config, Project, SwatchbookIntegration } from '@unpunnyfuns/swatchbook-core';\nimport { emitAxisProjectedCss, loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { snapshotForWire } from '@unpunnyfuns/swatchbook-core/snapshot-for-wire';\nimport { watch as fsWatch } from 'node:fs';\nimport type { FSWatcher } from 'node:fs';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'node:path';\nimport picomatch from 'picomatch';\nimport type { Plugin } from 'vite';\nimport {\n HMR_EVENT,\n INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID,\n RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID,\n RESOLVED_VIRTUAL_MODULE_ID,\n VIRTUAL_MODULE_ID,\n} from '#/constants.ts';\n\nexport interface SwatchbookPluginOptions {\n config: Config;\n cwd: string;\n /** Display-side integrations — each may contribute a virtual module the preview imports. */\n integrations?: readonly SwatchbookIntegration[];\n /**\n * Pre-loaded project to use for the first `buildStart` instead of\n * calling `loadProject` again. Lets `preset.viteFinal` share its\n * single `loadProject` call (originally needed for codegen) with the\n * plugin, eliminating a redundant parse pass at Storybook startup.\n * HMR-triggered reloads still call `loadProject` directly.\n */\n initialProject?: Project;\n}\n\n/** `\\0<virtualId>` — Vite convention for resolved virtual module IDs. */\nfunction resolvedId(virtualId: string): string {\n return `\\0${virtualId}`;\n}\n\n/**\n * Vite plugin that serves the virtual `virtual:swatchbook/tokens` module —\n * a single source of truth for permutations, resolved token maps, per-theme CSS,\n * and diagnostics. Watches the token files + resolver for changes and\n * invalidates the module so HMR reloads the preview with fresh data.\n */\nexport function swatchbookTokensPlugin({\n config,\n cwd,\n integrations = [],\n initialProject,\n}: SwatchbookPluginOptions): Plugin {\n let project: Project | undefined = initialProject;\n let css = project ? emitAxisProjectedCss(project) : '';\n\n async function refresh(): Promise<void> {\n project = await loadProject(config, cwd);\n css = emitAxisProjectedCss(project);\n }\n\n /** Map of resolvedId → integration, indexed once. */\n const integrationById = new Map<string, SwatchbookIntegration>();\n /** Virtual IDs the preview auto-imports as side effects (global CSS). */\n const autoInjectIds: string[] = [];\n for (const integration of integrations) {\n const vm = integration.virtualModule;\n if (!vm) continue;\n integrationById.set(resolvedId(vm.virtualId), integration);\n if (vm.autoInject) autoInjectIds.push(vm.virtualId);\n }\n\n return {\n name: 'swatchbook:virtual-tokens',\n enforce: 'pre',\n\n async buildStart() {\n // Skip the redundant load when preset.viteFinal already supplied\n // a freshly-loaded project via `initialProject`. The first HMR\n // reload (or a manual `refresh()`) calls `loadProject` as usual.\n if (project) return;\n await refresh();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;\n if (id === INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID) {\n return RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID;\n }\n for (const integration of integrations) {\n if (integration.virtualModule?.virtualId === id) {\n return resolvedId(integration.virtualModule.virtualId);\n }\n }\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID) {\n // Aggregate side-effect imports. Empty when no integration\n // opted in — still a valid ESM module, just a no-op.\n return autoInjectIds.map((vid) => `import ${JSON.stringify(vid)};`).join('\\n');\n }\n const integration = integrationById.get(id);\n if (integration?.virtualModule) {\n if (!project) return '';\n return integration.virtualModule.render(project);\n }\n if (id !== RESOLVED_VIRTUAL_MODULE_ID) return null;\n if (!project) return 'export default null;';\n // Emit a typed ESM module. `snapshotForWire` does the field set +\n // Map-to-Object conversion in one place; we destructure here and\n // JSON-stringify each field for ESM export.\n const snap = snapshotForWire(project, css);\n return [\n `/* swatchbook virtual module — generated */`,\n `export const axes = ${JSON.stringify(snap.axes)};`,\n `export const presets = ${JSON.stringify(snap.presets)};`,\n `export const disabledAxes = ${JSON.stringify(snap.disabledAxes)};`,\n `export const diagnostics = ${JSON.stringify(snap.diagnostics)};`,\n `export const css = ${JSON.stringify(snap.css)};`,\n `export const cssVarPrefix = ${JSON.stringify(snap.cssVarPrefix)};`,\n `export const listing = ${JSON.stringify(snap.listing)};`,\n `export const cells = ${JSON.stringify(snap.cells)};`,\n `export const jointOverrides = ${JSON.stringify(snap.jointOverrides)};`,\n `export const varianceByPath = ${JSON.stringify(snap.varianceByPath)};`,\n `export const defaultTuple = ${JSON.stringify(snap.defaultTuple)};`,\n ].join('\\n');\n },\n\n async configureServer(server) {\n // `configureServer` fires before `buildStart` in Vite's plugin\n // lifecycle, so `project` is still undefined when consumers only\n // set `config.resolver` (no `tokens` glob). Force an initial load\n // here so the watcher setup below sees a populated `sourceFiles`\n // list — otherwise only the resolver file itself gets watched,\n // and saves to any `$ref` target silently drop.\n if (!project) await refresh();\n\n /**\n * Editors typically emit two or three filesystem events per save\n * (atomic rename + rewrite + metadata). A 100 ms trailing debounce\n * coalesces those into a single reload while staying well under\n * user-perceptible latency.\n */\n let pending: ReturnType<typeof setTimeout> | null = null;\n const invalidate = (): void => {\n if (pending) clearTimeout(pending);\n pending = setTimeout(() => {\n pending = null;\n void (async () => {\n await refresh();\n if (!project) return;\n const tokenCount = project.varianceByPath.size;\n const diagCount = project.diagnostics.length;\n server.config.logger.info(\n `\\x1b[36m[swatchbook]\\x1b[0m tokens reloaded — ${tokenCount} tokens, ${diagCount} diagnostic${diagCount === 1 ? '' : 's'}`,\n { clear: false, timestamp: true },\n );\n const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n // Invalidate every integration-contributed virtual module so\n // its body re-renders against the fresh project on the next\n // request.\n for (const resolvedIntegrationId of integrationById.keys()) {\n const m = server.moduleGraph.getModuleById(resolvedIntegrationId);\n if (m) server.moduleGraph.invalidateModule(m);\n }\n /**\n * Send the fresh snapshot as a custom HMR event instead of a\n * full-reload. The preview subscribes and re-broadcasts to\n * blocks via the Storybook channel so the React tree\n * re-renders in place without losing toolbar / args / scroll\n * state. Field shape matches the INIT_EVENT payload so the\n * preview can hand it straight through.\n */\n server.ws.send({\n type: 'custom',\n event: HMR_EVENT,\n data: snapshotForWire(project, css),\n });\n })();\n }, 100);\n };\n\n /**\n * Watch each source file's *parent directory* rather than the file\n * itself. File-level `fs.watch` is fragile: atomic-save editors\n * unlink the old inode and write a new one, so the original\n * watcher either fires a one-shot 'rename' and goes deaf, or on\n * some platforms loops on ghost events for the old inode. Watching\n * the dir sidesteps both — the dir inode is stable across the\n * rename dance — and filename filtering keeps event volume low.\n *\n * Vite's `server.watcher` still wouldn't carry these events across\n * pnpm symlink boundaries, so we keep running our own watchers.\n */\n const byDir = new Map<string, Set<string>>();\n for (const file of project?.sourceFiles ?? []) {\n const dir = dirname(file);\n const set = byDir.get(dir) ?? new Set<string>();\n set.add(basename(file));\n byDir.set(dir, set);\n }\n\n const fileWatchers: FSWatcher[] = [];\n for (const [dir, names] of byDir) {\n try {\n const w = fsWatch(dir, { persistent: false }, (eventType, filename) => {\n if (!filename) return;\n if (!names.has(filename)) return;\n if (eventType === 'change' || eventType === 'rename') invalidate();\n });\n fileWatchers.push(w);\n } catch {\n // unwatchable dir — skip. Next loadProject pass will report it.\n }\n }\n server.httpServer?.once('close', () => {\n for (const w of fileWatchers) w.close();\n });\n },\n };\n}\n\n/**\n * Collect the set of filesystem paths the dev server should watch for\n * HMR. When `config.tokens` is set, use its globs (stripped to their\n * base directories) — users opt in to broader watching this way. When\n * absent, use the resolver file + every `$ref` target it pulled in, as\n * tracked on `project.sourceFiles` — which stays correct as the resolver\n * evolves without requiring a parallel `tokens` glob.\n */\n/** @internal Exported for tests; not part of the public API. */\nexport function collectWatchPaths(\n config: Config,\n project: Project | undefined,\n cwd: string,\n): string[] {\n const paths: string[] = [];\n if (config.tokens && config.tokens.length > 0) {\n for (const glob of config.tokens) {\n // `picomatch.scan` yields the longest literal prefix before any glob\n // metachar, so it handles brace expansion, nested globstars, and the\n // other shapes the hand-rolled regex missed.\n const { base } = picomatch.scan(glob);\n paths.push(resolveFromCwd(base || '.', cwd));\n }\n } else if (project?.sourceFiles) {\n for (const file of project.sourceFiles) paths.push(dirname(file));\n }\n if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));\n return [...new Set(paths)];\n}\n\nfunction resolveFromCwd(p: string, cwd: string): string {\n if (isAbsolute(p)) return p;\n return resolvePath(cwd, p);\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { enumerateThemes } from '@unpunnyfuns/swatchbook-core/themes';\nimport { createJiti } from 'jiti';\nimport type { InlineConfig } from 'vite';\nimport type { AddonOptions } from '#/options.ts';\nimport { swatchbookTokensPlugin } from '#/virtual/plugin.ts';\n\ninterface PresetOptions extends AddonOptions {\n /** Storybook injects this — the `.storybook` directory absolute path. */\n configDir: string;\n}\n\n/**\n * Storybook preset entry. Called by Storybook at config time; extends Vite's\n * plugin list with our virtual-module plugin so the preview can import\n * `virtual:swatchbook/tokens`. Also writes the typed token-path codegen so\n * `useToken()` autocompletes against the loaded project.\n */\nexport async function viteFinal(\n viteConfig: InlineConfig,\n options: PresetOptions,\n): Promise<InlineConfig> {\n const { config, cwd } = await resolveConfig(options);\n\n // One `loadProject` call shared between codegen (needs the resolved\n // shape to render typed token paths) and the Vite plugin (uses it\n // for its first virtual-module render). Without sharing, the addon\n // calls `loadProject` twice at Storybook startup — once here for\n // codegen, once again inside the plugin's `buildStart`.\n const project = await loadProject(config, cwd);\n await writeTokenCodegen(project, options);\n\n const plugins = Array.isArray(viteConfig.plugins) ? [...viteConfig.plugins] : [];\n plugins.push(\n swatchbookTokensPlugin({\n config,\n cwd,\n initialProject: project,\n ...(options.integrations !== undefined && { integrations: options.integrations }),\n }),\n );\n\n return { ...viteConfig, plugins };\n}\n\n/** Storybook appends this module into the manager bundle so our toolbar tool registers. */\nexport function managerEntries(entry: string[] = []): string[] {\n const managerUrl = import.meta.resolve('@unpunnyfuns/swatchbook-addon/manager');\n return [...entry, fileURLToPath(managerUrl)];\n}\n\nasync function resolveConfig(options: PresetOptions): Promise<{ config: Config; cwd: string }> {\n const projectRoot = resolve(options.configDir, '..');\n\n if (options.config) {\n return { config: options.config, cwd: projectRoot };\n }\n\n const path = options.configPath ?? 'swatchbook.config.ts';\n const absolute = isAbsolute(path) ? path : resolve(options.configDir, path);\n\n const jiti = createJiti(pathToFileURL(options.configDir).href, {\n interopDefault: true,\n moduleCache: false,\n });\n const loaded = (await jiti.import(absolute, { default: true })) as Config;\n\n // If the config file isn't at projectRoot, still resolve globs from its dir.\n const cwd = dirname(absolute);\n return { config: loaded, cwd };\n}\n\nasync function writeTokenCodegen(project: Project, options: PresetOptions): Promise<void> {\n const projectRoot = resolve(options.configDir, '..');\n const outDir = resolve(projectRoot, project.config.outDir ?? '.swatchbook');\n await mkdir(outDir, { recursive: true });\n const content = renderTokenTypes(project);\n await writeFile(resolve(outDir, 'tokens.d.ts'), content);\n}\n\n/** @internal Exported for tests; not part of the public API. */\nexport function renderTokenTypes(project: Project): string {\n const sorted = [...project.varianceByPath.keys()].toSorted();\n const tokenEntries = sorted.map((p) => ` ${JSON.stringify(p)}: string;`);\n const themeNames = project.axes.length === 0 ? [] : enumerateThemes(project).map((t) => t.name);\n const themeUnion =\n themeNames.length > 0 ? themeNames.map((n) => JSON.stringify(n)).join(' | ') : 'string';\n\n return [\n '// Generated by @unpunnyfuns/swatchbook-addon. Do not edit.',\n \"declare module '@unpunnyfuns/swatchbook-addon/hooks' {\",\n ' interface SwatchbookTokenMap {',\n ...tokenEntries,\n ' }',\n '',\n ` export type SwatchbookPermutationName = ${themeUnion};`,\n '}',\n '',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"preset.mjs","names":["fsWatch"],"sources":["../src/virtual/plugin.ts","../src/preset.ts"],"sourcesContent":["import type { Config, Project, SwatchbookIntegration } from '@unpunnyfuns/swatchbook-core';\nimport { emitAxisProjectedCss, loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { listPaths } from '@unpunnyfuns/swatchbook-core/graph';\nimport { snapshotForWire } from '@unpunnyfuns/swatchbook-core/snapshot-for-wire';\nimport { watch as fsWatch } from 'node:fs';\nimport type { FSWatcher } from 'node:fs';\nimport { basename, dirname, isAbsolute, resolve as resolvePath } from 'node:path';\nimport picomatch from 'picomatch';\nimport type { Plugin } from 'vite';\nimport {\n HMR_EVENT,\n INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID,\n RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID,\n RESOLVED_VIRTUAL_MODULE_ID,\n VIRTUAL_MODULE_ID,\n} from '#/constants.ts';\n\nexport interface SwatchbookPluginOptions {\n config: Config;\n cwd: string;\n /** Display-side integrations — each may contribute a virtual module the preview imports. */\n integrations?: readonly SwatchbookIntegration[];\n /**\n * Pre-loaded project to use for the first `buildStart` instead of\n * calling `loadProject` again. Lets `preset.viteFinal` share its\n * single `loadProject` call (originally needed for codegen) with the\n * plugin, eliminating a redundant parse pass at Storybook startup.\n * HMR-triggered reloads still call `loadProject` directly.\n */\n initialProject?: Project;\n}\n\n/** `\\0<virtualId>` — Vite convention for resolved virtual module IDs. */\nfunction resolvedId(virtualId: string): string {\n return `\\0${virtualId}`;\n}\n\n/**\n * Vite plugin that serves the virtual `virtual:swatchbook/tokens` module —\n * a single source of truth for permutations, resolved token maps, per-theme CSS,\n * and diagnostics. Watches the token files + resolver for changes and\n * invalidates the module so HMR reloads the preview with fresh data.\n */\nexport function swatchbookTokensPlugin({\n config,\n cwd,\n integrations = [],\n initialProject,\n}: SwatchbookPluginOptions): Plugin {\n let project: Project | undefined = initialProject;\n let css = project ? emitAxisProjectedCss(project) : '';\n\n async function refresh(): Promise<void> {\n project = await loadProject(config, cwd);\n css = emitAxisProjectedCss(project);\n }\n\n /** Map of resolvedId → integration, indexed once. */\n const integrationById = new Map<string, SwatchbookIntegration>();\n /** Virtual IDs the preview auto-imports as side effects (global CSS). */\n const autoInjectIds: string[] = [];\n for (const integration of integrations) {\n const vm = integration.virtualModule;\n if (!vm) continue;\n integrationById.set(resolvedId(vm.virtualId), integration);\n if (vm.autoInject) autoInjectIds.push(vm.virtualId);\n }\n\n return {\n name: 'swatchbook:virtual-tokens',\n enforce: 'pre',\n\n async buildStart() {\n // Skip the redundant load when preset.viteFinal already supplied\n // a freshly-loaded project via `initialProject`. The first HMR\n // reload (or a manual `refresh()`) calls `loadProject` as usual.\n if (project) return;\n await refresh();\n },\n\n resolveId(id) {\n if (id === VIRTUAL_MODULE_ID) return RESOLVED_VIRTUAL_MODULE_ID;\n if (id === INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID) {\n return RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID;\n }\n for (const integration of integrations) {\n if (integration.virtualModule?.virtualId === id) {\n return resolvedId(integration.virtualModule.virtualId);\n }\n }\n return null;\n },\n\n load(id) {\n if (id === RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID) {\n // Aggregate side-effect imports. Empty when no integration\n // opted in — still a valid ESM module, just a no-op.\n return autoInjectIds.map((vid) => `import ${JSON.stringify(vid)};`).join('\\n');\n }\n const integration = integrationById.get(id);\n if (integration?.virtualModule) {\n if (!project) return '';\n return integration.virtualModule.render(project);\n }\n if (id !== RESOLVED_VIRTUAL_MODULE_ID) return null;\n if (!project) return 'export default null;';\n // Emit a typed ESM module. `snapshotForWire` does the field set +\n // Map-to-Object conversion in one place; we destructure here and\n // JSON-stringify each field for ESM export.\n const snap = snapshotForWire(project, css);\n return [\n `/* swatchbook virtual module — generated */`,\n `export const axes = ${JSON.stringify(snap.axes)};`,\n `export const presets = ${JSON.stringify(snap.presets)};`,\n `export const disabledAxes = ${JSON.stringify(snap.disabledAxes)};`,\n `export const diagnostics = ${JSON.stringify(snap.diagnostics)};`,\n `export const css = ${JSON.stringify(snap.css)};`,\n `export const cssVarPrefix = ${JSON.stringify(snap.cssVarPrefix)};`,\n `export const listing = ${JSON.stringify(snap.listing)};`,\n `export const defaultTuple = ${JSON.stringify(snap.defaultTuple)};`,\n `export const tokenGraph = ${JSON.stringify(snap.tokenGraph)};`,\n ].join('\\n');\n },\n\n async configureServer(server) {\n // `configureServer` fires before `buildStart` in Vite's plugin\n // lifecycle, so `project` is still undefined when consumers only\n // set `config.resolver` (no `tokens` glob). Force an initial load\n // here so the watcher setup below sees a populated `sourceFiles`\n // list — otherwise only the resolver file itself gets watched,\n // and saves to any `$ref` target silently drop.\n if (!project) await refresh();\n\n /**\n * Editors typically emit two or three filesystem events per save\n * (atomic rename + rewrite + metadata). A 100 ms trailing debounce\n * coalesces those into a single reload while staying well under\n * user-perceptible latency.\n */\n let pending: ReturnType<typeof setTimeout> | null = null;\n const invalidate = (): void => {\n if (pending) clearTimeout(pending);\n pending = setTimeout(() => {\n pending = null;\n void (async () => {\n await refresh();\n if (!project) return;\n const tokenCount = [...listPaths(project.tokenGraph)].length;\n const diagCount = project.diagnostics.length;\n server.config.logger.info(\n `\\x1b[36m[swatchbook]\\x1b[0m tokens reloaded — ${tokenCount} tokens, ${diagCount} diagnostic${diagCount === 1 ? '' : 's'}`,\n { clear: false, timestamp: true },\n );\n const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n // Invalidate every integration-contributed virtual module so\n // its body re-renders against the fresh project on the next\n // request.\n for (const resolvedIntegrationId of integrationById.keys()) {\n const m = server.moduleGraph.getModuleById(resolvedIntegrationId);\n if (m) server.moduleGraph.invalidateModule(m);\n }\n /**\n * Send the fresh snapshot as a custom HMR event instead of a\n * full-reload. The preview subscribes and re-broadcasts to\n * blocks via the Storybook channel so the React tree\n * re-renders in place without losing toolbar / args / scroll\n * state. Field shape matches the INIT_EVENT payload so the\n * preview can hand it straight through.\n */\n server.ws.send({\n type: 'custom',\n event: HMR_EVENT,\n data: snapshotForWire(project, css),\n });\n })();\n }, 100);\n };\n\n /**\n * Watch each source file's *parent directory* rather than the file\n * itself. File-level `fs.watch` is fragile: atomic-save editors\n * unlink the old inode and write a new one, so the original\n * watcher either fires a one-shot 'rename' and goes deaf, or on\n * some platforms loops on ghost events for the old inode. Watching\n * the dir sidesteps both — the dir inode is stable across the\n * rename dance — and filename filtering keeps event volume low.\n *\n * Vite's `server.watcher` still wouldn't carry these events across\n * pnpm symlink boundaries, so we keep running our own watchers.\n */\n const byDir = new Map<string, Set<string>>();\n for (const file of project?.sourceFiles ?? []) {\n const dir = dirname(file);\n const set = byDir.get(dir) ?? new Set<string>();\n set.add(basename(file));\n byDir.set(dir, set);\n }\n\n const fileWatchers: FSWatcher[] = [];\n for (const [dir, names] of byDir) {\n try {\n const w = fsWatch(dir, { persistent: false }, (eventType, filename) => {\n if (!filename) return;\n if (!names.has(filename)) return;\n if (eventType === 'change' || eventType === 'rename') invalidate();\n });\n fileWatchers.push(w);\n } catch {\n // unwatchable dir — skip. Next loadProject pass will report it.\n }\n }\n server.httpServer?.once('close', () => {\n for (const w of fileWatchers) w.close();\n });\n },\n };\n}\n\n/**\n * Collect the set of filesystem paths the dev server should watch for\n * HMR. When `config.tokens` is set, use its globs (stripped to their\n * base directories) — users opt in to broader watching this way. When\n * absent, use the resolver file + every `$ref` target it pulled in, as\n * tracked on `project.sourceFiles` — which stays correct as the resolver\n * evolves without requiring a parallel `tokens` glob.\n */\n/** @internal Exported for tests; not part of the public API. */\nexport function collectWatchPaths(\n config: Config,\n project: Project | undefined,\n cwd: string,\n): string[] {\n const paths: string[] = [];\n if (config.tokens && config.tokens.length > 0) {\n for (const glob of config.tokens) {\n // `picomatch.scan` yields the longest literal prefix before any glob\n // metachar, so it handles brace expansion, nested globstars, and the\n // other shapes the hand-rolled regex missed.\n const { base } = picomatch.scan(glob);\n paths.push(resolveFromCwd(base || '.', cwd));\n }\n } else if (project?.sourceFiles) {\n for (const file of project.sourceFiles) paths.push(dirname(file));\n }\n if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));\n return [...new Set(paths)];\n}\n\nfunction resolveFromCwd(p: string, cwd: string): string {\n if (isAbsolute(p)) return p;\n return resolvePath(cwd, p);\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { listPaths } from '@unpunnyfuns/swatchbook-core/graph';\nimport { enumerateThemes } from '@unpunnyfuns/swatchbook-core/themes';\nimport { createJiti } from 'jiti';\nimport type { InlineConfig } from 'vite';\nimport type { AddonOptions } from '#/options.ts';\nimport { swatchbookTokensPlugin } from '#/virtual/plugin.ts';\n\ninterface PresetOptions extends AddonOptions {\n /** Storybook injects this — the `.storybook` directory absolute path. */\n configDir: string;\n}\n\n/**\n * Storybook preset entry. Called by Storybook at config time; extends Vite's\n * plugin list with our virtual-module plugin so the preview can import\n * `virtual:swatchbook/tokens`. Also writes the typed token-path codegen so\n * `useToken()` autocompletes against the loaded project.\n */\nexport async function viteFinal(\n viteConfig: InlineConfig,\n options: PresetOptions,\n): Promise<InlineConfig> {\n const { config, cwd } = await resolveConfig(options);\n\n // One `loadProject` call shared between codegen (needs the resolved\n // shape to render typed token paths) and the Vite plugin (uses it\n // for its first virtual-module render). Without sharing, the addon\n // calls `loadProject` twice at Storybook startup — once here for\n // codegen, once again inside the plugin's `buildStart`.\n const project = await loadProject(config, cwd);\n await writeTokenCodegen(project, options);\n\n const plugins = Array.isArray(viteConfig.plugins) ? [...viteConfig.plugins] : [];\n plugins.push(\n swatchbookTokensPlugin({\n config,\n cwd,\n initialProject: project,\n ...(options.integrations !== undefined && { integrations: options.integrations }),\n }),\n );\n\n return { ...viteConfig, plugins };\n}\n\nasync function resolveConfig(options: PresetOptions): Promise<{ config: Config; cwd: string }> {\n const projectRoot = resolve(options.configDir, '..');\n\n if (options.config) {\n return { config: options.config, cwd: projectRoot };\n }\n\n const path = options.configPath ?? 'swatchbook.config.ts';\n const absolute = isAbsolute(path) ? path : resolve(options.configDir, path);\n\n const jiti = createJiti(pathToFileURL(options.configDir).href, {\n interopDefault: true,\n moduleCache: false,\n });\n const loaded = (await jiti.import(absolute, { default: true })) as Config;\n\n // If the config file isn't at projectRoot, still resolve globs from its dir.\n const cwd = dirname(absolute);\n return { config: loaded, cwd };\n}\n\nasync function writeTokenCodegen(project: Project, options: PresetOptions): Promise<void> {\n const projectRoot = resolve(options.configDir, '..');\n const outDir = resolve(projectRoot, project.config.outDir ?? '.swatchbook');\n await mkdir(outDir, { recursive: true });\n const content = renderTokenTypes(project);\n await writeFile(resolve(outDir, 'tokens.d.ts'), content);\n}\n\n/** @internal Exported for tests; not part of the public API. */\nexport function renderTokenTypes(project: Project): string {\n const sorted = [...listPaths(project.tokenGraph)].toSorted();\n const tokenEntries = sorted.map((p) => ` ${JSON.stringify(p)}: string;`);\n const themeNames = project.axes.length === 0 ? [] : enumerateThemes(project).map((t) => t.name);\n const themeUnion =\n themeNames.length > 0 ? themeNames.map((n) => JSON.stringify(n)).join(' | ') : 'string';\n\n return [\n '// Generated by @unpunnyfuns/swatchbook-addon. Do not edit.',\n \"declare module '@unpunnyfuns/swatchbook-addon/hooks' {\",\n ' interface SwatchbookTokenMap {',\n ...tokenEntries,\n ' }',\n '',\n ` export type SwatchbookPermutationName = ${themeUnion};`,\n '}',\n '',\n ].join('\\n');\n}\n"],"mappings":"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"}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { a as INIT_EVENT, f as STYLE_ELEMENT_ID, i as HMR_EVENT, l as PREVIEW_MOUSEDOWN_EVENT, n as AXES_GLOBAL_KEY, o as INIT_REQUEST_EVENT, p as TOKENS_UPDATED_EVENT, r as COLOR_FORMAT_GLOBAL_KEY } from "./constants-B31xFInv.mjs";
|
|
2
|
-
import {
|
|
2
|
+
import { resolveAllAt } from "@unpunnyfuns/swatchbook-core/graph";
|
|
3
3
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { addons } from "storybook/preview-api";
|
|
5
5
|
import { dataAttr } from "@unpunnyfuns/swatchbook-core/data-attr";
|
|
6
6
|
import { ensureStyleElement } from "@unpunnyfuns/swatchbook-core/style-element";
|
|
7
7
|
import { tupleToName } from "@unpunnyfuns/swatchbook-core/themes";
|
|
8
8
|
import "virtual:swatchbook/integration-side-effects";
|
|
9
|
-
import { axes,
|
|
9
|
+
import { axes, css, cssVarPrefix, defaultTuple, diagnostics, disabledAxes, listing, presets, tokenGraph } from "virtual:swatchbook/tokens";
|
|
10
10
|
import { AxesContext, COLOR_FORMATS, ColorFormatContext, SwatchbookContext, ThemeContext } from "@unpunnyfuns/swatchbook-blocks";
|
|
11
11
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
12
12
|
//#region \0rolldown/runtime.js
|
|
@@ -118,9 +118,6 @@ function pickInitFields(source) {
|
|
|
118
118
|
presets: source.presets,
|
|
119
119
|
diagnostics: source.diagnostics,
|
|
120
120
|
cssVarPrefix: source.cssVarPrefix,
|
|
121
|
-
cells: source.cells,
|
|
122
|
-
jointOverrides: source.jointOverrides,
|
|
123
|
-
varianceByPath: source.varianceByPath,
|
|
124
121
|
defaultTuple: source.defaultTuple
|
|
125
122
|
};
|
|
126
123
|
}
|
|
@@ -136,9 +133,6 @@ function broadcastInit() {
|
|
|
136
133
|
presets,
|
|
137
134
|
diagnostics,
|
|
138
135
|
cssVarPrefix,
|
|
139
|
-
cells,
|
|
140
|
-
jointOverrides,
|
|
141
|
-
varianceByPath,
|
|
142
136
|
defaultTuple
|
|
143
137
|
}));
|
|
144
138
|
}
|
|
@@ -204,13 +198,13 @@ function resolveColorFormat(raw) {
|
|
|
204
198
|
}
|
|
205
199
|
/**
|
|
206
200
|
* Single shared `resolveAt` instance for the lifetime of the preview
|
|
207
|
-
* iframe.
|
|
208
|
-
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
201
|
+
* iframe. `virtualTokenGraph` is a module-level virtual-module export
|
|
202
|
+
* with stable identity, so this closure never needs to rebuild;
|
|
203
|
+
* downstream `ProjectSnapshot` consumers can key memos on the snapshot
|
|
204
|
+
* wrapper without worrying about `resolveAt` churning when Storybook
|
|
205
|
+
* recreates `context.globals`.
|
|
212
206
|
*/
|
|
213
|
-
const previewResolveAt =
|
|
207
|
+
const previewResolveAt = (tuple) => resolveAllAt(tokenGraph, tuple);
|
|
214
208
|
const themedDecorator = (Story, context) => {
|
|
215
209
|
const globals = context.globals;
|
|
216
210
|
const parameters = context.parameters;
|
|
@@ -256,9 +250,7 @@ const themedDecorator = (Story, context) => {
|
|
|
256
250
|
diagnostics,
|
|
257
251
|
css,
|
|
258
252
|
listing,
|
|
259
|
-
|
|
260
|
-
jointOverrides,
|
|
261
|
-
varianceByPath,
|
|
253
|
+
tokenGraph,
|
|
262
254
|
defaultTuple,
|
|
263
255
|
resolveAt: previewResolveAt
|
|
264
256
|
}), [themeName, tuple]);
|
|
@@ -383,4 +375,4 @@ if (import.meta.hot) import.meta.hot.on(HMR_EVENT, (payload) => {
|
|
|
383
375
|
//#endregion
|
|
384
376
|
export { preview_exports as i, globalTypes as n, initialGlobals as r, decorators as t };
|
|
385
377
|
|
|
386
|
-
//# sourceMappingURL=preview-
|
|
378
|
+
//# sourceMappingURL=preview-EthytSmK.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"preview-EthytSmK.mjs","names":["virtualDisabledAxes","virtualDefaultTuple","virtualAxes","virtualPresets","defaultTuple","virtualTokenGraph","virtualListing"],"sources":["../src/preview.tsx"],"sourcesContent":["/// <reference types=\"vite/client\" />\nimport { resolveAllAt } from '@unpunnyfuns/swatchbook-core/graph';\nimport type { TokenMap } from '@unpunnyfuns/swatchbook-core';\nimport type { Decorator, Preview } from '@storybook/react-vite';\nimport type { CSSProperties } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { addons } from 'storybook/preview-api';\nimport { dataAttr } from '@unpunnyfuns/swatchbook-core/data-attr';\nimport { ensureStyleElement } from '@unpunnyfuns/swatchbook-core/style-element';\nimport { tupleToName } from '@unpunnyfuns/swatchbook-core/themes';\n// Side-effect import for integrations that opted into `autoInject`\n// (e.g. Tailwind's `@theme` block). When no integration opts in, the\n// virtual module body is empty — still a valid no-op.\nimport 'virtual:swatchbook/integration-side-effects';\nimport {\n axes as virtualAxes,\n css,\n cssVarPrefix,\n defaultTuple as virtualDefaultTuple,\n diagnostics,\n disabledAxes as virtualDisabledAxes,\n listing as virtualListing,\n presets as virtualPresets,\n tokenGraph as virtualTokenGraph,\n} from 'virtual:swatchbook/tokens';\nimport {\n AxesContext,\n COLOR_FORMATS,\n ColorFormatContext,\n SwatchbookContext,\n ThemeContext,\n} from '@unpunnyfuns/swatchbook-blocks';\nimport type { ColorFormat, ProjectSnapshot } from '@unpunnyfuns/swatchbook-blocks';\nimport {\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n HMR_EVENT,\n INIT_EVENT,\n INIT_REQUEST_EVENT,\n PREVIEW_MOUSEDOWN_EVENT,\n STYLE_ELEMENT_ID,\n TOKENS_UPDATED_EVENT,\n} from '#/constants.ts';\nimport type { StoryParameters, SwatchbookGlobals } from '#/globals.ts';\n\n/**\n * Standard visually-hidden style for the theme-flip live region.\n * Keeps the announcement element discoverable by SR but out of visual\n * + pointer flow. The clip-path / `position: absolute` combination is\n * the canonical sr-only pattern.\n */\nconst SR_ONLY_STYLE: CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * The `html, body { ... }` rules that paint the iframe's own chrome\n * (outside any decorator wrapper — Docs mode, autodocs, empty gutters)\n * with the active theme's surface + text vars. Composed alongside the\n * emitted token CSS so both load through the same `<style>` element.\n */\nfunction iframeChromeRules(prefix: string): string {\n const surface = prefix ? `--${prefix}-color-surface-default` : '--color-surface-default';\n const text = prefix ? `--${prefix}-color-text-default` : '--color-text-default';\n return `\nhtml, body {\n background: var(${surface}, Canvas);\n color: var(${text}, CanvasText);\n margin: 0;\n}\n`;\n}\n\n/**\n * Inject the per-theme stylesheet plus the iframe-chrome block. Shared\n * with the HMR re-emit path below so a token refresh updates the\n * iframe's chrome rules from the same source.\n */\nfunction ensureStylesheet(cssText: string, prefix: string): void {\n ensureStyleElement(STYLE_ELEMENT_ID, `${cssText}\\n${iframeChromeRules(prefix)}`);\n}\n\n/**\n * Apply `cb(axisName, value)` for every pinned (disabled) axis whose\n * default-tuple value is set. `virtualDefaultTuple` carries the\n * post-filter axis defaults; disabled axes don't appear in\n * `virtualAxes` but their pinned context value still lives here, so\n * sampling it gives the same result the old \"first permutation's\n * input\" lookup did.\n */\nfunction forEachPinnedAxis(cb: (name: string, value: string) => void): void {\n for (const name of virtualDisabledAxes) {\n const value = virtualDefaultTuple[name];\n if (value !== undefined) cb(name, value);\n }\n}\n\n/**\n * Compose a stable theme name from a tuple — `axisValues.join(' · ')`\n * in axis order. Used for the `ThemeContext` value the blocks read and\n * the addon-channel signals downstream consumers subscribe to. Returns\n * empty string when there are no axes (no name to write).\n */\nfunction matchThemeName(tuple: Readonly<Record<string, string>>): string {\n if (virtualAxes.length === 0) return '';\n return tupleToName(virtualAxes, tuple);\n}\n\n/**\n * Write one `data-<prefix>-<axis>=<context>` per axis on `<html>`.\n * The smart CSS emitter targets these single-axis selectors (and\n * joint compounds across multiple) — that's the actual scoping\n * surface the cascade resolves through. Prefix follows `cssVarPrefix`\n * so attr namespace and emitted selectors stay in lockstep.\n */\nfunction setRootAxes(tuple: Readonly<Record<string, string>>): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n for (const axis of virtualAxes) {\n const attr = dataAttr(cssVarPrefix, axis.name);\n const value = tuple[axis.name];\n if (value === undefined) {\n root.removeAttribute(attr);\n } else {\n root.setAttribute(attr, value);\n }\n }\n forEachPinnedAxis((name, value) => {\n root.setAttribute(dataAttr(cssVarPrefix, name), value);\n });\n}\n\n/**\n * Subset of an INIT_EVENT-shaped object the manager bundle needs. The\n * fields are the union of what the toolbar and panel read; named so\n * `broadcastInit` (module-level virtual exports) and the HMR re-emit\n * (`payload`-shaped) compose the same payload from the same shape.\n */\ninterface InitFieldsSource {\n axes: typeof virtualAxes;\n disabledAxes: typeof virtualDisabledAxes;\n presets: typeof virtualPresets;\n diagnostics: typeof diagnostics;\n cssVarPrefix: string;\n defaultTuple: typeof virtualDefaultTuple;\n}\n\nfunction pickInitFields(source: InitFieldsSource): InitFieldsSource {\n return {\n axes: source.axes,\n disabledAxes: source.disabledAxes,\n presets: source.presets,\n diagnostics: source.diagnostics,\n cssVarPrefix: source.cssVarPrefix,\n defaultTuple: source.defaultTuple,\n };\n}\n\n/**\n * Emit the full virtual-module payload to the manager over Storybook's\n * channel so the toolbar + panel (which run in the manager bundle and\n * can't import our virtual module) can render from it.\n */\nfunction broadcastInit(): void {\n const channel = addons.getChannel();\n channel.emit(\n INIT_EVENT,\n pickInitFields({\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n diagnostics,\n cssVarPrefix,\n defaultTuple: virtualDefaultTuple,\n }),\n );\n}\n\n/** Axis-default tuple, used as the baseline before overrides. */\nfunction defaultTuple(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of virtualAxes) out[axis.name] = axis.default;\n return out;\n}\n\n/**\n * Reverse-engineer a tuple from a `Light · Brand A · Normal`-shape\n * theme name. Splits on ` · ` and zips with `virtualAxes` in declared\n * order — matches `matchThemeName`'s production direction so a\n * round-trip is lossless. Returns `undefined` when the segment count\n * doesn't match the axis count.\n */\nfunction tupleForName(name: string): Record<string, string> | undefined {\n if (!name) return undefined;\n const parts = name.split(' · ');\n if (parts.length !== virtualAxes.length) return undefined;\n const out: Record<string, string> = {};\n for (let i = 0; i < virtualAxes.length; i++) {\n const axis = virtualAxes[i] as (typeof virtualAxes)[number];\n const value = parts[i];\n if (value === undefined) return undefined;\n out[axis.name] = value;\n }\n return out;\n}\n\n/**\n * Merge a partial tuple onto the axis defaults, dropping keys for axes that\n * don't exist and silently falling back to the default for contexts that\n * aren't listed on the axis.\n */\nfunction normalizeTuple(partial: Readonly<Record<string, string>>): Record<string, string> {\n const out = defaultTuple();\n for (const axis of virtualAxes) {\n const candidate = partial[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n return out;\n}\n\n/**\n * Resolve the active tuple from all input channels, in priority order:\n * 1. `parameters.swatchbook.axes` — per-story tuple.\n * 2. `parameters.swatchbook.permutation` — per-story composed name.\n * 3. `globals.swatchbookAxes` — toolbar-set tuple.\n * 4. virtual module default.\n */\nfunction resolveTuple(\n axesGlobal: SwatchbookGlobals[typeof AXES_GLOBAL_KEY],\n paramSwatchbook: StoryParameters['swatchbook'],\n): Record<string, string> {\n const paramAxes = paramSwatchbook?.axes;\n if (paramAxes) {\n return normalizeTuple(paramAxes);\n }\n if (paramSwatchbook?.permutation) {\n const hit = tupleForName(paramSwatchbook.permutation);\n if (hit) return normalizeTuple(hit);\n }\n if (axesGlobal && typeof axesGlobal === 'object') {\n return normalizeTuple(axesGlobal as Record<string, string>);\n }\n return defaultTuple();\n}\n\nfunction resolveColorFormat(raw: SwatchbookGlobals[typeof COLOR_FORMAT_GLOBAL_KEY]): ColorFormat {\n if (typeof raw === 'string' && (COLOR_FORMATS as readonly string[]).includes(raw)) {\n return raw as ColorFormat;\n }\n return 'hex';\n}\n\n/**\n * Single shared `resolveAt` instance for the lifetime of the preview\n * iframe. `virtualTokenGraph` is a module-level virtual-module export\n * with stable identity, so this closure never needs to rebuild;\n * downstream `ProjectSnapshot` consumers can key memos on the snapshot\n * wrapper without worrying about `resolveAt` churning when Storybook\n * recreates `context.globals`.\n */\nconst previewResolveAt = (tuple: Record<string, string>): TokenMap =>\n resolveAllAt(virtualTokenGraph, tuple);\n\nconst themedDecorator: Decorator = (Story, context) => {\n const globals = context.globals as SwatchbookGlobals;\n const parameters = context.parameters as StoryParameters;\n const axesGlobal = globals[AXES_GLOBAL_KEY];\n const colorFormatGlobal = globals[COLOR_FORMAT_GLOBAL_KEY];\n const paramSwatchbook = parameters.swatchbook;\n const tuple = useMemo(\n () => resolveTuple(axesGlobal, paramSwatchbook),\n [axesGlobal, paramSwatchbook],\n );\n const colorFormat = useMemo(() => resolveColorFormat(colorFormatGlobal), [colorFormatGlobal]);\n const themeName = useMemo(() => matchThemeName(tuple), [tuple]);\n\n useEffect(() => {\n ensureStylesheet(css, cssVarPrefix);\n broadcastInit();\n }, []);\n\n useEffect(() => {\n setRootAxes(tuple);\n }, [tuple]);\n\n // Page-level live region announces theme/axis flips to SR users.\n // Initial mount stays silent (no spurious announcement on every story\n // load); subsequent `themeName` changes schedule a debounced update so\n // rapid axis flips (or per-story tuple overrides while paging through\n // a Storybook docs index) collapse into one announcement.\n const [announcement, setAnnouncement] = useState('');\n const initialThemeRef = useRef(themeName);\n useEffect(() => {\n if (themeName === initialThemeRef.current) return;\n const timer = setTimeout(() => {\n setAnnouncement(themeName ? `Theme: ${themeName}` : '');\n }, 250);\n return () => {\n clearTimeout(timer);\n };\n }, [themeName]);\n\n const wrapperAttrs: Record<string, string> = {};\n for (const axis of virtualAxes) {\n const value = tuple[axis.name];\n if (value !== undefined) wrapperAttrs[dataAttr(cssVarPrefix, axis.name)] = value;\n }\n forEachPinnedAxis((name, value) => {\n wrapperAttrs[dataAttr(cssVarPrefix, name)] = value;\n });\n\n const snapshot = useMemo<ProjectSnapshot>(\n () => ({\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n activeTheme: themeName,\n activeAxes: tuple,\n cssVarPrefix,\n diagnostics,\n css,\n listing: virtualListing,\n tokenGraph: virtualTokenGraph,\n defaultTuple: virtualDefaultTuple,\n resolveAt: previewResolveAt,\n }),\n [themeName, tuple],\n );\n\n return (\n <SwatchbookContext.Provider value={snapshot}>\n <ThemeContext.Provider value={themeName}>\n <AxesContext.Provider value={tuple}>\n <ColorFormatContext.Provider value={colorFormat}>\n <div\n {...wrapperAttrs}\n style={{\n padding: '1rem',\n minHeight: '100%',\n }}\n >\n <Story />\n </div>\n <div role=\"status\" aria-live=\"polite\" style={SR_ONLY_STYLE}>\n {announcement}\n </div>\n </ColorFormatContext.Provider>\n </AxesContext.Provider>\n </ThemeContext.Provider>\n </SwatchbookContext.Provider>\n );\n};\n\n/**\n * Named exports consumed by `definePreviewAddon(previewExports)` in the\n * addon's CSF Next factory (`src/index.ts`).\n */\nexport const decorators: NonNullable<Preview['decorators']> = [themedDecorator];\n\nexport const globalTypes: NonNullable<Preview['globalTypes']> = {\n [AXES_GLOBAL_KEY]: {\n name: 'Axes',\n description: 'Per-axis context selection — the active permutation tuple.',\n },\n [COLOR_FORMAT_GLOBAL_KEY]: {\n name: 'Color format',\n description: 'Display format for color tokens in blocks. Emitted CSS is unaffected.',\n },\n};\n\nfunction buildInitialAxes(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of virtualAxes) out[axis.name] = axis.default;\n return out;\n}\n\nexport const initialGlobals: NonNullable<Preview['initialGlobals']> = {\n [AXES_GLOBAL_KEY]: buildInitialAxes(),\n [COLOR_FORMAT_GLOBAL_KEY]: 'hex',\n};\n\n/**\n * Module-level channel subscription: writes the active tuple's attributes\n * onto `<html>` regardless of whether a story decorator is rendering.\n *\n * The {@link themedDecorator} already sets these inside story renders, but\n * it never runs on MDX docs pages that embed blocks without `<Story />`.\n * Without attrs on an ancestor, the per-tuple CSS selectors\n * (`[data-mode=\"Dark\"][data-brand=\"…\"]`) don't match and everything falls\n * back to the `:root` default tuple — so colors stay defaults even after\n * the toolbar switches axes. Subscribing globally fixes MDX docs at the\n * cost of one idempotent redundant write per story render.\n */\nfunction installGlobalAxisApplier(): void {\n if (typeof document === 'undefined') return;\n const channel = addons.getChannel();\n /**\n * Inject the stylesheet and emit the init payload once on module load so\n * the manager's toolbar populates and CSS vars are available even when no\n * story/decorator ever runs (bare MDX docs pages). Without these, the\n * toolbar sits in its disabled \"loading…\" state and nothing is styled.\n */\n ensureStylesheet(css, cssVarPrefix);\n broadcastInit();\n /**\n * If the manager subscribes to INIT_EVENT after our initial broadcast,\n * it misses the payload and the toolbar stays in its \"loading…\" state\n * until something else re-fires it. Honor an explicit request event so\n * a late-mounting manager can ask for the payload.\n */\n channel.on(INIT_REQUEST_EVENT, broadcastInit);\n const apply = (globals: SwatchbookGlobals): void => {\n ensureStylesheet(css, cssVarPrefix);\n const tuple = resolveTuple(globals[AXES_GLOBAL_KEY], undefined);\n setRootAxes(tuple);\n };\n // Storybook fires `globalsUpdated`, `setGlobals`, and `updateGlobals`\n // for the same logical change (preview init + every toolbar tick).\n // Subscribing to all three is intentional — `setGlobals` carries the\n // initial URL-persisted globals; `updateGlobals` is the toolbar\n // signal; `globalsUpdated` is the cross-frame echo. Apply the same\n // handler to all three but dedupe via a stringified-tuple guard so\n // downstream `setRootAxes` + `useSyncExternalStore` consumers\n // re-render at most once per real change instead of three times per\n // tick.\n let lastApplied = '';\n const onGlobals = (payload: { globals?: SwatchbookGlobals }): void => {\n if (!payload.globals) return;\n const tuple = resolveTuple(payload.globals[AXES_GLOBAL_KEY], undefined);\n const fingerprint = matchThemeName(tuple);\n if (fingerprint === lastApplied) return;\n lastApplied = fingerprint;\n apply(payload.globals);\n };\n channel.on('globalsUpdated', onGlobals);\n channel.on('setGlobals', onGlobals);\n channel.on('updateGlobals', onGlobals);\n}\n\ninstallGlobalAxisApplier();\n\n/**\n * Bridge `mousedown` inside the preview iframe to the manager via a\n * dedicated channel event. The toolbar popover's outside-click listener\n * runs on the manager's document, which can't observe mousedowns inside\n * the preview; without this bridge, clicking the canvas leaves the\n * popover open. Idempotent: fires at most once per real mousedown.\n */\nfunction installPreviewMouseDownBridge(): void {\n if (typeof document === 'undefined') return;\n const channel = addons.getChannel();\n document.addEventListener('mousedown', () => {\n channel.emit(PREVIEW_MOUSEDOWN_EVENT);\n });\n}\n\ninstallPreviewMouseDownBridge();\n\n/**\n * Wire the dev-time token-refresh HMR path. The plugin emits `HMR_EVENT`\n * with the fresh virtual-module payload whenever a watched source file\n * changes; we re-inject the stylesheet and forward to the Storybook\n * channel so the toolbar re-renders and blocks can re-subscribe with\n * the new snapshot — no full preview reload, so args / scroll / open\n * overlays survive the refresh. No-ops in production where\n * `import.meta.hot` is undefined.\n */\ninterface HmrSnapshot {\n axes: typeof virtualAxes;\n disabledAxes: typeof virtualDisabledAxes;\n presets: typeof virtualPresets;\n diagnostics: typeof diagnostics;\n css: string;\n cssVarPrefix: string;\n listing: typeof virtualListing;\n tokenGraph: typeof virtualTokenGraph;\n defaultTuple: typeof virtualDefaultTuple;\n}\nif (import.meta.hot) {\n import.meta.hot.on(HMR_EVENT, (payload: HmrSnapshot) => {\n ensureStylesheet(payload.css, payload.cssVarPrefix);\n const channel = addons.getChannel();\n channel.emit(INIT_EVENT, pickInitFields(payload));\n channel.emit(TOKENS_UPDATED_EVENT, payload);\n });\n}\n"],"mappings":"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"}
|
package/dist/preview.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { n as globalTypes, r as initialGlobals, t as decorators } from "./preview-
|
|
1
|
+
import { n as globalTypes, r as initialGlobals, t as decorators } from "./preview-EthytSmK.mjs";
|
|
2
2
|
export { decorators, globalTypes, initialGlobals };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@unpunnyfuns/swatchbook-addon",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.60.1",
|
|
4
4
|
"description": "Storybook addon for DTCG design tokens — toolbar, panel, and useToken hook.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "unpunnyfuns <unpunnyfuns@gmail.com>",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"dependencies": {
|
|
76
76
|
"jiti": "^2.4.0",
|
|
77
77
|
"picomatch": "^4.0.4",
|
|
78
|
-
"@unpunnyfuns/swatchbook-blocks": "0.
|
|
79
|
-
"@unpunnyfuns/swatchbook-core": "0.
|
|
80
|
-
"@unpunnyfuns/swatchbook-switcher": "0.
|
|
78
|
+
"@unpunnyfuns/swatchbook-blocks": "0.60.1",
|
|
79
|
+
"@unpunnyfuns/swatchbook-core": "0.60.1",
|
|
80
|
+
"@unpunnyfuns/swatchbook-switcher": "0.60.1"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
83
|
"@storybook/react-vite": "^10.3.5",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"preview-C3BkGw7M.mjs","names":["virtualDisabledAxes","virtualDefaultTuple","virtualAxes","virtualPresets","virtualCells","virtualJointOverrides","virtualVarianceByPath","defaultTuple","virtualListing"],"sources":["../src/preview.tsx"],"sourcesContent":["/// <reference types=\"vite/client\" />\nimport { buildResolveAt } from '@unpunnyfuns/swatchbook-core/resolve-at';\nimport type {\n Axis as CoreAxis,\n Cells as CoreCells,\n JointOverrides,\n} from '@unpunnyfuns/swatchbook-core';\nimport type { Decorator, Preview } from '@storybook/react-vite';\nimport type { CSSProperties } from 'react';\nimport { useEffect, useMemo, useRef, useState } from 'react';\nimport { addons } from 'storybook/preview-api';\nimport { dataAttr } from '@unpunnyfuns/swatchbook-core/data-attr';\nimport { ensureStyleElement } from '@unpunnyfuns/swatchbook-core/style-element';\nimport { tupleToName } from '@unpunnyfuns/swatchbook-core/themes';\n// Side-effect import for integrations that opted into `autoInject`\n// (e.g. Tailwind's `@theme` block). When no integration opts in, the\n// virtual module body is empty — still a valid no-op.\nimport 'virtual:swatchbook/integration-side-effects';\nimport {\n axes as virtualAxes,\n cells as virtualCells,\n css,\n cssVarPrefix,\n defaultTuple as virtualDefaultTuple,\n diagnostics,\n disabledAxes as virtualDisabledAxes,\n jointOverrides as virtualJointOverrides,\n listing as virtualListing,\n presets as virtualPresets,\n varianceByPath as virtualVarianceByPath,\n} from 'virtual:swatchbook/tokens';\nimport {\n AxesContext,\n COLOR_FORMATS,\n ColorFormatContext,\n SwatchbookContext,\n ThemeContext,\n} from '@unpunnyfuns/swatchbook-blocks';\nimport type { ColorFormat, ProjectSnapshot } from '@unpunnyfuns/swatchbook-blocks';\nimport {\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n HMR_EVENT,\n INIT_EVENT,\n INIT_REQUEST_EVENT,\n PREVIEW_MOUSEDOWN_EVENT,\n STYLE_ELEMENT_ID,\n TOKENS_UPDATED_EVENT,\n} from '#/constants.ts';\nimport type { StoryParameters, SwatchbookGlobals } from '#/globals.ts';\n\n/**\n * Standard visually-hidden style for the theme-flip live region.\n * Keeps the announcement element discoverable by SR but out of visual\n * + pointer flow. The clip-path / `position: absolute` combination is\n * the canonical sr-only pattern.\n */\nconst SR_ONLY_STYLE: CSSProperties = {\n position: 'absolute',\n width: 1,\n height: 1,\n padding: 0,\n margin: -1,\n overflow: 'hidden',\n clip: 'rect(0, 0, 0, 0)',\n whiteSpace: 'nowrap',\n border: 0,\n};\n\n/**\n * The `html, body { ... }` rules that paint the iframe's own chrome\n * (outside any decorator wrapper — Docs mode, autodocs, empty gutters)\n * with the active theme's surface + text vars. Composed alongside the\n * emitted token CSS so both load through the same `<style>` element.\n */\nfunction iframeChromeRules(prefix: string): string {\n const surface = prefix ? `--${prefix}-color-surface-default` : '--color-surface-default';\n const text = prefix ? `--${prefix}-color-text-default` : '--color-text-default';\n return `\nhtml, body {\n background: var(${surface}, Canvas);\n color: var(${text}, CanvasText);\n margin: 0;\n}\n`;\n}\n\n/**\n * Inject the per-theme stylesheet plus the iframe-chrome block. Shared\n * with the HMR re-emit path below so a token refresh updates the\n * iframe's chrome rules from the same source.\n */\nfunction ensureStylesheet(cssText: string, prefix: string): void {\n ensureStyleElement(STYLE_ELEMENT_ID, `${cssText}\\n${iframeChromeRules(prefix)}`);\n}\n\n/**\n * Apply `cb(axisName, value)` for every pinned (disabled) axis whose\n * default-tuple value is set. `virtualDefaultTuple` carries the\n * post-filter axis defaults; disabled axes don't appear in\n * `virtualAxes` but their pinned context value still lives here, so\n * sampling it gives the same result the old \"first permutation's\n * input\" lookup did.\n */\nfunction forEachPinnedAxis(cb: (name: string, value: string) => void): void {\n for (const name of virtualDisabledAxes) {\n const value = virtualDefaultTuple[name];\n if (value !== undefined) cb(name, value);\n }\n}\n\n/**\n * Compose a stable theme name from a tuple — `axisValues.join(' · ')`\n * in axis order. Used for the `ThemeContext` value the blocks read and\n * the addon-channel signals downstream consumers subscribe to. Returns\n * empty string when there are no axes (no name to write).\n */\nfunction matchThemeName(tuple: Readonly<Record<string, string>>): string {\n if (virtualAxes.length === 0) return '';\n return tupleToName(virtualAxes, tuple);\n}\n\n/**\n * Write one `data-<prefix>-<axis>=<context>` per axis on `<html>`.\n * The smart CSS emitter targets these single-axis selectors (and\n * joint compounds across multiple) — that's the actual scoping\n * surface the cascade resolves through. Prefix follows `cssVarPrefix`\n * so attr namespace and emitted selectors stay in lockstep.\n */\nfunction setRootAxes(tuple: Readonly<Record<string, string>>): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n for (const axis of virtualAxes) {\n const attr = dataAttr(cssVarPrefix, axis.name);\n const value = tuple[axis.name];\n if (value === undefined) {\n root.removeAttribute(attr);\n } else {\n root.setAttribute(attr, value);\n }\n }\n forEachPinnedAxis((name, value) => {\n root.setAttribute(dataAttr(cssVarPrefix, name), value);\n });\n}\n\n/**\n * Subset of an INIT_EVENT-shaped object the manager bundle needs. The 9\n * fields are the union of what the toolbar and panel read; named so\n * `broadcastInit` (module-level virtual exports) and the HMR re-emit\n * (`payload`-shaped) compose the same payload from the same shape.\n */\ninterface InitFieldsSource {\n axes: typeof virtualAxes;\n disabledAxes: typeof virtualDisabledAxes;\n presets: typeof virtualPresets;\n diagnostics: typeof diagnostics;\n cssVarPrefix: string;\n cells: typeof virtualCells;\n jointOverrides: typeof virtualJointOverrides;\n varianceByPath: typeof virtualVarianceByPath;\n defaultTuple: typeof virtualDefaultTuple;\n}\n\nfunction pickInitFields(source: InitFieldsSource): InitFieldsSource {\n return {\n axes: source.axes,\n disabledAxes: source.disabledAxes,\n presets: source.presets,\n diagnostics: source.diagnostics,\n cssVarPrefix: source.cssVarPrefix,\n cells: source.cells,\n jointOverrides: source.jointOverrides,\n varianceByPath: source.varianceByPath,\n defaultTuple: source.defaultTuple,\n };\n}\n\n/**\n * Emit the full virtual-module payload to the manager over Storybook's\n * channel so the toolbar + panel (which run in the manager bundle and\n * can't import our virtual module) can render from it.\n */\nfunction broadcastInit(): void {\n const channel = addons.getChannel();\n channel.emit(\n INIT_EVENT,\n pickInitFields({\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n diagnostics,\n cssVarPrefix,\n cells: virtualCells,\n jointOverrides: virtualJointOverrides,\n varianceByPath: virtualVarianceByPath,\n defaultTuple: virtualDefaultTuple,\n }),\n );\n}\n\n/** Axis-default tuple, used as the baseline before overrides. */\nfunction defaultTuple(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of virtualAxes) out[axis.name] = axis.default;\n return out;\n}\n\n/**\n * Reverse-engineer a tuple from a `Light · Brand A · Normal`-shape\n * theme name. Splits on ` · ` and zips with `virtualAxes` in declared\n * order — matches `matchThemeName`'s production direction so a\n * round-trip is lossless. Returns `undefined` when the segment count\n * doesn't match the axis count.\n */\nfunction tupleForName(name: string): Record<string, string> | undefined {\n if (!name) return undefined;\n const parts = name.split(' · ');\n if (parts.length !== virtualAxes.length) return undefined;\n const out: Record<string, string> = {};\n for (let i = 0; i < virtualAxes.length; i++) {\n const axis = virtualAxes[i] as (typeof virtualAxes)[number];\n const value = parts[i];\n if (value === undefined) return undefined;\n out[axis.name] = value;\n }\n return out;\n}\n\n/**\n * Merge a partial tuple onto the axis defaults, dropping keys for axes that\n * don't exist and silently falling back to the default for contexts that\n * aren't listed on the axis.\n */\nfunction normalizeTuple(partial: Readonly<Record<string, string>>): Record<string, string> {\n const out = defaultTuple();\n for (const axis of virtualAxes) {\n const candidate = partial[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n return out;\n}\n\n/**\n * Resolve the active tuple from all input channels, in priority order:\n * 1. `parameters.swatchbook.axes` — per-story tuple.\n * 2. `parameters.swatchbook.permutation` — per-story composed name.\n * 3. `globals.swatchbookAxes` — toolbar-set tuple.\n * 4. virtual module default.\n */\nfunction resolveTuple(\n axesGlobal: SwatchbookGlobals[typeof AXES_GLOBAL_KEY],\n paramSwatchbook: StoryParameters['swatchbook'],\n): Record<string, string> {\n const paramAxes = paramSwatchbook?.axes;\n if (paramAxes) {\n return normalizeTuple(paramAxes);\n }\n if (paramSwatchbook?.permutation) {\n const hit = tupleForName(paramSwatchbook.permutation);\n if (hit) return normalizeTuple(hit);\n }\n if (axesGlobal && typeof axesGlobal === 'object') {\n return normalizeTuple(axesGlobal as Record<string, string>);\n }\n return defaultTuple();\n}\n\nfunction resolveColorFormat(raw: SwatchbookGlobals[typeof COLOR_FORMAT_GLOBAL_KEY]): ColorFormat {\n if (typeof raw === 'string' && (COLOR_FORMATS as readonly string[]).includes(raw)) {\n return raw as ColorFormat;\n }\n return 'hex';\n}\n\n/**\n * Single shared `resolveAt` instance for the lifetime of the preview\n * iframe. The inputs (`virtualAxes`, `virtualCells`, …) are all\n * module-level virtual-module exports with stable identity, so this\n * never needs to rebuild; downstream `ProjectSnapshot` consumers can\n * key memos on the snapshot wrapper without worrying about\n * `resolveAt` churning when Storybook recreates `context.globals`.\n */\nconst previewResolveAt = buildResolveAt(\n virtualAxes as readonly CoreAxis[],\n virtualCells as CoreCells,\n virtualJointOverrides as JointOverrides,\n virtualDefaultTuple,\n);\n\nconst themedDecorator: Decorator = (Story, context) => {\n const globals = context.globals as SwatchbookGlobals;\n const parameters = context.parameters as StoryParameters;\n const axesGlobal = globals[AXES_GLOBAL_KEY];\n const colorFormatGlobal = globals[COLOR_FORMAT_GLOBAL_KEY];\n const paramSwatchbook = parameters.swatchbook;\n const tuple = useMemo(\n () => resolveTuple(axesGlobal, paramSwatchbook),\n [axesGlobal, paramSwatchbook],\n );\n const colorFormat = useMemo(() => resolveColorFormat(colorFormatGlobal), [colorFormatGlobal]);\n const themeName = useMemo(() => matchThemeName(tuple), [tuple]);\n\n useEffect(() => {\n ensureStylesheet(css, cssVarPrefix);\n broadcastInit();\n }, []);\n\n useEffect(() => {\n setRootAxes(tuple);\n }, [tuple]);\n\n // Page-level live region announces theme/axis flips to SR users.\n // Initial mount stays silent (no spurious announcement on every story\n // load); subsequent `themeName` changes schedule a debounced update so\n // rapid axis flips (or per-story tuple overrides while paging through\n // a Storybook docs index) collapse into one announcement.\n const [announcement, setAnnouncement] = useState('');\n const initialThemeRef = useRef(themeName);\n useEffect(() => {\n if (themeName === initialThemeRef.current) return;\n const timer = setTimeout(() => {\n setAnnouncement(themeName ? `Theme: ${themeName}` : '');\n }, 250);\n return () => {\n clearTimeout(timer);\n };\n }, [themeName]);\n\n const wrapperAttrs: Record<string, string> = {};\n for (const axis of virtualAxes) {\n const value = tuple[axis.name];\n if (value !== undefined) wrapperAttrs[dataAttr(cssVarPrefix, axis.name)] = value;\n }\n forEachPinnedAxis((name, value) => {\n wrapperAttrs[dataAttr(cssVarPrefix, name)] = value;\n });\n\n const snapshot = useMemo<ProjectSnapshot>(\n () => ({\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n activeTheme: themeName,\n activeAxes: tuple,\n cssVarPrefix,\n diagnostics,\n css,\n listing: virtualListing,\n cells: virtualCells,\n jointOverrides: virtualJointOverrides,\n varianceByPath: virtualVarianceByPath,\n defaultTuple: virtualDefaultTuple,\n resolveAt: previewResolveAt,\n }),\n [themeName, tuple],\n );\n\n return (\n <SwatchbookContext.Provider value={snapshot}>\n <ThemeContext.Provider value={themeName}>\n <AxesContext.Provider value={tuple}>\n <ColorFormatContext.Provider value={colorFormat}>\n <div\n {...wrapperAttrs}\n style={{\n padding: '1rem',\n minHeight: '100%',\n }}\n >\n <Story />\n </div>\n <div role=\"status\" aria-live=\"polite\" style={SR_ONLY_STYLE}>\n {announcement}\n </div>\n </ColorFormatContext.Provider>\n </AxesContext.Provider>\n </ThemeContext.Provider>\n </SwatchbookContext.Provider>\n );\n};\n\n/**\n * Named exports consumed by `definePreviewAddon(previewExports)` in the\n * addon's CSF Next factory (`src/index.ts`).\n */\nexport const decorators: NonNullable<Preview['decorators']> = [themedDecorator];\n\nexport const globalTypes: NonNullable<Preview['globalTypes']> = {\n [AXES_GLOBAL_KEY]: {\n name: 'Axes',\n description: 'Per-axis context selection — the active permutation tuple.',\n },\n [COLOR_FORMAT_GLOBAL_KEY]: {\n name: 'Color format',\n description: 'Display format for color tokens in blocks. Emitted CSS is unaffected.',\n },\n};\n\nfunction buildInitialAxes(): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of virtualAxes) out[axis.name] = axis.default;\n return out;\n}\n\nexport const initialGlobals: NonNullable<Preview['initialGlobals']> = {\n [AXES_GLOBAL_KEY]: buildInitialAxes(),\n [COLOR_FORMAT_GLOBAL_KEY]: 'hex',\n};\n\n/**\n * Module-level channel subscription: writes the active tuple's attributes\n * onto `<html>` regardless of whether a story decorator is rendering.\n *\n * The {@link themedDecorator} already sets these inside story renders, but\n * it never runs on MDX docs pages that embed blocks without `<Story />`.\n * Without attrs on an ancestor, the per-tuple CSS selectors\n * (`[data-mode=\"Dark\"][data-brand=\"…\"]`) don't match and everything falls\n * back to the `:root` default tuple — so colors stay defaults even after\n * the toolbar switches axes. Subscribing globally fixes MDX docs at the\n * cost of one idempotent redundant write per story render.\n */\nfunction installGlobalAxisApplier(): void {\n if (typeof document === 'undefined') return;\n const channel = addons.getChannel();\n /**\n * Inject the stylesheet and emit the init payload once on module load so\n * the manager's toolbar populates and CSS vars are available even when no\n * story/decorator ever runs (bare MDX docs pages). Without these, the\n * toolbar sits in its disabled \"loading…\" state and nothing is styled.\n */\n ensureStylesheet(css, cssVarPrefix);\n broadcastInit();\n /**\n * If the manager subscribes to INIT_EVENT after our initial broadcast,\n * it misses the payload and the toolbar stays in its \"loading…\" state\n * until something else re-fires it. Honor an explicit request event so\n * a late-mounting manager can ask for the payload.\n */\n channel.on(INIT_REQUEST_EVENT, broadcastInit);\n const apply = (globals: SwatchbookGlobals): void => {\n ensureStylesheet(css, cssVarPrefix);\n const tuple = resolveTuple(globals[AXES_GLOBAL_KEY], undefined);\n setRootAxes(tuple);\n };\n // Storybook fires `globalsUpdated`, `setGlobals`, and `updateGlobals`\n // for the same logical change (preview init + every toolbar tick).\n // Subscribing to all three is intentional — `setGlobals` carries the\n // initial URL-persisted globals; `updateGlobals` is the toolbar\n // signal; `globalsUpdated` is the cross-frame echo. Apply the same\n // handler to all three but dedupe via a stringified-tuple guard so\n // downstream `setRootAxes` + `useSyncExternalStore` consumers\n // re-render at most once per real change instead of three times per\n // tick.\n let lastApplied = '';\n const onGlobals = (payload: { globals?: SwatchbookGlobals }): void => {\n if (!payload.globals) return;\n const tuple = resolveTuple(payload.globals[AXES_GLOBAL_KEY], undefined);\n const fingerprint = matchThemeName(tuple);\n if (fingerprint === lastApplied) return;\n lastApplied = fingerprint;\n apply(payload.globals);\n };\n channel.on('globalsUpdated', onGlobals);\n channel.on('setGlobals', onGlobals);\n channel.on('updateGlobals', onGlobals);\n}\n\ninstallGlobalAxisApplier();\n\n/**\n * Bridge `mousedown` inside the preview iframe to the manager via a\n * dedicated channel event. The toolbar popover's outside-click listener\n * runs on the manager's document, which can't observe mousedowns inside\n * the preview; without this bridge, clicking the canvas leaves the\n * popover open. Idempotent: fires at most once per real mousedown.\n */\nfunction installPreviewMouseDownBridge(): void {\n if (typeof document === 'undefined') return;\n const channel = addons.getChannel();\n document.addEventListener('mousedown', () => {\n channel.emit(PREVIEW_MOUSEDOWN_EVENT);\n });\n}\n\ninstallPreviewMouseDownBridge();\n\n/**\n * Wire the dev-time token-refresh HMR path. The plugin emits `HMR_EVENT`\n * with the fresh virtual-module payload whenever a watched source file\n * changes; we re-inject the stylesheet and forward to the Storybook\n * channel so the toolbar re-renders and blocks can re-subscribe with\n * the new snapshot — no full preview reload, so args / scroll / open\n * overlays survive the refresh. No-ops in production where\n * `import.meta.hot` is undefined.\n */\ninterface HmrSnapshot {\n axes: typeof virtualAxes;\n disabledAxes: typeof virtualDisabledAxes;\n presets: typeof virtualPresets;\n diagnostics: typeof diagnostics;\n css: string;\n cssVarPrefix: string;\n listing: typeof virtualListing;\n cells: typeof virtualCells;\n jointOverrides: typeof virtualJointOverrides;\n varianceByPath: typeof virtualVarianceByPath;\n defaultTuple: typeof virtualDefaultTuple;\n}\nif (import.meta.hot) {\n import.meta.hot.on(HMR_EVENT, (payload: HmrSnapshot) => {\n ensureStylesheet(payload.css, payload.cssVarPrefix);\n const channel = addons.getChannel();\n channel.emit(INIT_EVENT, pickInitFields(payload));\n channel.emit(TOKENS_UPDATED_EVENT, payload);\n });\n}\n"],"mappings":"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"}
|