@unpunnyfuns/swatchbook-addon 0.20.5 → 0.51.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,7 @@
2
2
  const ADDON_ID = "swatchbook";
3
3
  const TOOL_ID = `${ADDON_ID}/theme-switcher`;
4
4
  const PARAM_KEY = "swatchbook";
5
- /** Canonical active-theme global: composed permutation ID (string). Read by toolbar, panel, blocks. */
6
- const GLOBAL_KEY = "swatchbookTheme";
7
- /** Tuple companion: `Record<axisName, contextName>`. Optional — when present, takes precedence over the string global. */
5
+ /** Canonical active-permutation tuple: `Record<axisName, contextName>`. Read by toolbar, panel, blocks. */
8
6
  const AXES_GLOBAL_KEY = "swatchbookAxes";
9
7
  /** Display-only color format for blocks (`hex` | `rgb` | `hsl` | `oklch` | `raw`). Emitted CSS is unaffected. */
10
8
  const COLOR_FORMAT_GLOBAL_KEY = "swatchbookColorFormat";
@@ -45,6 +43,6 @@ const TOKENS_UPDATED_EVENT = "swatchbook/tokens-updated";
45
43
  * plugin doesn't need a Storybook-channel dependency. */
46
44
  const HMR_EVENT = "swatchbook/tokens-updated";
47
45
  //#endregion
48
- export { HMR_EVENT as a, INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID as c, RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID as d, RESOLVED_VIRTUAL_MODULE_ID as f, VIRTUAL_MODULE_ID as g, TOOL_ID as h, GLOBAL_KEY as i, PARAM_KEY as l, TOKENS_UPDATED_EVENT as m, AXES_GLOBAL_KEY as n, INIT_EVENT as o, STYLE_ELEMENT_ID as p, COLOR_FORMAT_GLOBAL_KEY as r, INIT_REQUEST_EVENT as s, ADDON_ID as t, PREVIEW_MOUSEDOWN_EVENT as u };
46
+ export { INIT_EVENT as a, PARAM_KEY as c, RESOLVED_VIRTUAL_MODULE_ID as d, STYLE_ELEMENT_ID as f, VIRTUAL_MODULE_ID as h, HMR_EVENT as i, PREVIEW_MOUSEDOWN_EVENT as l, TOOL_ID as m, AXES_GLOBAL_KEY as n, INIT_REQUEST_EVENT as o, TOKENS_UPDATED_EVENT as p, COLOR_FORMAT_GLOBAL_KEY as r, INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID as s, ADDON_ID as t, RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID as u };
49
47
 
50
- //# sourceMappingURL=constants-DhgRF7SI.mjs.map
48
+ //# sourceMappingURL=constants-B31xFInv.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants-B31xFInv.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const ADDON_ID = 'swatchbook';\nexport const TOOL_ID = `${ADDON_ID}/theme-switcher`;\nexport const PARAM_KEY = 'swatchbook';\n/** Canonical active-permutation tuple: `Record<axisName, contextName>`. Read by toolbar, panel, blocks. */\nexport const AXES_GLOBAL_KEY = 'swatchbookAxes';\n/** Display-only color format for blocks (`hex` | `rgb` | `hsl` | `oklch` | `raw`). Emitted CSS is unaffected. */\nexport const COLOR_FORMAT_GLOBAL_KEY = 'swatchbookColorFormat';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:swatchbook/tokens';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\n/**\n * Aggregate virtual module the addon's preview always imports. Its body\n * is a sequence of side-effect imports — one per integration that\n * declared `virtualModule.autoInject: true`. Integrations contributing\n * global stylesheets (Tailwind's `@theme` block, a rules-heavy CSS\n * file) can opt into this path so consumers never hand-write an\n * `import 'virtual:swatchbook/…'` line themselves; the body is empty\n * when no integration opts in.\n */\nexport const INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID = 'virtual:swatchbook/integration-side-effects';\nexport const RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID = `\\0${INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID}`;\n\nexport const STYLE_ELEMENT_ID = 'swatchbook-tokens';\n\n/** Channel event: preview → manager, carries theme list + mode. */\nexport const INIT_EVENT = 'swatchbook/init';\n/** Channel event: manager → preview, asks preview to re-emit INIT_EVENT.\n * Covers the race where the manager subscribes after the preview's\n * initial broadcast — without it the toolbar stays in \"loading…\" until\n * the user triggers anything that re-fires INIT_EVENT. */\nexport const INIT_REQUEST_EVENT = 'swatchbook/init-request';\n/** Channel event: preview → manager, fires once per `mousedown` on the\n * preview document. The toolbar popover listens for it so clicks landing\n * inside the preview iframe close the popover — a plain document-level\n * listener on the manager can't see iframe events. */\nexport const PREVIEW_MOUSEDOWN_EVENT = 'swatchbook/preview-mousedown';\n\n/** Channel event: preview → blocks, carries the fresh virtual-module\n * payload after a dev-time token refresh so blocks can re-render in\n * place without a full iframe reload. Fired by the preview in response\n * to the `HMR_EVENT` below. */\nexport const TOKENS_UPDATED_EVENT = 'swatchbook/tokens-updated';\n\n/** Custom Vite HMR event: plugin → preview. Preview forwards it to the\n * Storybook channel as {@link TOKENS_UPDATED_EVENT} so blocks can\n * update their snapshot. Kept distinct from the channel event so the\n * plugin doesn't need a Storybook-channel dependency. */\nexport const HMR_EVENT = 'swatchbook/tokens-updated';\n"],"mappings":";AAAA,MAAa,WAAW;AACxB,MAAa,UAAU,GAAG,SAAS;AACnC,MAAa,YAAY;;AAEzB,MAAa,kBAAkB;;AAE/B,MAAa,0BAA0B;AAEvC,MAAa,oBAAoB;AACjC,MAAa,6BAA6B,KAAK;;;;;;;;;;AAW/C,MAAa,sCAAsC;AACnD,MAAa,+CAA+C,KAAK;AAEjE,MAAa,mBAAmB;;AAGhC,MAAa,aAAa;;;;;AAK1B,MAAa,qBAAqB;;;;;AAKlC,MAAa,0BAA0B;;;;;AAMvC,MAAa,uBAAuB;;;;;AAMpC,MAAa,YAAY"}
@@ -21,7 +21,7 @@ interface TokenInfo {
21
21
  }
22
22
  /**
23
23
  * Read a DTCG token for the currently active theme. Re-reads on theme
24
- * switch via the addon's `ThemeContext`. Returns `{ value, cssVar, type,
24
+ * switch via the addon's `PermutationContext`. Returns `{ value, cssVar, type,
25
25
  * description }`.
26
26
  *
27
27
  * Typed paths appear automatically once `.swatchbook/tokens.d.ts` is
@@ -1,5 +1,5 @@
1
- import { cssVarPrefix, defaultTheme, themesResolved } from "virtual:swatchbook/tokens";
2
- import { useActiveTheme } from "@unpunnyfuns/swatchbook-blocks";
1
+ import { cssVarPrefix, defaultPermutation, permutationsResolved } from "virtual:swatchbook/tokens";
2
+ import { useActivePermutation } from "@unpunnyfuns/swatchbook-blocks";
3
3
  //#region src/hooks/use-token.ts
4
4
  function makeCssVar(path, prefix) {
5
5
  const tail = path.replaceAll(".", "-");
@@ -7,7 +7,7 @@ function makeCssVar(path, prefix) {
7
7
  }
8
8
  /**
9
9
  * Read a DTCG token for the currently active theme. Re-reads on theme
10
- * switch via the addon's `ThemeContext`. Returns `{ value, cssVar, type,
10
+ * switch via the addon's `PermutationContext`. Returns `{ value, cssVar, type,
11
11
  * description }`.
12
12
  *
13
13
  * Typed paths appear automatically once `.swatchbook/tokens.d.ts` is
@@ -18,7 +18,7 @@ function makeCssVar(path, prefix) {
18
18
  * Storybook's preview-only hooks.
19
19
  */
20
20
  function useToken(path) {
21
- const token = (themesResolved[useActiveTheme() || (defaultTheme ?? "")] ?? {})[path];
21
+ const token = (permutationsResolved[useActivePermutation() || (defaultPermutation ?? "")] ?? {})[path];
22
22
  const info = {
23
23
  value: token?.$value,
24
24
  cssVar: makeCssVar(path, cssVarPrefix)
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/hooks/use-token.ts"],"sourcesContent":["import { cssVarPrefix, defaultTheme, themesResolved } from 'virtual:swatchbook/tokens';\nimport { useActiveTheme } from '@unpunnyfuns/swatchbook-blocks';\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\nfunction makeCssVar(path: string, prefix: string): string {\n const tail = path.replaceAll('.', '-');\n return prefix ? `var(--${prefix}-${tail})` : `var(--${tail})`;\n}\n\n/**\n * Read a DTCG token for the currently active theme. Re-reads on theme\n * switch via the addon's `ThemeContext`. Returns `{ value, cssVar, type,\n * 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.\n */\nexport function useToken(path: TokenPath): TokenInfo {\n const contextTheme = useActiveTheme();\n const themeName = contextTheme || (defaultTheme ?? '');\n const tokens = themesResolved[themeName] ?? {};\n const token = tokens[path];\n const info: TokenInfo = {\n value: token?.$value,\n cssVar: makeCssVar(path, cssVarPrefix),\n };\n if (token?.$type) info.type = token.$type;\n if (token?.$description) info.description = token.$description;\n return info;\n}\n"],"mappings":";;;AA2BA,SAAS,WAAW,MAAc,QAAwB;CACxD,MAAM,OAAO,KAAK,WAAW,KAAK,IAAI;AACtC,QAAO,SAAS,SAAS,OAAO,GAAG,KAAK,KAAK,SAAS,KAAK;;;;;;;;;;;;;;AAe7D,SAAgB,SAAS,MAA4B;CAInD,MAAM,SADS,eAFM,gBAAgB,KACF,gBAAgB,QACP,EAAE,EACzB;CACrB,MAAM,OAAkB;EACtB,OAAO,OAAO;EACd,QAAQ,WAAW,MAAM,aAAa;EACvC;AACD,KAAI,OAAO,MAAO,MAAK,OAAO,MAAM;AACpC,KAAI,OAAO,aAAc,MAAK,cAAc,MAAM;AAClD,QAAO"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/hooks/use-token.ts"],"sourcesContent":["import { cssVarPrefix, defaultPermutation, permutationsResolved } from 'virtual:swatchbook/tokens';\nimport { useActivePermutation } from '@unpunnyfuns/swatchbook-blocks';\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\nfunction makeCssVar(path: string, prefix: string): string {\n const tail = path.replaceAll('.', '-');\n return prefix ? `var(--${prefix}-${tail})` : `var(--${tail})`;\n}\n\n/**\n * Read a DTCG token for the currently active theme. Re-reads on theme\n * switch via the addon's `PermutationContext`. Returns `{ value, cssVar, type,\n * 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.\n */\nexport function useToken(path: TokenPath): TokenInfo {\n const contextPermutation = useActivePermutation();\n const permutationName = contextPermutation || (defaultPermutation ?? '');\n const tokens = permutationsResolved[permutationName] ?? {};\n const token = tokens[path];\n const info: TokenInfo = {\n value: token?.$value,\n cssVar: makeCssVar(path, cssVarPrefix),\n };\n if (token?.$type) info.type = token.$type;\n if (token?.$description) info.description = token.$description;\n return info;\n}\n"],"mappings":";;;AA2BA,SAAS,WAAW,MAAc,QAAwB;CACxD,MAAM,OAAO,KAAK,WAAW,KAAK,IAAI;AACtC,QAAO,SAAS,SAAS,OAAO,GAAG,KAAK,KAAK,SAAS,KAAK;;;;;;;;;;;;;;AAe7D,SAAgB,SAAS,MAA4B;CAInD,MAAM,SADS,qBAFY,sBAAsB,KACF,sBAAsB,QACb,EAAE,EACrC;CACrB,MAAM,OAAkB;EACtB,OAAO,OAAO;EACd,QAAQ,WAAW,MAAM,aAAa;EACvC;AACD,KAAI,OAAO,MAAO,MAAK,OAAO,MAAM;AACpC,KAAI,OAAO,aAAc,MAAK,cAAc,MAAM;AAClD,QAAO"}
package/dist/index.d.mts CHANGED
@@ -6,9 +6,7 @@ export * from "@unpunnyfuns/swatchbook-switcher";
6
6
  //#region src/constants.d.ts
7
7
  declare const ADDON_ID = "swatchbook";
8
8
  declare const PARAM_KEY = "swatchbook";
9
- /** Canonical active-theme global: composed permutation ID (string). Read by toolbar, panel, blocks. */
10
- declare const GLOBAL_KEY = "swatchbookTheme";
11
- /** Tuple companion: `Record<axisName, contextName>`. Optional — when present, takes precedence over the string global. */
9
+ /** Canonical active-permutation tuple: `Record<axisName, contextName>`. Read by toolbar, panel, blocks. */
12
10
  declare const AXES_GLOBAL_KEY = "swatchbookAxes";
13
11
  declare const VIRTUAL_MODULE_ID = "virtual:swatchbook/tokens";
14
12
  //#endregion
@@ -20,5 +18,5 @@ declare const VIRTUAL_MODULE_ID = "virtual:swatchbook/tokens";
20
18
  */
21
19
  declare function swatchbookAddon(): ReturnType<typeof definePreviewAddon>;
22
20
  //#endregion
23
- export { ADDON_ID, AXES_GLOBAL_KEY, type AddonOptions, GLOBAL_KEY, PARAM_KEY, VIRTUAL_MODULE_ID, swatchbookAddon as default };
21
+ export { ADDON_ID, AXES_GLOBAL_KEY, type AddonOptions, PARAM_KEY, VIRTUAL_MODULE_ID, swatchbookAddon as default };
24
22
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { i as preview_exports } from "./preview-D-mlU5Ip.mjs";
2
- import { g as VIRTUAL_MODULE_ID, i as GLOBAL_KEY, l as PARAM_KEY, n as AXES_GLOBAL_KEY, t as ADDON_ID } from "./constants-DhgRF7SI.mjs";
1
+ import { i as preview_exports } from "./preview-CFEfgrWb.mjs";
2
+ import { c as PARAM_KEY, h as VIRTUAL_MODULE_ID, n as AXES_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";
5
5
  export * from "@unpunnyfuns/swatchbook-switcher";
@@ -13,6 +13,6 @@ function swatchbookAddon() {
13
13
  return definePreviewAddon(preview_exports);
14
14
  }
15
15
  //#endregion
16
- export { ADDON_ID, AXES_GLOBAL_KEY, GLOBAL_KEY, PARAM_KEY, VIRTUAL_MODULE_ID, swatchbookAddon as default };
16
+ export { ADDON_ID, AXES_GLOBAL_KEY, PARAM_KEY, VIRTUAL_MODULE_ID, swatchbookAddon as default };
17
17
 
18
18
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["previewExports"],"sources":["../src/index.ts"],"sourcesContent":["import { definePreviewAddon } from 'storybook/internal/csf';\nimport * as previewExports from '#/preview.tsx';\n\nexport {\n ADDON_ID,\n AXES_GLOBAL_KEY,\n GLOBAL_KEY,\n PARAM_KEY,\n VIRTUAL_MODULE_ID,\n} from '#/constants.ts';\nexport type { AddonOptions } from '#/options.ts';\n\n/**\n * Re-export the full user-facing surface from `@unpunnyfuns/swatchbook-blocks`\n * and `@unpunnyfuns/swatchbook-switcher` so consumers can\n * `import { TokenTable, ThemeSwitcher, useToken } from '@unpunnyfuns/swatchbook-addon'`\n * without adding the sibling packages to their own package.json. Both are\n * declared as regular dependencies of the addon, so they come along for the\n * ride. Subpath entries (`./preset`, `./manager`, `./preview`, `./hooks`)\n * still exist for Storybook's preset loader — this meta re-export is just\n * for the React / MDX consumer path.\n */\nexport * from '@unpunnyfuns/swatchbook-blocks';\nexport * from '@unpunnyfuns/swatchbook-switcher';\n\n/**\n * CSF Next factory. Consumers call this inside\n * `definePreview({ addons: [swatchbookAddon()] })` so the preview annotations\n * (decorator, globalTypes, initialGlobals) are added to the preview bundle.\n */\nexport default function swatchbookAddon(): ReturnType<typeof definePreviewAddon> {\n return definePreviewAddon(previewExports);\n}\n"],"mappings":";;;;;;;;;;;AA8BA,SAAwB,kBAAyD;AAC/E,QAAO,mBAAmBA,gBAAe"}
1
+ {"version":3,"file":"index.mjs","names":["previewExports"],"sources":["../src/index.ts"],"sourcesContent":["import { definePreviewAddon } from 'storybook/internal/csf';\nimport * as previewExports from '#/preview.tsx';\n\nexport { ADDON_ID, AXES_GLOBAL_KEY, PARAM_KEY, VIRTUAL_MODULE_ID } from '#/constants.ts';\nexport type { AddonOptions } from '#/options.ts';\n\n/**\n * Re-export the full user-facing surface from `@unpunnyfuns/swatchbook-blocks`\n * and `@unpunnyfuns/swatchbook-switcher` so consumers can\n * `import { TokenTable, ThemeSwitcher, useToken } from '@unpunnyfuns/swatchbook-addon'`\n * without adding the sibling packages to their own package.json. Both are\n * declared as regular dependencies of the addon, so they come along for the\n * ride. Subpath entries (`./preset`, `./manager`, `./preview`, `./hooks`)\n * still exist for Storybook's preset loader — this meta re-export is just\n * for the React / MDX consumer path.\n */\nexport * from '@unpunnyfuns/swatchbook-blocks';\nexport * from '@unpunnyfuns/swatchbook-switcher';\n\n/**\n * CSF Next factory. Consumers call this inside\n * `definePreview({ addons: [swatchbookAddon()] })` so the preview annotations\n * (decorator, globalTypes, initialGlobals) are added to the preview bundle.\n */\nexport default function swatchbookAddon(): ReturnType<typeof definePreviewAddon> {\n return definePreviewAddon(previewExports);\n}\n"],"mappings":";;;;;;;;;;;AAwBA,SAAwB,kBAAyD;AAC/E,QAAO,mBAAmBA,gBAAe"}
package/dist/manager.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { h as TOOL_ID, i as GLOBAL_KEY, n as AXES_GLOBAL_KEY, o as INIT_EVENT, r as COLOR_FORMAT_GLOBAL_KEY, s as INIT_REQUEST_EVENT, t as ADDON_ID, u as PREVIEW_MOUSEDOWN_EVENT } from "./constants-DhgRF7SI.mjs";
1
+ import { a as INIT_EVENT, l as PREVIEW_MOUSEDOWN_EVENT, m as TOOL_ID, n as AXES_GLOBAL_KEY, o as INIT_REQUEST_EVENT, r as COLOR_FORMAT_GLOBAL_KEY, t as ADDON_ID } from "./constants-B31xFInv.mjs";
2
2
  import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
3
  import { ThemeSwitcher } from "@unpunnyfuns/swatchbook-switcher";
4
4
  import { IconButton, WithTooltipPure } from "storybook/internal/components";
@@ -53,7 +53,7 @@ function ColorFormatSelector({ active, onSelect }) {
53
53
  const h = React.createElement;
54
54
  const EMPTY_AXES = [];
55
55
  const EMPTY_PRESETS = [];
56
- const EMPTY_THEMES = [];
56
+ const EMPTY_PERMUTATIONS = [];
57
57
  /**
58
58
  * Root toolbar glyph — a split-circle ("yinyang") mark: a faint filled
59
59
  * disc for the full-swatch silhouette, with a darker half-and-inset-disc
@@ -76,14 +76,6 @@ function SwatchbookIcon() {
76
76
  fill: "currentColor"
77
77
  }));
78
78
  }
79
- function tupleMatchesInput(tuple, input) {
80
- const keys = Object.keys(input);
81
- if (keys.length === 0) return false;
82
- return keys.every((k) => input[k] === tuple[k]);
83
- }
84
- function composedNameFor(tuple, themes, fallback) {
85
- return themes.find((t) => tupleMatchesInput(tuple, t.input))?.name ?? fallback;
86
- }
87
79
  function defaultTupleFor(axes) {
88
80
  const out = {};
89
81
  for (const axis of axes) out[axis.name] = axis.default;
@@ -126,7 +118,7 @@ function AxesToolbar() {
126
118
  }, []);
127
119
  const axes = payload?.axes ?? EMPTY_AXES;
128
120
  const presets = payload?.presets ?? EMPTY_PRESETS;
129
- const themes = payload?.themes ?? EMPTY_THEMES;
121
+ const permutations = payload?.permutations ?? EMPTY_PERMUTATIONS;
130
122
  const defaults = useMemo(() => defaultTupleFor(axes), [axes]);
131
123
  const [lastApplied, setLastApplied] = useState(null);
132
124
  const globalTuple = globals[AXES_GLOBAL_KEY];
@@ -148,30 +140,15 @@ function AxesToolbar() {
148
140
  ...activeTuple,
149
141
  [axisName]: next
150
142
  };
151
- const composed = composedNameFor(tuple, themes, payload?.defaultTheme ?? themes[0]?.name ?? "");
152
- updateGlobals({
153
- [AXES_GLOBAL_KEY]: tuple,
154
- [GLOBAL_KEY]: composed
155
- });
156
- }, [
157
- activeTuple,
158
- themes,
159
- payload?.defaultTheme,
160
- updateGlobals
161
- ]);
143
+ updateGlobals({ [AXES_GLOBAL_KEY]: tuple });
144
+ }, [activeTuple, updateGlobals]);
162
145
  const applyPreset = useCallback((preset) => {
163
146
  const tuple = presetTuple(preset, axes, defaults);
164
- const composed = composedNameFor(tuple, themes, payload?.defaultTheme ?? themes[0]?.name ?? "");
165
- updateGlobals({
166
- [AXES_GLOBAL_KEY]: tuple,
167
- [GLOBAL_KEY]: composed
168
- });
147
+ updateGlobals({ [AXES_GLOBAL_KEY]: tuple });
169
148
  setLastApplied(preset.name);
170
149
  }, [
171
150
  axes,
172
151
  defaults,
173
- themes,
174
- payload?.defaultTheme,
175
152
  updateGlobals
176
153
  ]);
177
154
  useEffect(() => {
@@ -259,7 +236,7 @@ function AxesToolbar() {
259
236
  const tooltipBody = h(ThemeSwitcher, {
260
237
  axes,
261
238
  presets,
262
- themes,
239
+ permutations,
263
240
  activeTuple,
264
241
  defaults,
265
242
  lastApplied,
@@ -1 +1 @@
1
- {"version":3,"file":"manager.mjs","names":["h"],"sources":["../src/ColorFormatSelector.tsx","../src/manager.tsx"],"sourcesContent":["import React, { type ReactElement } from 'react';\n\n/**\n * Storybook-addon-specific pill row for picking how color sub-values\n * render in swatchbook blocks (hex / rgb / hsl / oklch / raw). Lives in\n * the addon rather than the shared switcher because color format is a\n * blocks-rendering concern, not a theming one — the docs-site navbar\n * switcher has no consumer for it.\n *\n * Reuses the `sb-switcher__*` class names so styling stays consistent\n * with the rest of the toolbar popover. The switcher's CSS is already\n * loaded on the page because this selector only renders inside\n * `<ThemeSwitcher>`'s `footer` prop.\n *\n * Uses `React.createElement` (via `h`) to survive embedding in Storybook's\n * manager bundle, which doesn't expose `react/jsx-runtime`.\n */\n\nexport type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'oklch' | 'raw';\n\nconst COLOR_FORMAT_OPTIONS: readonly { id: ColorFormat; label: string }[] = [\n { id: 'hex', label: 'Hex' },\n { id: 'rgb', label: 'RGB' },\n { id: 'hsl', label: 'HSL' },\n { id: 'oklch', label: 'OKLCH' },\n { id: 'raw', label: 'Raw (JSON)' },\n];\n\nconst h = React.createElement;\n\nexport interface ColorFormatSelectorProps {\n active: ColorFormat;\n onSelect(next: ColorFormat): void;\n}\n\nexport function ColorFormatSelector({ active, onSelect }: ColorFormatSelectorProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { className: 'sb-switcher__section-label' }, 'Color format'),\n h(\n 'div',\n { className: 'sb-switcher__section-body' },\n ...COLOR_FORMAT_OPTIONS.map((opt) =>\n h(\n 'button',\n {\n key: `color-format/${opt.id}`,\n type: 'button',\n onClick: () => onSelect(opt.id),\n onMouseDown: (event: React.MouseEvent) => event.preventDefault(),\n className:\n opt.id === active\n ? 'sb-switcher__pill sb-switcher__pill--active'\n : 'sb-switcher__pill',\n },\n opt.label,\n ),\n ),\n ),\n );\n}\n","import { ThemeSwitcher } from '@unpunnyfuns/swatchbook-switcher';\nimport { type ColorFormat, ColorFormatSelector } from '#/ColorFormatSelector.tsx';\nimport React, { useCallback, useEffect, useMemo, useRef, useState, type ReactElement } from 'react';\nimport { IconButton, WithTooltipPure } from 'storybook/internal/components';\nimport { addons, types, useGlobals, useStorybookApi } from 'storybook/manager-api';\nimport {\n type InitPayload,\n type VirtualAxis as AxisEntry,\n type VirtualPreset as PresetEntry,\n type VirtualTheme as ThemeEntry,\n} from '#/channel-types.ts';\nimport {\n ADDON_ID,\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n GLOBAL_KEY,\n INIT_EVENT,\n INIT_REQUEST_EVENT,\n PREVIEW_MOUSEDOWN_EVENT,\n TOOL_ID,\n} from '#/constants.ts';\n\n/**\n * Use explicit `React.createElement` rather than JSX so the manager bundle\n * doesn't take a hard dependency on `react/jsx-runtime`. Storybook's manager\n * page injects its own React as a runtime global; `react/jsx-runtime` isn't\n * always part of that exposure, which breaks JSX with\n * \"Cannot read properties of undefined (reading 'recentlyCreatedOwnerStacks')\".\n * Mirrors the pattern `@storybook/addon-a11y` uses in its manager.\n *\n * The imported `<ThemeSwitcher>` from `@unpunnyfuns/swatchbook-switcher`\n * compiles with classic JSX (`React.createElement`) specifically so it\n * survives embedding in the manager bundle the same way.\n */\nconst h = React.createElement;\n\nconst EMPTY_AXES: readonly AxisEntry[] = [];\nconst EMPTY_PRESETS: readonly PresetEntry[] = [];\nconst EMPTY_THEMES: readonly ThemeEntry[] = [];\n\n/**\n * Root toolbar glyph — a split-circle (\"yinyang\") mark: a faint filled\n * disc for the full-swatch silhouette, with a darker half-and-inset-disc\n * path reading as a pair of theme variants swapped in place.\n */\nfunction SwatchbookIcon(): ReactElement {\n return h(\n 'svg',\n { width: 14, height: 14, viewBox: '0 0 14 14', 'aria-hidden': true },\n h('circle', { cx: 7, cy: 7, r: 6, fill: 'currentColor', opacity: 0.15 }),\n h('path', {\n d: 'M7 1a6 6 0 0 0 0 12 3 3 0 0 0 0-6 3 3 0 0 1 0-6Z',\n fill: 'currentColor',\n }),\n );\n}\n\nfunction tupleMatchesInput(\n tuple: Readonly<Record<string, string>>,\n input: Readonly<Record<string, string>>,\n): boolean {\n const keys = Object.keys(input);\n if (keys.length === 0) return false;\n return keys.every((k) => input[k] === tuple[k]);\n}\n\nfunction composedNameFor(\n tuple: Readonly<Record<string, string>>,\n themes: readonly ThemeEntry[],\n fallback: string,\n): string {\n const match = themes.find((t) => tupleMatchesInput(tuple, t.input));\n return match?.name ?? fallback;\n}\n\nfunction defaultTupleFor(axes: readonly AxisEntry[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of axes) out[axis.name] = axis.default;\n return out;\n}\n\n/**\n * Compose a preset's sanitized partial tuple with the axis defaults, so\n * applying a preset that only names some axes leaves the omitted ones at\n * their defaults (not blank). Mirrors the preview decorator's own fallback\n * logic so what the toolbar sends out is what the decorator honors.\n */\nfunction presetTuple(\n preset: PresetEntry,\n axes: readonly AxisEntry[],\n defaults: Readonly<Record<string, string>>,\n): Record<string, string> {\n const out: Record<string, string> = { ...defaults };\n for (const axis of axes) {\n const candidate = preset.axes[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n return out;\n}\n\nfunction AxesToolbar(): ReactElement {\n const [globals, updateGlobals] = useGlobals();\n const api = useStorybookApi();\n const [payload, setPayload] = useState<InitPayload | null>(null);\n const [open, setOpen] = useState(false);\n const bodyRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n const channel = addons.getChannel();\n const onInit = (next: InitPayload): void => setPayload(next);\n channel.on(INIT_EVENT, onInit);\n /**\n * Ask the preview to (re-)emit INIT_EVENT in case it already broadcast\n * before this effect subscribed. Without this request, a late-mounting\n * manager (story navigation, docs reload) can stay in \"loading…\" until\n * the user triggers a globals change.\n */\n channel.emit(INIT_REQUEST_EVENT);\n return () => {\n channel.off(INIT_EVENT, onInit);\n };\n }, []);\n\n const axes = payload?.axes ?? EMPTY_AXES;\n const presets = payload?.presets ?? EMPTY_PRESETS;\n const themes = payload?.themes ?? EMPTY_THEMES;\n const defaults = useMemo(() => defaultTupleFor(axes), [axes]);\n const [lastApplied, setLastApplied] = useState<string | null>(null);\n const globalTuple = globals[AXES_GLOBAL_KEY] as Record<string, string> | undefined;\n const activeColorFormat = ((globals[COLOR_FORMAT_GLOBAL_KEY] as string | undefined) ??\n 'hex') as ColorFormat;\n\n const activeTuple = useMemo<Record<string, string>>(() => {\n const out: Record<string, string> = { ...defaults };\n if (globalTuple) {\n for (const axis of axes) {\n const candidate = globalTuple[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n }\n return out;\n }, [axes, defaults, globalTuple]);\n\n const setAxis = useCallback(\n (axisName: string, next: string): void => {\n const tuple: Record<string, string> = { ...activeTuple, [axisName]: next };\n const fallback = payload?.defaultTheme ?? themes[0]?.name ?? '';\n const composed = composedNameFor(tuple, themes, fallback);\n updateGlobals({ [AXES_GLOBAL_KEY]: tuple, [GLOBAL_KEY]: composed });\n },\n [activeTuple, themes, payload?.defaultTheme, updateGlobals],\n );\n\n const applyPreset = useCallback(\n (preset: PresetEntry): void => {\n const tuple = presetTuple(preset, axes, defaults);\n const fallback = payload?.defaultTheme ?? themes[0]?.name ?? '';\n const composed = composedNameFor(tuple, themes, fallback);\n updateGlobals({ [AXES_GLOBAL_KEY]: tuple, [GLOBAL_KEY]: composed });\n setLastApplied(preset.name);\n },\n [axes, defaults, themes, payload?.defaultTheme, updateGlobals],\n );\n\n useEffect(() => {\n if (presets.length === 0) return;\n // `alt+shift+C` rather than `alt+T` — the latter conflicts with\n // Storybook's built-in \"toggle addon panel\" binding on some\n // platforms. Rebindable from Storybook's keyboard-shortcuts panel.\n api.setAddonShortcut(ADDON_ID, {\n label: `Cycle swatchbook presets (${presets.length})`,\n defaultShortcut: ['alt', 'shift', 'C'],\n actionName: 'cyclePreset',\n showInMenu: true,\n action: () => {\n const currentIdx = lastApplied\n ? presets.findIndex((preset) => preset.name === lastApplied)\n : -1;\n const next = presets[(currentIdx + 1) % presets.length];\n if (next) applyPreset(next);\n },\n });\n }, [api, presets, lastApplied, applyPreset]);\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n setOpen(false);\n }\n }, []);\n\n /**\n * Escape closes even when focus hasn't entered the popover yet (e.g. the\n * user opened it via click and the mouse is still over the canvas). We\n * attach a document-level listener when open.\n */\n useEffect(() => {\n if (!open) return;\n const onDocKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') setOpen(false);\n };\n document.addEventListener('keydown', onDocKey);\n return () => document.removeEventListener('keydown', onDocKey);\n }, [open]);\n\n /**\n * `WithTooltipPure`'s built-in `closeOnOutsideClick` misses some cases\n * (portaled popover + manager iframe boundaries). Belt-and-suspenders:\n * close when the user mouses down anywhere that isn't the trigger wrapper\n * or the popover body.\n */\n useEffect(() => {\n if (!open) return;\n const onDocMouseDown = (e: MouseEvent): void => {\n const target = e.target;\n if (!(target instanceof Element)) return;\n if (bodyRef.current?.contains(target)) return;\n if (target.closest('[data-testid=\"swatchbook-switcher\"]')) return;\n setOpen(false);\n };\n /**\n * The manager's document-level listener above can't see mousedowns\n * inside the preview iframe. Preview emits PREVIEW_MOUSEDOWN_EVENT on\n * every mousedown over its own document; listen for it here so\n * clicking the canvas / docs page also closes the popover.\n */\n const channel = addons.getChannel();\n const onPreviewMouseDown = (): void => setOpen(false);\n document.addEventListener('mousedown', onDocMouseDown);\n channel.on(PREVIEW_MOUSEDOWN_EVENT, onPreviewMouseDown);\n return () => {\n document.removeEventListener('mousedown', onDocMouseDown);\n channel.off(PREVIEW_MOUSEDOWN_EVENT, onPreviewMouseDown);\n };\n }, [open]);\n\n if (axes.length === 0) {\n return h(\n IconButton,\n { key: TOOL_ID, title: 'Swatchbook theme (loading…)', disabled: true },\n h(SwatchbookIcon),\n );\n }\n\n const summary = axes.map((a) => activeTuple[a.name] ?? a.default).join(' · ');\n const title = `Swatchbook · ${summary}`;\n\n const button = h(\n IconButton,\n {\n key: TOOL_ID,\n title,\n active: open,\n onClick: () => setOpen((prev) => !prev),\n },\n h(SwatchbookIcon),\n );\n\n const tooltipBody = h(ThemeSwitcher, {\n axes,\n presets,\n themes,\n activeTuple,\n defaults,\n lastApplied,\n onAxisChange: setAxis,\n onPresetApply: applyPreset,\n onKeyDown: handleKeyDown,\n /**\n * Color format is addon-local chrome — drives how swatchbook blocks\n * stringify colors inside stories and docs. Slotted through the\n * switcher's `footer` escape hatch so shared theming UI stays free\n * of this concern.\n */\n footer: h(ColorFormatSelector, {\n active: activeColorFormat,\n onSelect: (next: ColorFormat) => updateGlobals({ [COLOR_FORMAT_GLOBAL_KEY]: next }),\n }),\n });\n\n return h(\n 'span',\n { ref: bodyRef, style: { display: 'inline-flex', alignItems: 'center' } },\n h(WithTooltipPure, {\n placement: 'bottom',\n trigger: 'click',\n visible: open,\n onVisibleChange: (next: boolean) => setOpen(next),\n closeOnOutsideClick: true,\n tooltip: tooltipBody,\n children: button,\n }),\n );\n}\n\naddons.register(ADDON_ID, () => {\n addons.add(TOOL_ID, {\n type: types.TOOL,\n title: 'Swatchbook theme',\n match: ({ viewMode, tabId }) => !tabId && (viewMode === 'story' || viewMode === 'docs'),\n render: () => h(AxesToolbar),\n });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"manager.mjs","names":["h"],"sources":["../src/ColorFormatSelector.tsx","../src/manager.tsx"],"sourcesContent":["import React, { type ReactElement } from 'react';\n\n/**\n * Storybook-addon-specific pill row for picking how color sub-values\n * render in swatchbook blocks (hex / rgb / hsl / oklch / raw). Lives in\n * the addon rather than the shared switcher because color format is a\n * blocks-rendering concern, not a theming one — the docs-site navbar\n * switcher has no consumer for it.\n *\n * Reuses the `sb-switcher__*` class names so styling stays consistent\n * with the rest of the toolbar popover. The switcher's CSS is already\n * loaded on the page because this selector only renders inside\n * `<ThemeSwitcher>`'s `footer` prop.\n *\n * Uses `React.createElement` (via `h`) to survive embedding in Storybook's\n * manager bundle, which doesn't expose `react/jsx-runtime`.\n */\n\nexport type ColorFormat = 'hex' | 'rgb' | 'hsl' | 'oklch' | 'raw';\n\nconst COLOR_FORMAT_OPTIONS: readonly { id: ColorFormat; label: string }[] = [\n { id: 'hex', label: 'Hex' },\n { id: 'rgb', label: 'RGB' },\n { id: 'hsl', label: 'HSL' },\n { id: 'oklch', label: 'OKLCH' },\n { id: 'raw', label: 'Raw (JSON)' },\n];\n\nconst h = React.createElement;\n\nexport interface ColorFormatSelectorProps {\n active: ColorFormat;\n onSelect(next: ColorFormat): void;\n}\n\nexport function ColorFormatSelector({ active, onSelect }: ColorFormatSelectorProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { className: 'sb-switcher__section-label' }, 'Color format'),\n h(\n 'div',\n { className: 'sb-switcher__section-body' },\n ...COLOR_FORMAT_OPTIONS.map((opt) =>\n h(\n 'button',\n {\n key: `color-format/${opt.id}`,\n type: 'button',\n onClick: () => onSelect(opt.id),\n onMouseDown: (event: React.MouseEvent) => event.preventDefault(),\n className:\n opt.id === active\n ? 'sb-switcher__pill sb-switcher__pill--active'\n : 'sb-switcher__pill',\n },\n opt.label,\n ),\n ),\n ),\n );\n}\n","import { ThemeSwitcher } from '@unpunnyfuns/swatchbook-switcher';\nimport { type ColorFormat, ColorFormatSelector } from '#/ColorFormatSelector.tsx';\nimport React, { useCallback, useEffect, useMemo, useRef, useState, type ReactElement } from 'react';\nimport { IconButton, WithTooltipPure } from 'storybook/internal/components';\nimport { addons, types, useGlobals, useStorybookApi } from 'storybook/manager-api';\nimport {\n type InitPayload,\n type VirtualAxis as AxisEntry,\n type VirtualPermutation as PermutationEntry,\n type VirtualPreset as PresetEntry,\n} from '#/channel-types.ts';\nimport {\n ADDON_ID,\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n INIT_EVENT,\n INIT_REQUEST_EVENT,\n PREVIEW_MOUSEDOWN_EVENT,\n TOOL_ID,\n} from '#/constants.ts';\n\n/**\n * Use explicit `React.createElement` rather than JSX so the manager bundle\n * doesn't take a hard dependency on `react/jsx-runtime`. Storybook's manager\n * page injects its own React as a runtime global; `react/jsx-runtime` isn't\n * always part of that exposure, which breaks JSX with\n * \"Cannot read properties of undefined (reading 'recentlyCreatedOwnerStacks')\".\n * Mirrors the pattern `@storybook/addon-a11y` uses in its manager.\n *\n * The imported `<ThemeSwitcher>` from `@unpunnyfuns/swatchbook-switcher`\n * compiles with classic JSX (`React.createElement`) specifically so it\n * survives embedding in the manager bundle the same way.\n */\nconst h = React.createElement;\n\nconst EMPTY_AXES: readonly AxisEntry[] = [];\nconst EMPTY_PRESETS: readonly PresetEntry[] = [];\nconst EMPTY_PERMUTATIONS: readonly PermutationEntry[] = [];\n\n/**\n * Root toolbar glyph — a split-circle (\"yinyang\") mark: a faint filled\n * disc for the full-swatch silhouette, with a darker half-and-inset-disc\n * path reading as a pair of theme variants swapped in place.\n */\nfunction SwatchbookIcon(): ReactElement {\n return h(\n 'svg',\n { width: 14, height: 14, viewBox: '0 0 14 14', 'aria-hidden': true },\n h('circle', { cx: 7, cy: 7, r: 6, fill: 'currentColor', opacity: 0.15 }),\n h('path', {\n d: 'M7 1a6 6 0 0 0 0 12 3 3 0 0 0 0-6 3 3 0 0 1 0-6Z',\n fill: 'currentColor',\n }),\n );\n}\n\nfunction defaultTupleFor(axes: readonly AxisEntry[]): Record<string, string> {\n const out: Record<string, string> = {};\n for (const axis of axes) out[axis.name] = axis.default;\n return out;\n}\n\n/**\n * Compose a preset's sanitized partial tuple with the axis defaults, so\n * applying a preset that only names some axes leaves the omitted ones at\n * their defaults (not blank). Mirrors the preview decorator's own fallback\n * logic so what the toolbar sends out is what the decorator honors.\n */\nfunction presetTuple(\n preset: PresetEntry,\n axes: readonly AxisEntry[],\n defaults: Readonly<Record<string, string>>,\n): Record<string, string> {\n const out: Record<string, string> = { ...defaults };\n for (const axis of axes) {\n const candidate = preset.axes[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n return out;\n}\n\nfunction AxesToolbar(): ReactElement {\n const [globals, updateGlobals] = useGlobals();\n const api = useStorybookApi();\n const [payload, setPayload] = useState<InitPayload | null>(null);\n const [open, setOpen] = useState(false);\n const bodyRef = useRef<HTMLDivElement | null>(null);\n\n useEffect(() => {\n const channel = addons.getChannel();\n const onInit = (next: InitPayload): void => setPayload(next);\n channel.on(INIT_EVENT, onInit);\n /**\n * Ask the preview to (re-)emit INIT_EVENT in case it already broadcast\n * before this effect subscribed. Without this request, a late-mounting\n * manager (story navigation, docs reload) can stay in \"loading…\" until\n * the user triggers a globals change.\n */\n channel.emit(INIT_REQUEST_EVENT);\n return () => {\n channel.off(INIT_EVENT, onInit);\n };\n }, []);\n\n const axes = payload?.axes ?? EMPTY_AXES;\n const presets = payload?.presets ?? EMPTY_PRESETS;\n const permutations = payload?.permutations ?? EMPTY_PERMUTATIONS;\n const defaults = useMemo(() => defaultTupleFor(axes), [axes]);\n const [lastApplied, setLastApplied] = useState<string | null>(null);\n const globalTuple = globals[AXES_GLOBAL_KEY] as Record<string, string> | undefined;\n const activeColorFormat = ((globals[COLOR_FORMAT_GLOBAL_KEY] as string | undefined) ??\n 'hex') as ColorFormat;\n\n const activeTuple = useMemo<Record<string, string>>(() => {\n const out: Record<string, string> = { ...defaults };\n if (globalTuple) {\n for (const axis of axes) {\n const candidate = globalTuple[axis.name];\n if (candidate !== undefined && axis.contexts.includes(candidate)) {\n out[axis.name] = candidate;\n }\n }\n }\n return out;\n }, [axes, defaults, globalTuple]);\n\n const setAxis = useCallback(\n (axisName: string, next: string): void => {\n const tuple: Record<string, string> = { ...activeTuple, [axisName]: next };\n updateGlobals({ [AXES_GLOBAL_KEY]: tuple });\n },\n [activeTuple, updateGlobals],\n );\n\n const applyPreset = useCallback(\n (preset: PresetEntry): void => {\n const tuple = presetTuple(preset, axes, defaults);\n updateGlobals({ [AXES_GLOBAL_KEY]: tuple });\n setLastApplied(preset.name);\n },\n [axes, defaults, updateGlobals],\n );\n\n useEffect(() => {\n if (presets.length === 0) return;\n // `alt+shift+C` rather than `alt+T` — the latter conflicts with\n // Storybook's built-in \"toggle addon panel\" binding on some\n // platforms. Rebindable from Storybook's keyboard-shortcuts panel.\n api.setAddonShortcut(ADDON_ID, {\n label: `Cycle swatchbook presets (${presets.length})`,\n defaultShortcut: ['alt', 'shift', 'C'],\n actionName: 'cyclePreset',\n showInMenu: true,\n action: () => {\n const currentIdx = lastApplied\n ? presets.findIndex((preset) => preset.name === lastApplied)\n : -1;\n const next = presets[(currentIdx + 1) % presets.length];\n if (next) applyPreset(next);\n },\n });\n }, [api, presets, lastApplied, applyPreset]);\n\n const handleKeyDown = useCallback((event: React.KeyboardEvent<HTMLDivElement>): void => {\n if (event.key === 'Escape') {\n event.stopPropagation();\n setOpen(false);\n }\n }, []);\n\n /**\n * Escape closes even when focus hasn't entered the popover yet (e.g. the\n * user opened it via click and the mouse is still over the canvas). We\n * attach a document-level listener when open.\n */\n useEffect(() => {\n if (!open) return;\n const onDocKey = (e: KeyboardEvent): void => {\n if (e.key === 'Escape') setOpen(false);\n };\n document.addEventListener('keydown', onDocKey);\n return () => document.removeEventListener('keydown', onDocKey);\n }, [open]);\n\n /**\n * `WithTooltipPure`'s built-in `closeOnOutsideClick` misses some cases\n * (portaled popover + manager iframe boundaries). Belt-and-suspenders:\n * close when the user mouses down anywhere that isn't the trigger wrapper\n * or the popover body.\n */\n useEffect(() => {\n if (!open) return;\n const onDocMouseDown = (e: MouseEvent): void => {\n const target = e.target;\n if (!(target instanceof Element)) return;\n if (bodyRef.current?.contains(target)) return;\n if (target.closest('[data-testid=\"swatchbook-switcher\"]')) return;\n setOpen(false);\n };\n /**\n * The manager's document-level listener above can't see mousedowns\n * inside the preview iframe. Preview emits PREVIEW_MOUSEDOWN_EVENT on\n * every mousedown over its own document; listen for it here so\n * clicking the canvas / docs page also closes the popover.\n */\n const channel = addons.getChannel();\n const onPreviewMouseDown = (): void => setOpen(false);\n document.addEventListener('mousedown', onDocMouseDown);\n channel.on(PREVIEW_MOUSEDOWN_EVENT, onPreviewMouseDown);\n return () => {\n document.removeEventListener('mousedown', onDocMouseDown);\n channel.off(PREVIEW_MOUSEDOWN_EVENT, onPreviewMouseDown);\n };\n }, [open]);\n\n if (axes.length === 0) {\n return h(\n IconButton,\n { key: TOOL_ID, title: 'Swatchbook theme (loading…)', disabled: true },\n h(SwatchbookIcon),\n );\n }\n\n const summary = axes.map((a) => activeTuple[a.name] ?? a.default).join(' · ');\n const title = `Swatchbook · ${summary}`;\n\n const button = h(\n IconButton,\n {\n key: TOOL_ID,\n title,\n active: open,\n onClick: () => setOpen((prev) => !prev),\n },\n h(SwatchbookIcon),\n );\n\n const tooltipBody = h(ThemeSwitcher, {\n axes,\n presets,\n permutations,\n activeTuple,\n defaults,\n lastApplied,\n onAxisChange: setAxis,\n onPresetApply: applyPreset,\n onKeyDown: handleKeyDown,\n /**\n * Color format is addon-local chrome — drives how swatchbook blocks\n * stringify colors inside stories and docs. Slotted through the\n * switcher's `footer` escape hatch so shared theming UI stays free\n * of this concern.\n */\n footer: h(ColorFormatSelector, {\n active: activeColorFormat,\n onSelect: (next: ColorFormat) => updateGlobals({ [COLOR_FORMAT_GLOBAL_KEY]: next }),\n }),\n });\n\n return h(\n 'span',\n { ref: bodyRef, style: { display: 'inline-flex', alignItems: 'center' } },\n h(WithTooltipPure, {\n placement: 'bottom',\n trigger: 'click',\n visible: open,\n onVisibleChange: (next: boolean) => setOpen(next),\n closeOnOutsideClick: true,\n tooltip: tooltipBody,\n children: button,\n }),\n );\n}\n\naddons.register(ADDON_ID, () => {\n addons.add(TOOL_ID, {\n type: types.TOOL,\n title: 'Swatchbook theme',\n match: ({ viewMode, tabId }) => !tabId && (viewMode === 'story' || viewMode === 'docs'),\n render: () => h(AxesToolbar),\n });\n});\n"],"mappings":"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"}
package/dist/preset.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { a as HMR_EVENT, d as RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID, f as RESOLVED_VIRTUAL_MODULE_ID } from "./constants-DhgRF7SI.mjs";
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
2
  import { mkdir, writeFile } from "node:fs/promises";
3
3
  import { basename, dirname, isAbsolute, resolve } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
@@ -13,7 +13,7 @@ function resolvedId(virtualId) {
13
13
  }
14
14
  /**
15
15
  * Vite plugin that serves the virtual `virtual:swatchbook/tokens` module —
16
- * a single source of truth for themes, resolved token maps, per-theme CSS,
16
+ * a single source of truth for permutations, resolved token maps, per-theme CSS,
17
17
  * and diagnostics. Watches the token files + resolver for changes and
18
18
  * invalidates the module so HMR reloads the preview with fresh data.
19
19
  */
@@ -60,9 +60,9 @@ function swatchbookTokensPlugin({ config, cwd, integrations = [] }) {
60
60
  `export const axes = ${JSON.stringify(project.axes)};`,
61
61
  `export const presets = ${JSON.stringify(project.presets)};`,
62
62
  `export const disabledAxes = ${JSON.stringify(project.disabledAxes)};`,
63
- `export const themes = ${JSON.stringify(project.themes)};`,
64
- `export const defaultTheme = ${JSON.stringify(project.themes[0]?.name ?? null)};`,
65
- `export const themesResolved = ${JSON.stringify(project.themesResolved)};`,
63
+ `export const permutations = ${JSON.stringify(project.permutations)};`,
64
+ `export const defaultPermutation = ${JSON.stringify(project.permutations[0]?.name ?? null)};`,
65
+ `export const permutationsResolved = ${JSON.stringify(project.permutationsResolved)};`,
66
66
  `export const diagnostics = ${JSON.stringify(project.diagnostics)};`,
67
67
  `export const css = ${JSON.stringify(css)};`,
68
68
  `export const cssVarPrefix = ${JSON.stringify(config.cssVarPrefix ?? "")};`,
@@ -85,7 +85,7 @@ function swatchbookTokensPlugin({ config, cwd, integrations = [] }) {
85
85
  (async () => {
86
86
  await refresh();
87
87
  if (!project) return;
88
- const tokenCount = Object.keys(project.themesResolved[project.themes[0]?.name ?? ""] ?? {}).length;
88
+ const tokenCount = Object.keys(project.permutationsResolved[project.permutations[0]?.name ?? ""] ?? {}).length;
89
89
  const diagCount = project.diagnostics.length;
90
90
  server.config.logger.info(`\x1b[36m[swatchbook]\x1b[0m tokens reloaded — ${tokenCount} tokens, ${diagCount} diagnostic${diagCount === 1 ? "" : "s"}`, {
91
91
  clear: false,
@@ -112,9 +112,9 @@ function swatchbookTokensPlugin({ config, cwd, integrations = [] }) {
112
112
  axes: project.axes,
113
113
  disabledAxes: project.disabledAxes,
114
114
  presets: project.presets,
115
- themes: project.themes,
116
- defaultTheme: project.themes[0]?.name ?? null,
117
- themesResolved: project.themesResolved,
115
+ permutations: project.permutations,
116
+ defaultPermutation: project.permutations[0]?.name ?? null,
117
+ permutationsResolved: project.permutationsResolved,
118
118
  diagnostics: project.diagnostics,
119
119
  css,
120
120
  cssVarPrefix: config.cssVarPrefix ?? "",
@@ -229,13 +229,13 @@ async function writeTokenCodegen(config, cwd, options) {
229
229
  /** @internal Exported for tests; not part of the public API. */
230
230
  function renderTokenTypes(project) {
231
231
  const paths = /* @__PURE__ */ new Set();
232
- for (const theme of project.themes) {
233
- const tokens = project.themesResolved[theme.name];
232
+ for (const theme of project.permutations) {
233
+ const tokens = project.permutationsResolved[theme.name];
234
234
  if (!tokens) continue;
235
235
  for (const path of Object.keys(tokens)) paths.add(path);
236
236
  }
237
237
  const tokenEntries = [...paths].toSorted().map((p) => ` ${JSON.stringify(p)}: string;`);
238
- const themeUnion = project.themes.map((t) => JSON.stringify(t.name)).join(" | ") || "string";
238
+ const themeUnion = project.permutations.map((t) => JSON.stringify(t.name)).join(" | ") || "string";
239
239
  return [
240
240
  "// Generated by @unpunnyfuns/swatchbook-addon. Do not edit.",
241
241
  "declare module '@unpunnyfuns/swatchbook-addon/hooks' {",
@@ -243,7 +243,7 @@ function renderTokenTypes(project) {
243
243
  ...tokenEntries,
244
244
  " }",
245
245
  "",
246
- ` export type SwatchbookThemeName = ${themeUnion};`,
246
+ ` export type SwatchbookPermutationName = ${themeUnion};`,
247
247
  "}",
248
248
  ""
249
249
  ].join("\n");
@@ -1 +1 @@
1
- {"version":3,"file":"preset.mjs","names":["fsWatch"],"sources":["../src/virtual/plugin.ts","../src/preset.ts"],"sourcesContent":["import type {\n Config,\n ListedToken,\n Project,\n SwatchbookIntegration,\n TokenListingByPath,\n} from '@unpunnyfuns/swatchbook-core';\nimport { loadProject, projectCss } from '@unpunnyfuns/swatchbook-core';\nimport { type FSWatcher, watch as fsWatch } 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\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 themes, 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}: SwatchbookPluginOptions): Plugin {\n let project: Project | undefined;\n let css = '';\n\n async function refresh(): Promise<void> {\n project = await loadProject(config, cwd);\n css = projectCss(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 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. Values are JSON-stringified for stability.\n return [\n `/* swatchbook virtual module — generated */`,\n `export const axes = ${JSON.stringify(project.axes)};`,\n `export const presets = ${JSON.stringify(project.presets)};`,\n `export const disabledAxes = ${JSON.stringify(project.disabledAxes)};`,\n `export const themes = ${JSON.stringify(project.themes)};`,\n `export const defaultTheme = ${JSON.stringify(project.themes[0]?.name ?? null)};`,\n `export const themesResolved = ${JSON.stringify(project.themesResolved)};`,\n `export const diagnostics = ${JSON.stringify(project.diagnostics)};`,\n `export const css = ${JSON.stringify(css)};`,\n `export const cssVarPrefix = ${JSON.stringify(config.cssVarPrefix ?? '')};`,\n `export const listing = ${JSON.stringify(slimListing(project.listing))};`,\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 = Object.keys(\n project.themesResolved[project.themes[0]?.name ?? ''] ?? {},\n ).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: {\n axes: project.axes,\n disabledAxes: project.disabledAxes,\n presets: project.presets,\n themes: project.themes,\n defaultTheme: project.themes[0]?.name ?? null,\n themesResolved: project.themesResolved,\n diagnostics: project.diagnostics,\n css,\n cssVarPrefix: config.cssVarPrefix ?? '',\n listing: slimListing(project.listing),\n },\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\ntype SlimListedToken = Pick<\n ListedToken['$extensions']['app.terrazzo.listing'],\n 'names' | 'previewValue' | 'source'\n>;\n\n/**\n * Reduce the full Token Listing surface down to the fields blocks read.\n * Drops `originalValue` (large, not needed for display), `$value`, `$type`,\n * `mode`, `subtype` for now — the blocks don't consume them yet. Keeps the\n * virtual module payload lean, especially for large projects where each\n * token's raw listing entry can weigh a few KB.\n */\nfunction slimListing(listing: TokenListingByPath): Record<string, SlimListedToken> {\n const out: Record<string, SlimListedToken> = {};\n for (const [path, entry] of Object.entries(listing)) {\n const ext = entry.$extensions['app.terrazzo.listing'];\n const slim: SlimListedToken = { names: ext.names };\n if (ext.previewValue !== undefined) slim.previewValue = ext.previewValue;\n if (ext.source !== undefined) slim.source = ext.source;\n out[path] = slim;\n }\n return out;\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 { 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 // Codegen runs once at Vite startup. The virtual module plugin still\n // owns the live reload path via its HMR watcher; this file just gives\n // TS autocomplete for `useToken('…')` in consumer stories.\n await writeTokenCodegen(config, cwd, options);\n\n const plugins = Array.isArray(viteConfig.plugins) ? [...viteConfig.plugins] : [];\n plugins.push(\n swatchbookTokensPlugin({\n config,\n cwd,\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(\n config: Config,\n cwd: string,\n options: PresetOptions,\n): Promise<void> {\n const project = await loadProject(config, cwd);\n const projectRoot = resolve(options.configDir, '..');\n const outDir = resolve(projectRoot, 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 paths = new Set<string>();\n for (const theme of project.themes) {\n const tokens = project.themesResolved[theme.name];\n if (!tokens) continue;\n for (const path of Object.keys(tokens)) paths.add(path);\n }\n const sorted = [...paths].toSorted();\n const tokenEntries = sorted.map((p) => ` ${JSON.stringify(p)}: string;`);\n const themeUnion = project.themes.map((t) => JSON.stringify(t.name)).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 SwatchbookThemeName = ${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 {\n Config,\n ListedToken,\n Project,\n SwatchbookIntegration,\n TokenListingByPath,\n} from '@unpunnyfuns/swatchbook-core';\nimport { loadProject, projectCss } from '@unpunnyfuns/swatchbook-core';\nimport { type FSWatcher, watch as fsWatch } 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\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}: SwatchbookPluginOptions): Plugin {\n let project: Project | undefined;\n let css = '';\n\n async function refresh(): Promise<void> {\n project = await loadProject(config, cwd);\n css = projectCss(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 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. Values are JSON-stringified for stability.\n return [\n `/* swatchbook virtual module — generated */`,\n `export const axes = ${JSON.stringify(project.axes)};`,\n `export const presets = ${JSON.stringify(project.presets)};`,\n `export const disabledAxes = ${JSON.stringify(project.disabledAxes)};`,\n `export const permutations = ${JSON.stringify(project.permutations)};`,\n `export const defaultPermutation = ${JSON.stringify(project.permutations[0]?.name ?? null)};`,\n `export const permutationsResolved = ${JSON.stringify(project.permutationsResolved)};`,\n `export const diagnostics = ${JSON.stringify(project.diagnostics)};`,\n `export const css = ${JSON.stringify(css)};`,\n `export const cssVarPrefix = ${JSON.stringify(config.cssVarPrefix ?? '')};`,\n `export const listing = ${JSON.stringify(slimListing(project.listing))};`,\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 = Object.keys(\n project.permutationsResolved[project.permutations[0]?.name ?? ''] ?? {},\n ).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: {\n axes: project.axes,\n disabledAxes: project.disabledAxes,\n presets: project.presets,\n permutations: project.permutations,\n defaultPermutation: project.permutations[0]?.name ?? null,\n permutationsResolved: project.permutationsResolved,\n diagnostics: project.diagnostics,\n css,\n cssVarPrefix: config.cssVarPrefix ?? '',\n listing: slimListing(project.listing),\n },\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\ntype SlimListedToken = Pick<\n ListedToken['$extensions']['app.terrazzo.listing'],\n 'names' | 'previewValue' | 'source'\n>;\n\n/**\n * Reduce the full Token Listing surface down to the fields blocks read.\n * Drops `originalValue` (large, not needed for display), `$value`, `$type`,\n * `mode`, `subtype` for now — the blocks don't consume them yet. Keeps the\n * virtual module payload lean, especially for large projects where each\n * token's raw listing entry can weigh a few KB.\n */\nfunction slimListing(listing: TokenListingByPath): Record<string, SlimListedToken> {\n const out: Record<string, SlimListedToken> = {};\n for (const [path, entry] of Object.entries(listing)) {\n const ext = entry.$extensions['app.terrazzo.listing'];\n const slim: SlimListedToken = { names: ext.names };\n if (ext.previewValue !== undefined) slim.previewValue = ext.previewValue;\n if (ext.source !== undefined) slim.source = ext.source;\n out[path] = slim;\n }\n return out;\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 { 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 // Codegen runs once at Vite startup. The virtual module plugin still\n // owns the live reload path via its HMR watcher; this file just gives\n // TS autocomplete for `useToken('…')` in consumer stories.\n await writeTokenCodegen(config, cwd, options);\n\n const plugins = Array.isArray(viteConfig.plugins) ? [...viteConfig.plugins] : [];\n plugins.push(\n swatchbookTokensPlugin({\n config,\n cwd,\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(\n config: Config,\n cwd: string,\n options: PresetOptions,\n): Promise<void> {\n const project = await loadProject(config, cwd);\n const projectRoot = resolve(options.configDir, '..');\n const outDir = resolve(projectRoot, 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 paths = new Set<string>();\n for (const theme of project.permutations) {\n const tokens = project.permutationsResolved[theme.name];\n if (!tokens) continue;\n for (const path of Object.keys(tokens)) paths.add(path);\n }\n const sorted = [...paths].toSorted();\n const tokenEntries = sorted.map((p) => ` ${JSON.stringify(p)}: string;`);\n const themeUnion =\n project.permutations.map((t) => JSON.stringify(t.name)).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,9 +1,9 @@
1
- import { a as HMR_EVENT, i as GLOBAL_KEY, l as PARAM_KEY, m as TOKENS_UPDATED_EVENT, n as AXES_GLOBAL_KEY, o as INIT_EVENT, p as STYLE_ELEMENT_ID, r as COLOR_FORMAT_GLOBAL_KEY, s as INIT_REQUEST_EVENT, u as PREVIEW_MOUSEDOWN_EVENT } from "./constants-DhgRF7SI.mjs";
1
+ import { a as INIT_EVENT, c as PARAM_KEY, 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
2
  import { useEffect, useMemo } from "react";
3
3
  import { addons } from "storybook/preview-api";
4
4
  import "virtual:swatchbook/integration-side-effects";
5
- import { axes, css, cssVarPrefix, defaultTheme, diagnostics, disabledAxes, listing, presets, themes, themesResolved } from "virtual:swatchbook/tokens";
6
- import { AxesContext, COLOR_FORMATS, ColorFormatContext, SwatchbookContext, ThemeContext } from "@unpunnyfuns/swatchbook-blocks";
5
+ import { axes, css, cssVarPrefix, defaultPermutation, diagnostics, disabledAxes, listing, permutations, permutationsResolved, presets } from "virtual:swatchbook/tokens";
6
+ import { AxesContext, COLOR_FORMATS, ColorFormatContext, PermutationContext, SwatchbookContext } from "@unpunnyfuns/swatchbook-blocks";
7
7
  import { jsx } from "react/jsx-runtime";
8
8
  //#region \0rolldown/runtime.js
9
9
  var __defProp = Object.defineProperty;
@@ -69,7 +69,7 @@ html, body {
69
69
  * same pinned context per disabled axis, so sampling any theme works.
70
70
  */
71
71
  function forEachPinnedAxis(cb) {
72
- const pinnedSample = themes[0]?.input;
72
+ const pinnedSample = permutations[0]?.input;
73
73
  if (!pinnedSample) return;
74
74
  for (const name of disabledAxes) {
75
75
  const value = pinnedSample[name];
@@ -77,15 +77,15 @@ function forEachPinnedAxis(cb) {
77
77
  }
78
78
  }
79
79
  /**
80
- * Pick the theme name for a tuple, falling back to `defaultTheme` and then
81
- * the first theme. Returns empty string when the project has no themes so
80
+ * Pick the theme name for a tuple, falling back to `defaultPermutation` and then
81
+ * the first theme. Returns empty string when the project has no permutations so
82
82
  * callers can omit the attr instead of writing a made-up context name.
83
83
  */
84
- function matchThemeName(tuple) {
85
- return themes.find((t) => {
84
+ function matchPermutationName(tuple) {
85
+ return permutations.find((t) => {
86
86
  const input = t.input;
87
87
  return Object.keys(input).every((k) => input[k] === tuple[k]);
88
- })?.name ?? defaultTheme ?? themes[0]?.name ?? "";
88
+ })?.name ?? defaultPermutation ?? permutations[0]?.name ?? "";
89
89
  }
90
90
  /**
91
91
  * Write the composed permutation ID to `data-<prefix>-theme` plus one
@@ -119,9 +119,9 @@ function broadcastInit() {
119
119
  axes,
120
120
  disabledAxes,
121
121
  presets,
122
- themes,
123
- defaultTheme,
124
- themesResolved,
122
+ permutations,
123
+ defaultPermutation,
124
+ permutationsResolved,
125
125
  diagnostics,
126
126
  cssVarPrefix
127
127
  });
@@ -132,9 +132,9 @@ function defaultTuple() {
132
132
  for (const axis of axes) out[axis.name] = axis.default;
133
133
  return out;
134
134
  }
135
- /** Look up a `Theme.input` by composed name. Returns `undefined` if no theme matches. */
135
+ /** Look up a `Permutation.input` by composed name. Returns `undefined` if no theme matches. */
136
136
  function tupleForName(name) {
137
- return themes.find((t) => t.name === name)?.input;
137
+ return permutations.find((t) => t.name === name)?.input;
138
138
  }
139
139
  /**
140
140
  * Merge a partial tuple onto the axis defaults, dropping keys for axes that
@@ -150,29 +150,23 @@ function normalizeTuple(partial) {
150
150
  return out;
151
151
  }
152
152
  /**
153
- * Resolve the active tuple from all four input channels, in priority order:
153
+ * Resolve the active tuple from all input channels, in priority order:
154
154
  * 1. `parameters.swatchbook.axes` — per-story tuple.
155
- * 2. `parameters.swatchbook.theme` — per-story composed name (legacy).
155
+ * 2. `parameters.swatchbook.permutation` — per-story composed name.
156
156
  * 3. `globals.swatchbookAxes` — toolbar-set tuple.
157
- * 4. `globals.swatchbookTheme` toolbar-set composed name.
158
- * 5. virtual module default.
157
+ * 4. virtual module default.
159
158
  */
160
159
  function resolveTuple(globals, parameters) {
161
160
  const param = parameters[PARAM_KEY];
162
161
  const paramAxes = param?.["axes"];
163
162
  if (paramAxes && typeof paramAxes === "object") return normalizeTuple(paramAxes);
164
- const paramTheme = param?.["theme"];
165
- if (typeof paramTheme === "string") {
166
- const hit = tupleForName(paramTheme);
163
+ const paramPermutation = param?.["permutation"];
164
+ if (typeof paramPermutation === "string") {
165
+ const hit = tupleForName(paramPermutation);
167
166
  if (hit) return normalizeTuple(hit);
168
167
  }
169
168
  const globalAxes = globals[AXES_GLOBAL_KEY];
170
169
  if (globalAxes && typeof globalAxes === "object") return normalizeTuple(globalAxes);
171
- const globalTheme = globals[GLOBAL_KEY];
172
- if (typeof globalTheme === "string") {
173
- const hit = tupleForName(globalTheme);
174
- if (hit) return normalizeTuple(hit);
175
- }
176
170
  return defaultTuple();
177
171
  }
178
172
  function resolveColorFormat(globals) {
@@ -183,7 +177,7 @@ function resolveColorFormat(globals) {
183
177
  const themedDecorator = (Story, context) => {
184
178
  const tuple = useMemo(() => resolveTuple(context.globals, context.parameters), [context.globals, context.parameters]);
185
179
  const colorFormat = useMemo(() => resolveColorFormat(context.globals), [context.globals]);
186
- const themeName = useMemo(() => matchThemeName(tuple), [tuple]);
180
+ const themeName = useMemo(() => matchPermutationName(tuple), [tuple]);
187
181
  useEffect(() => {
188
182
  ensureStylesheet();
189
183
  broadcastInit();
@@ -204,9 +198,9 @@ const themedDecorator = (Story, context) => {
204
198
  axes,
205
199
  disabledAxes,
206
200
  presets,
207
- themes,
208
- themesResolved,
209
- activeTheme: themeName,
201
+ permutations,
202
+ permutationsResolved,
203
+ activePermutation: themeName,
210
204
  activeAxes: tuple,
211
205
  cssVarPrefix,
212
206
  diagnostics,
@@ -215,7 +209,7 @@ const themedDecorator = (Story, context) => {
215
209
  }), [themeName, tuple]);
216
210
  return /* @__PURE__ */ jsx(SwatchbookContext.Provider, {
217
211
  value: snapshot,
218
- children: /* @__PURE__ */ jsx(ThemeContext.Provider, {
212
+ children: /* @__PURE__ */ jsx(PermutationContext.Provider, {
219
213
  value: themeName,
220
214
  children: /* @__PURE__ */ jsx(AxesContext.Provider, {
221
215
  value: tuple,
@@ -240,13 +234,9 @@ const themedDecorator = (Story, context) => {
240
234
  */
241
235
  const decorators = [themedDecorator];
242
236
  const globalTypes = {
243
- [GLOBAL_KEY]: {
244
- name: "Theme",
245
- description: "Active swatchbook theme (composed permutation ID)."
246
- },
247
237
  [AXES_GLOBAL_KEY]: {
248
238
  name: "Axes",
249
- description: "Per-axis context selection. Takes precedence over the composed theme name."
239
+ description: "Per-axis context selection the active permutation tuple."
250
240
  },
251
241
  [COLOR_FORMAT_GLOBAL_KEY]: {
252
242
  name: "Color format",
@@ -259,7 +249,6 @@ function buildInitialAxes() {
259
249
  return out;
260
250
  }
261
251
  const initialGlobals = {
262
- [GLOBAL_KEY]: defaultTheme ?? themes[0]?.name ?? "",
263
252
  [AXES_GLOBAL_KEY]: buildInitialAxes(),
264
253
  [COLOR_FORMAT_GLOBAL_KEY]: "hex"
265
254
  };
@@ -296,7 +285,7 @@ function installGlobalAxisApplier() {
296
285
  const apply = (globals) => {
297
286
  ensureStylesheet();
298
287
  const tuple = resolveTuple(globals, {});
299
- setRootAxes(matchThemeName(tuple), tuple);
288
+ setRootAxes(matchPermutationName(tuple), tuple);
300
289
  };
301
290
  const onGlobals = (payload) => {
302
291
  if (payload.globals) apply(payload.globals);
@@ -344,9 +333,9 @@ html, body {
344
333
  axes: payload.axes,
345
334
  disabledAxes: payload.disabledAxes,
346
335
  presets: payload.presets,
347
- themes: payload.themes,
348
- defaultTheme: payload.defaultTheme,
349
- themesResolved: payload.themesResolved,
336
+ permutations: payload.permutations,
337
+ defaultPermutation: payload.defaultPermutation,
338
+ permutationsResolved: payload.permutationsResolved,
350
339
  diagnostics: payload.diagnostics,
351
340
  cssVarPrefix: payload.cssVarPrefix
352
341
  });
@@ -355,4 +344,4 @@ html, body {
355
344
  //#endregion
356
345
  export { preview_exports as i, globalTypes as n, initialGlobals as r, decorators as t };
357
346
 
358
- //# sourceMappingURL=preview-D-mlU5Ip.mjs.map
347
+ //# sourceMappingURL=preview-CFEfgrWb.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"preview-CFEfgrWb.mjs","names":["virtualDisabledAxes","virtualAxes","virtualPresets","virtualListing"],"sources":["../src/data-attr.ts","../src/preview.tsx"],"sourcesContent":["/**\n * Produce a prefixed `data-*` attribute name when `prefix` is set, bare\n * `data-<key>` otherwise. Duplicated from `@unpunnyfuns/swatchbook-core`'s\n * `dataAttr` so the preview bundle doesn't have to import from core —\n * core's top-level entry pulls in Node-only modules (`node:fs/promises`\n * via the loader) that throw when evaluated in the browser.\n */\nexport function dataAttr(prefix: string, key: string): string {\n return prefix ? `data-${prefix}-${key}` : `data-${key}`;\n}\n","/// <reference types=\"vite/client\" />\nimport type { Decorator, Preview } from '@storybook/react-vite';\nimport { useEffect, useMemo } from 'react';\nimport { addons } from 'storybook/preview-api';\nimport { dataAttr } from '#/data-attr.ts';\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 defaultPermutation,\n diagnostics,\n disabledAxes as virtualDisabledAxes,\n listing as virtualListing,\n presets as virtualPresets,\n permutations,\n permutationsResolved,\n} from 'virtual:swatchbook/tokens';\nimport {\n AxesContext,\n COLOR_FORMATS,\n type ColorFormat,\n ColorFormatContext,\n type ProjectSnapshot,\n SwatchbookContext,\n PermutationContext,\n} 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 PARAM_KEY,\n STYLE_ELEMENT_ID,\n TOKENS_UPDATED_EVENT,\n} from '#/constants.ts';\n\n/** CSS var name with the active prefix applied. */\nfunction v(name: string): string {\n return cssVarPrefix ? `--${cssVarPrefix}-${name}` : `--${name}`;\n}\n\n/**\n * Inject the per-theme stylesheet plus a tiny `html, body { ... }` block so\n * the iframe's own chrome (outside any decorator wrapper — Docs mode,\n * autodocs, empty gutters) also picks up the active theme.\n */\nfunction ensureStylesheet(): void {\n if (typeof document === 'undefined') return;\n const bodyRules = `\nhtml, body {\n background: var(${v('color-surface-default')}, Canvas);\n color: var(${v('color-text-default')}, CanvasText);\n margin: 0;\n}\n`;\n const text = `${css}\\n${bodyRules}`;\n let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!style) {\n style = document.createElement('style');\n style.id = STYLE_ELEMENT_ID;\n document.head.appendChild(style);\n }\n if (style.textContent !== text) style.textContent = text;\n}\n\n/**\n * Apply `cb(axisName, value)` for every pinned (disabled) axis whose value\n * is present on any surviving theme's `input`. Disabled axes don't appear\n * in `virtualAxes`, but CSS may still reference their pinned value on\n * compound selectors — every theme that survived filtering carries the\n * same pinned context per disabled axis, so sampling any theme works.\n */\nfunction forEachPinnedAxis(cb: (name: string, value: string) => void): void {\n const pinnedSample = permutations[0]?.input;\n if (!pinnedSample) return;\n for (const name of virtualDisabledAxes) {\n const value = pinnedSample[name];\n if (value !== undefined) cb(name, value);\n }\n}\n\n/**\n * Pick the theme name for a tuple, falling back to `defaultPermutation` and then\n * the first theme. Returns empty string when the project has no permutations so\n * callers can omit the attr instead of writing a made-up context name.\n */\nfunction matchPermutationName(tuple: Readonly<Record<string, string>>): string {\n const match = permutations.find((t) => {\n const input = t.input as Record<string, string>;\n return Object.keys(input).every((k) => input[k] === tuple[k]);\n });\n return match?.name ?? defaultPermutation ?? permutations[0]?.name ?? '';\n}\n\n/**\n * Write the composed permutation ID to `data-<prefix>-theme` plus one\n * `data-<prefix>-<axis>=<context>` per axis. Prefix follows `cssVarPrefix`\n * so the attr namespace and the emitted-CSS selectors stay in lockstep;\n * empty prefix keeps the bare `data-theme` form.\n */\nfunction setRootAxes(themeName: string, tuple: Readonly<Record<string, string>>): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n const themeAttr = dataAttr(cssVarPrefix, 'theme');\n if (themeName) root.setAttribute(themeAttr, themeName);\n else root.removeAttribute(themeAttr);\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 * 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(INIT_EVENT, {\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n permutations,\n defaultPermutation,\n permutationsResolved,\n diagnostics,\n cssVarPrefix,\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/** Look up a `Permutation.input` by composed name. Returns `undefined` if no theme matches. */\nfunction tupleForName(name: string): Record<string, string> | undefined {\n const match = permutations.find((t) => t.name === name);\n return match?.input;\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 globals: Record<string, unknown>,\n parameters: Record<string, Record<string, unknown>>,\n): Record<string, string> {\n const param = parameters[PARAM_KEY];\n const paramAxes = param?.['axes'];\n if (paramAxes && typeof paramAxes === 'object') {\n return normalizeTuple(paramAxes as Record<string, string>);\n }\n const paramPermutation = param?.['permutation'];\n if (typeof paramPermutation === 'string') {\n const hit = tupleForName(paramPermutation);\n if (hit) return normalizeTuple(hit);\n }\n const globalAxes = globals[AXES_GLOBAL_KEY];\n if (globalAxes && typeof globalAxes === 'object') {\n return normalizeTuple(globalAxes as Record<string, string>);\n }\n return defaultTuple();\n}\n\nfunction resolveColorFormat(globals: Record<string, unknown>): ColorFormat {\n const raw = globals[COLOR_FORMAT_GLOBAL_KEY];\n if (typeof raw === 'string' && (COLOR_FORMATS as readonly string[]).includes(raw)) {\n return raw as ColorFormat;\n }\n return 'hex';\n}\n\nconst themedDecorator: Decorator = (Story, context) => {\n const tuple = useMemo(\n () =>\n resolveTuple(\n context.globals as Record<string, unknown>,\n context.parameters as Record<string, Record<string, unknown>>,\n ),\n [context.globals, context.parameters],\n );\n const colorFormat = useMemo(\n () => resolveColorFormat(context.globals as Record<string, unknown>),\n [context.globals],\n );\n const themeName = useMemo(() => matchPermutationName(tuple), [tuple]);\n\n useEffect(() => {\n ensureStylesheet();\n broadcastInit();\n }, []);\n\n useEffect(() => {\n setRootAxes(themeName, tuple);\n }, [themeName, tuple]);\n\n const wrapperAttrs: Record<string, string> = {};\n if (themeName) wrapperAttrs[dataAttr(cssVarPrefix, 'theme')] = themeName;\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 permutations,\n permutationsResolved,\n activePermutation: themeName,\n activeAxes: tuple,\n cssVarPrefix,\n diagnostics,\n css,\n listing: virtualListing,\n }),\n [themeName, tuple],\n );\n\n return (\n <SwatchbookContext.Provider value={snapshot}>\n <PermutationContext.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 </ColorFormatContext.Provider>\n </AxesContext.Provider>\n </PermutationContext.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();\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: Record<string, unknown>): void => {\n ensureStylesheet();\n const tuple = resolveTuple(globals, {});\n setRootAxes(matchPermutationName(tuple), tuple);\n };\n const onGlobals = (payload: { globals?: Record<string, unknown> }): void => {\n if (payload.globals) 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 permutations: typeof permutations;\n defaultPermutation: typeof defaultPermutation;\n permutationsResolved: typeof permutationsResolved;\n diagnostics: typeof diagnostics;\n css: string;\n cssVarPrefix: string;\n listing: typeof virtualListing;\n}\nif (import.meta.hot) {\n import.meta.hot.on(HMR_EVENT, (payload: HmrSnapshot) => {\n if (typeof document !== 'undefined') {\n const bodyRules = `\nhtml, body {\n background: var(${payload.cssVarPrefix ? `--${payload.cssVarPrefix}-` : '--'}color-surface-default, Canvas);\n color: var(${payload.cssVarPrefix ? `--${payload.cssVarPrefix}-` : '--'}color-text-default, CanvasText);\n margin: 0;\n}\n`;\n const text = `${payload.css}\\n${bodyRules}`;\n let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!style) {\n style = document.createElement('style');\n style.id = STYLE_ELEMENT_ID;\n document.head.appendChild(style);\n }\n if (style.textContent !== text) style.textContent = text;\n }\n const channel = addons.getChannel();\n channel.emit(INIT_EVENT, {\n axes: payload.axes,\n disabledAxes: payload.disabledAxes,\n presets: payload.presets,\n permutations: payload.permutations,\n defaultPermutation: payload.defaultPermutation,\n permutationsResolved: payload.permutationsResolved,\n diagnostics: payload.diagnostics,\n cssVarPrefix: payload.cssVarPrefix,\n });\n channel.emit(TOKENS_UPDATED_EVENT, payload);\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAOA,SAAgB,SAAS,QAAgB,KAAqB;AAC5D,QAAO,SAAS,QAAQ,OAAO,GAAG,QAAQ,QAAQ;;;;;;;;;;ACmCpD,SAAS,EAAE,MAAsB;AAC/B,QAAO,eAAe,KAAK,aAAa,GAAG,SAAS,KAAK;;;;;;;AAQ3D,SAAS,mBAAyB;AAChC,KAAI,OAAO,aAAa,YAAa;CAQrC,MAAM,OAAO,GAAG,IAAI,IAPF;;oBAEA,EAAE,wBAAwB,CAAC;eAChC,EAAE,qBAAqB,CAAC;;;;CAKrC,IAAI,QAAQ,SAAS,eAAe,iBAAiB;AACrD,KAAI,CAAC,OAAO;AACV,UAAQ,SAAS,cAAc,QAAQ;AACvC,QAAM,KAAK;AACX,WAAS,KAAK,YAAY,MAAM;;AAElC,KAAI,MAAM,gBAAgB,KAAM,OAAM,cAAc;;;;;;;;;AAUtD,SAAS,kBAAkB,IAAiD;CAC1E,MAAM,eAAe,aAAa,IAAI;AACtC,KAAI,CAAC,aAAc;AACnB,MAAK,MAAM,QAAQA,cAAqB;EACtC,MAAM,QAAQ,aAAa;AAC3B,MAAI,UAAU,KAAA,EAAW,IAAG,MAAM,MAAM;;;;;;;;AAS5C,SAAS,qBAAqB,OAAiD;AAK7E,QAJc,aAAa,MAAM,MAAM;EACrC,MAAM,QAAQ,EAAE;AAChB,SAAO,OAAO,KAAK,MAAM,CAAC,OAAO,MAAM,MAAM,OAAO,MAAM,GAAG;GAC7D,EACY,QAAQ,sBAAsB,aAAa,IAAI,QAAQ;;;;;;;;AASvE,SAAS,YAAY,WAAmB,OAA+C;AACrF,KAAI,OAAO,aAAa,YAAa;CACrC,MAAM,OAAO,SAAS;CACtB,MAAM,YAAY,SAAS,cAAc,QAAQ;AACjD,KAAI,UAAW,MAAK,aAAa,WAAW,UAAU;KACjD,MAAK,gBAAgB,UAAU;AACpC,MAAK,MAAM,QAAQC,MAAa;EAC9B,MAAM,OAAO,SAAS,cAAc,KAAK,KAAK;EAC9C,MAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,UAAU,KAAA,EACZ,MAAK,gBAAgB,KAAK;MAE1B,MAAK,aAAa,MAAM,MAAM;;AAGlC,oBAAmB,MAAM,UAAU;AACjC,OAAK,aAAa,SAAS,cAAc,KAAK,EAAE,MAAM;GACtD;;;;;;;AAQJ,SAAS,gBAAsB;AACb,QAAO,YAAY,CAC3B,KAAK,YAAY;EACjBA;EACQD;EACLE;EACT;EACA;EACA;EACA;EACA;EACD,CAAC;;;AAIJ,SAAS,eAAuC;CAC9C,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,QAAQD,KAAa,KAAI,KAAK,QAAQ,KAAK;AACtD,QAAO;;;AAIT,SAAS,aAAa,MAAkD;AAEtE,QADc,aAAa,MAAM,MAAM,EAAE,SAAS,KAAK,EACzC;;;;;;;AAQhB,SAAS,eAAe,SAAmE;CACzF,MAAM,MAAM,cAAc;AAC1B,MAAK,MAAM,QAAQA,MAAa;EAC9B,MAAM,YAAY,QAAQ,KAAK;AAC/B,MAAI,cAAc,KAAA,KAAa,KAAK,SAAS,SAAS,UAAU,CAC9D,KAAI,KAAK,QAAQ;;AAGrB,QAAO;;;;;;;;;AAUT,SAAS,aACP,SACA,YACwB;CACxB,MAAM,QAAQ,WAAW;CACzB,MAAM,YAAY,QAAQ;AAC1B,KAAI,aAAa,OAAO,cAAc,SACpC,QAAO,eAAe,UAAoC;CAE5D,MAAM,mBAAmB,QAAQ;AACjC,KAAI,OAAO,qBAAqB,UAAU;EACxC,MAAM,MAAM,aAAa,iBAAiB;AAC1C,MAAI,IAAK,QAAO,eAAe,IAAI;;CAErC,MAAM,aAAa,QAAQ;AAC3B,KAAI,cAAc,OAAO,eAAe,SACtC,QAAO,eAAe,WAAqC;AAE7D,QAAO,cAAc;;AAGvB,SAAS,mBAAmB,SAA+C;CACzE,MAAM,MAAM,QAAQ;AACpB,KAAI,OAAO,QAAQ,YAAa,cAAoC,SAAS,IAAI,CAC/E,QAAO;AAET,QAAO;;AAGT,MAAM,mBAA8B,OAAO,YAAY;CACrD,MAAM,QAAQ,cAEV,aACE,QAAQ,SACR,QAAQ,WACT,EACH,CAAC,QAAQ,SAAS,QAAQ,WAAW,CACtC;CACD,MAAM,cAAc,cACZ,mBAAmB,QAAQ,QAAmC,EACpE,CAAC,QAAQ,QAAQ,CAClB;CACD,MAAM,YAAY,cAAc,qBAAqB,MAAM,EAAE,CAAC,MAAM,CAAC;AAErE,iBAAgB;AACd,oBAAkB;AAClB,iBAAe;IACd,EAAE,CAAC;AAEN,iBAAgB;AACd,cAAY,WAAW,MAAM;IAC5B,CAAC,WAAW,MAAM,CAAC;CAEtB,MAAM,eAAuC,EAAE;AAC/C,KAAI,UAAW,cAAa,SAAS,cAAc,QAAQ,IAAI;AAC/D,MAAK,MAAM,QAAQA,MAAa;EAC9B,MAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,UAAU,KAAA,EAAW,cAAa,SAAS,cAAc,KAAK,KAAK,IAAI;;AAE7E,oBAAmB,MAAM,UAAU;AACjC,eAAa,SAAS,cAAc,KAAK,IAAI;GAC7C;CAEF,MAAM,WAAW,eACR;EACCA;EACQD;EACLE;EACT;EACA;EACA,mBAAmB;EACnB,YAAY;EACZ;EACA;EACA;EACSC;EACV,GACD,CAAC,WAAW,MAAM,CACnB;AAED,QACE,oBAAC,kBAAkB,UAAnB;EAA4B,OAAO;YACjC,oBAAC,mBAAmB,UAApB;GAA6B,OAAO;aAClC,oBAAC,YAAY,UAAb;IAAsB,OAAO;cAC3B,oBAAC,mBAAmB,UAApB;KAA6B,OAAO;eAClC,oBAAC,OAAD;MACE,GAAI;MACJ,OAAO;OACL,SAAS;OACT,WAAW;OACZ;gBAED,oBAAC,OAAD,EAAS,CAAA;MACL,CAAA;KACsB,CAAA;IACT,CAAA;GACK,CAAA;EACH,CAAA;;;;;;AAQjC,MAAa,aAAiD,CAAC,gBAAgB;AAE/E,MAAa,cAAmD;EAC7D,kBAAkB;EACjB,MAAM;EACN,aAAa;EACd;EACA,0BAA0B;EACzB,MAAM;EACN,aAAa;EACd;CACF;AAED,SAAS,mBAA2C;CAClD,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,QAAQF,KAAa,KAAI,KAAK,QAAQ,KAAK;AACtD,QAAO;;AAGT,MAAa,iBAAyD;EACnE,kBAAkB,kBAAkB;EACpC,0BAA0B;CAC5B;;;;;;;;;;;;;AAcD,SAAS,2BAAiC;AACxC,KAAI,OAAO,aAAa,YAAa;CACrC,MAAM,UAAU,OAAO,YAAY;;;;;;;AAOnC,mBAAkB;AAClB,gBAAe;;;;;;;AAOf,SAAQ,GAAG,oBAAoB,cAAc;CAC7C,MAAM,SAAS,YAA2C;AACxD,oBAAkB;EAClB,MAAM,QAAQ,aAAa,SAAS,EAAE,CAAC;AACvC,cAAY,qBAAqB,MAAM,EAAE,MAAM;;CAEjD,MAAM,aAAa,YAAyD;AAC1E,MAAI,QAAQ,QAAS,OAAM,QAAQ,QAAQ;;AAE7C,SAAQ,GAAG,kBAAkB,UAAU;AACvC,SAAQ,GAAG,cAAc,UAAU;AACnC,SAAQ,GAAG,iBAAiB,UAAU;;AAGxC,0BAA0B;;;;;;;;AAS1B,SAAS,gCAAsC;AAC7C,KAAI,OAAO,aAAa,YAAa;CACrC,MAAM,UAAU,OAAO,YAAY;AACnC,UAAS,iBAAiB,mBAAmB;AAC3C,UAAQ,KAAK,wBAAwB;GACrC;;AAGJ,+BAA+B;AAuB/B,IAAI,OAAO,KAAK,IACd,QAAO,KAAK,IAAI,GAAG,YAAY,YAAyB;AACtD,KAAI,OAAO,aAAa,aAAa;EACnC,MAAM,YAAY;;oBAEJ,QAAQ,eAAe,KAAK,QAAQ,aAAa,KAAK,KAAK;eAChE,QAAQ,eAAe,KAAK,QAAQ,aAAa,KAAK,KAAK;;;;EAIpE,MAAM,OAAO,GAAG,QAAQ,IAAI,IAAI;EAChC,IAAI,QAAQ,SAAS,eAAe,iBAAiB;AACrD,MAAI,CAAC,OAAO;AACV,WAAQ,SAAS,cAAc,QAAQ;AACvC,SAAM,KAAK;AACX,YAAS,KAAK,YAAY,MAAM;;AAElC,MAAI,MAAM,gBAAgB,KAAM,OAAM,cAAc;;CAEtD,MAAM,UAAU,OAAO,YAAY;AACnC,SAAQ,KAAK,YAAY;EACvB,MAAM,QAAQ;EACd,cAAc,QAAQ;EACtB,SAAS,QAAQ;EACjB,cAAc,QAAQ;EACtB,oBAAoB,QAAQ;EAC5B,sBAAsB,QAAQ;EAC9B,aAAa,QAAQ;EACrB,cAAc,QAAQ;EACvB,CAAC;AACF,SAAQ,KAAK,sBAAsB,QAAQ;EAC3C"}
package/dist/preview.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { n as globalTypes, r as initialGlobals, t as decorators } from "./preview-D-mlU5Ip.mjs";
1
+ import { n as globalTypes, r as initialGlobals, t as decorators } from "./preview-CFEfgrWb.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.20.5",
3
+ "version": "0.51.0",
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>",
@@ -56,7 +56,9 @@
56
56
  "files": [
57
57
  "dist"
58
58
  ],
59
- "sideEffects": false,
59
+ "sideEffects": [
60
+ "./dist/preview.mjs"
61
+ ],
60
62
  "publishConfig": {
61
63
  "access": "public"
62
64
  },
@@ -70,14 +72,16 @@
70
72
  "dependencies": {
71
73
  "jiti": "^2.4.0",
72
74
  "picomatch": "^4.0.4",
73
- "@unpunnyfuns/swatchbook-blocks": "0.20.5",
74
- "@unpunnyfuns/swatchbook-core": "0.20.5",
75
- "@unpunnyfuns/swatchbook-switcher": "0.20.5"
75
+ "@unpunnyfuns/swatchbook-blocks": "0.51.0",
76
+ "@unpunnyfuns/swatchbook-core": "0.51.0",
77
+ "@unpunnyfuns/swatchbook-switcher": "0.51.0"
76
78
  },
77
79
  "peerDependencies": {
80
+ "@storybook/react-vite": "^10.3.5",
78
81
  "react": ">=18",
79
82
  "react-dom": ">=18",
80
- "storybook": "^10.3.5"
83
+ "storybook": "^10.3.5",
84
+ "vite": ">=5"
81
85
  },
82
86
  "devDependencies": {
83
87
  "@storybook/react-vite": "^10.3.5",
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants-DhgRF7SI.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const ADDON_ID = 'swatchbook';\nexport const TOOL_ID = `${ADDON_ID}/theme-switcher`;\nexport const PARAM_KEY = 'swatchbook';\n/** Canonical active-theme global: composed permutation ID (string). Read by toolbar, panel, blocks. */\nexport const GLOBAL_KEY = 'swatchbookTheme';\n/** Tuple companion: `Record<axisName, contextName>`. Optional — when present, takes precedence over the string global. */\nexport const AXES_GLOBAL_KEY = 'swatchbookAxes';\n/** Display-only color format for blocks (`hex` | `rgb` | `hsl` | `oklch` | `raw`). Emitted CSS is unaffected. */\nexport const COLOR_FORMAT_GLOBAL_KEY = 'swatchbookColorFormat';\n\nexport const VIRTUAL_MODULE_ID = 'virtual:swatchbook/tokens';\nexport const RESOLVED_VIRTUAL_MODULE_ID = `\\0${VIRTUAL_MODULE_ID}`;\n\n/**\n * Aggregate virtual module the addon's preview always imports. Its body\n * is a sequence of side-effect imports — one per integration that\n * declared `virtualModule.autoInject: true`. Integrations contributing\n * global stylesheets (Tailwind's `@theme` block, a rules-heavy CSS\n * file) can opt into this path so consumers never hand-write an\n * `import 'virtual:swatchbook/…'` line themselves; the body is empty\n * when no integration opts in.\n */\nexport const INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID = 'virtual:swatchbook/integration-side-effects';\nexport const RESOLVED_INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID = `\\0${INTEGRATION_SIDE_EFFECTS_VIRTUAL_ID}`;\n\nexport const STYLE_ELEMENT_ID = 'swatchbook-tokens';\n\n/** Channel event: preview → manager, carries theme list + mode. */\nexport const INIT_EVENT = 'swatchbook/init';\n/** Channel event: manager → preview, asks preview to re-emit INIT_EVENT.\n * Covers the race where the manager subscribes after the preview's\n * initial broadcast — without it the toolbar stays in \"loading…\" until\n * the user triggers anything that re-fires INIT_EVENT. */\nexport const INIT_REQUEST_EVENT = 'swatchbook/init-request';\n/** Channel event: preview → manager, fires once per `mousedown` on the\n * preview document. The toolbar popover listens for it so clicks landing\n * inside the preview iframe close the popover — a plain document-level\n * listener on the manager can't see iframe events. */\nexport const PREVIEW_MOUSEDOWN_EVENT = 'swatchbook/preview-mousedown';\n\n/** Channel event: preview → blocks, carries the fresh virtual-module\n * payload after a dev-time token refresh so blocks can re-render in\n * place without a full iframe reload. Fired by the preview in response\n * to the `HMR_EVENT` below. */\nexport const TOKENS_UPDATED_EVENT = 'swatchbook/tokens-updated';\n\n/** Custom Vite HMR event: plugin → preview. Preview forwards it to the\n * Storybook channel as {@link TOKENS_UPDATED_EVENT} so blocks can\n * update their snapshot. Kept distinct from the channel event so the\n * plugin doesn't need a Storybook-channel dependency. */\nexport const HMR_EVENT = 'swatchbook/tokens-updated';\n"],"mappings":";AAAA,MAAa,WAAW;AACxB,MAAa,UAAU,GAAG,SAAS;AACnC,MAAa,YAAY;;AAEzB,MAAa,aAAa;;AAE1B,MAAa,kBAAkB;;AAE/B,MAAa,0BAA0B;AAEvC,MAAa,oBAAoB;AACjC,MAAa,6BAA6B,KAAK;;;;;;;;;;AAW/C,MAAa,sCAAsC;AACnD,MAAa,+CAA+C,KAAK;AAEjE,MAAa,mBAAmB;;AAGhC,MAAa,aAAa;;;;;AAK1B,MAAa,qBAAqB;;;;;AAKlC,MAAa,0BAA0B;;;;;AAMvC,MAAa,uBAAuB;;;;;AAMpC,MAAa,YAAY"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"preview-D-mlU5Ip.mjs","names":["virtualDisabledAxes","virtualAxes","virtualPresets","virtualListing"],"sources":["../src/data-attr.ts","../src/preview.tsx"],"sourcesContent":["/**\n * Produce a prefixed `data-*` attribute name when `prefix` is set, bare\n * `data-<key>` otherwise. Duplicated from `@unpunnyfuns/swatchbook-core`'s\n * `dataAttr` so the preview bundle doesn't have to import from core —\n * core's top-level entry pulls in Node-only modules (`node:fs/promises`\n * via the loader) that throw when evaluated in the browser.\n */\nexport function dataAttr(prefix: string, key: string): string {\n return prefix ? `data-${prefix}-${key}` : `data-${key}`;\n}\n","/// <reference types=\"vite/client\" />\nimport type { Decorator, Preview } from '@storybook/react-vite';\nimport { useEffect, useMemo } from 'react';\nimport { addons } from 'storybook/preview-api';\nimport { dataAttr } from '#/data-attr.ts';\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 defaultTheme,\n diagnostics,\n disabledAxes as virtualDisabledAxes,\n listing as virtualListing,\n presets as virtualPresets,\n themes,\n themesResolved,\n} from 'virtual:swatchbook/tokens';\nimport {\n AxesContext,\n COLOR_FORMATS,\n type ColorFormat,\n ColorFormatContext,\n type ProjectSnapshot,\n SwatchbookContext,\n ThemeContext,\n} from '@unpunnyfuns/swatchbook-blocks';\nimport {\n AXES_GLOBAL_KEY,\n COLOR_FORMAT_GLOBAL_KEY,\n GLOBAL_KEY,\n HMR_EVENT,\n INIT_EVENT,\n INIT_REQUEST_EVENT,\n PREVIEW_MOUSEDOWN_EVENT,\n PARAM_KEY,\n STYLE_ELEMENT_ID,\n TOKENS_UPDATED_EVENT,\n} from '#/constants.ts';\n\n/** CSS var name with the active prefix applied. */\nfunction v(name: string): string {\n return cssVarPrefix ? `--${cssVarPrefix}-${name}` : `--${name}`;\n}\n\n/**\n * Inject the per-theme stylesheet plus a tiny `html, body { ... }` block so\n * the iframe's own chrome (outside any decorator wrapper — Docs mode,\n * autodocs, empty gutters) also picks up the active theme.\n */\nfunction ensureStylesheet(): void {\n if (typeof document === 'undefined') return;\n const bodyRules = `\nhtml, body {\n background: var(${v('color-surface-default')}, Canvas);\n color: var(${v('color-text-default')}, CanvasText);\n margin: 0;\n}\n`;\n const text = `${css}\\n${bodyRules}`;\n let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!style) {\n style = document.createElement('style');\n style.id = STYLE_ELEMENT_ID;\n document.head.appendChild(style);\n }\n if (style.textContent !== text) style.textContent = text;\n}\n\n/**\n * Apply `cb(axisName, value)` for every pinned (disabled) axis whose value\n * is present on any surviving theme's `input`. Disabled axes don't appear\n * in `virtualAxes`, but CSS may still reference their pinned value on\n * compound selectors — every theme that survived filtering carries the\n * same pinned context per disabled axis, so sampling any theme works.\n */\nfunction forEachPinnedAxis(cb: (name: string, value: string) => void): void {\n const pinnedSample = themes[0]?.input;\n if (!pinnedSample) return;\n for (const name of virtualDisabledAxes) {\n const value = pinnedSample[name];\n if (value !== undefined) cb(name, value);\n }\n}\n\n/**\n * Pick the theme name for a tuple, falling back to `defaultTheme` and then\n * the first theme. Returns empty string when the project has no themes so\n * callers can omit the attr instead of writing a made-up context name.\n */\nfunction matchThemeName(tuple: Readonly<Record<string, string>>): string {\n const match = themes.find((t) => {\n const input = t.input as Record<string, string>;\n return Object.keys(input).every((k) => input[k] === tuple[k]);\n });\n return match?.name ?? defaultTheme ?? themes[0]?.name ?? '';\n}\n\n/**\n * Write the composed permutation ID to `data-<prefix>-theme` plus one\n * `data-<prefix>-<axis>=<context>` per axis. Prefix follows `cssVarPrefix`\n * so the attr namespace and the emitted-CSS selectors stay in lockstep;\n * empty prefix keeps the bare `data-theme` form.\n */\nfunction setRootAxes(themeName: string, tuple: Readonly<Record<string, string>>): void {\n if (typeof document === 'undefined') return;\n const root = document.documentElement;\n const themeAttr = dataAttr(cssVarPrefix, 'theme');\n if (themeName) root.setAttribute(themeAttr, themeName);\n else root.removeAttribute(themeAttr);\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 * 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(INIT_EVENT, {\n axes: virtualAxes,\n disabledAxes: virtualDisabledAxes,\n presets: virtualPresets,\n themes,\n defaultTheme,\n themesResolved,\n diagnostics,\n cssVarPrefix,\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/** Look up a `Theme.input` by composed name. Returns `undefined` if no theme matches. */\nfunction tupleForName(name: string): Record<string, string> | undefined {\n const match = themes.find((t) => t.name === name);\n return match?.input;\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 four input channels, in priority order:\n * 1. `parameters.swatchbook.axes` — per-story tuple.\n * 2. `parameters.swatchbook.theme` — per-story composed name (legacy).\n * 3. `globals.swatchbookAxes` — toolbar-set tuple.\n * 4. `globals.swatchbookTheme` — toolbar-set composed name.\n * 5. virtual module default.\n */\nfunction resolveTuple(\n globals: Record<string, unknown>,\n parameters: Record<string, Record<string, unknown>>,\n): Record<string, string> {\n const param = parameters[PARAM_KEY];\n const paramAxes = param?.['axes'];\n if (paramAxes && typeof paramAxes === 'object') {\n return normalizeTuple(paramAxes as Record<string, string>);\n }\n const paramTheme = param?.['theme'];\n if (typeof paramTheme === 'string') {\n const hit = tupleForName(paramTheme);\n if (hit) return normalizeTuple(hit);\n }\n const globalAxes = globals[AXES_GLOBAL_KEY];\n if (globalAxes && typeof globalAxes === 'object') {\n return normalizeTuple(globalAxes as Record<string, string>);\n }\n const globalTheme = globals[GLOBAL_KEY];\n if (typeof globalTheme === 'string') {\n const hit = tupleForName(globalTheme);\n if (hit) return normalizeTuple(hit);\n }\n return defaultTuple();\n}\n\nfunction resolveColorFormat(globals: Record<string, unknown>): ColorFormat {\n const raw = globals[COLOR_FORMAT_GLOBAL_KEY];\n if (typeof raw === 'string' && (COLOR_FORMATS as readonly string[]).includes(raw)) {\n return raw as ColorFormat;\n }\n return 'hex';\n}\n\nconst themedDecorator: Decorator = (Story, context) => {\n const tuple = useMemo(\n () =>\n resolveTuple(\n context.globals as Record<string, unknown>,\n context.parameters as Record<string, Record<string, unknown>>,\n ),\n [context.globals, context.parameters],\n );\n const colorFormat = useMemo(\n () => resolveColorFormat(context.globals as Record<string, unknown>),\n [context.globals],\n );\n const themeName = useMemo(() => matchThemeName(tuple), [tuple]);\n\n useEffect(() => {\n ensureStylesheet();\n broadcastInit();\n }, []);\n\n useEffect(() => {\n setRootAxes(themeName, tuple);\n }, [themeName, tuple]);\n\n const wrapperAttrs: Record<string, string> = {};\n if (themeName) wrapperAttrs[dataAttr(cssVarPrefix, 'theme')] = themeName;\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 themes,\n themesResolved,\n activeTheme: themeName,\n activeAxes: tuple,\n cssVarPrefix,\n diagnostics,\n css,\n listing: virtualListing,\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 </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 [GLOBAL_KEY]: {\n name: 'Theme',\n description: 'Active swatchbook theme (composed permutation ID).',\n },\n [AXES_GLOBAL_KEY]: {\n name: 'Axes',\n description: 'Per-axis context selection. Takes precedence over the composed theme name.',\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 [GLOBAL_KEY]: defaultTheme ?? themes[0]?.name ?? '',\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();\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: Record<string, unknown>): void => {\n ensureStylesheet();\n const tuple = resolveTuple(globals, {});\n setRootAxes(matchThemeName(tuple), tuple);\n };\n const onGlobals = (payload: { globals?: Record<string, unknown> }): void => {\n if (payload.globals) 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 themes: typeof themes;\n defaultTheme: typeof defaultTheme;\n themesResolved: typeof themesResolved;\n diagnostics: typeof diagnostics;\n css: string;\n cssVarPrefix: string;\n listing: typeof virtualListing;\n}\nif (import.meta.hot) {\n import.meta.hot.on(HMR_EVENT, (payload: HmrSnapshot) => {\n if (typeof document !== 'undefined') {\n const bodyRules = `\nhtml, body {\n background: var(${payload.cssVarPrefix ? `--${payload.cssVarPrefix}-` : '--'}color-surface-default, Canvas);\n color: var(${payload.cssVarPrefix ? `--${payload.cssVarPrefix}-` : '--'}color-text-default, CanvasText);\n margin: 0;\n}\n`;\n const text = `${payload.css}\\n${bodyRules}`;\n let style = document.getElementById(STYLE_ELEMENT_ID) as HTMLStyleElement | null;\n if (!style) {\n style = document.createElement('style');\n style.id = STYLE_ELEMENT_ID;\n document.head.appendChild(style);\n }\n if (style.textContent !== text) style.textContent = text;\n }\n const channel = addons.getChannel();\n channel.emit(INIT_EVENT, {\n axes: payload.axes,\n disabledAxes: payload.disabledAxes,\n presets: payload.presets,\n themes: payload.themes,\n defaultTheme: payload.defaultTheme,\n themesResolved: payload.themesResolved,\n diagnostics: payload.diagnostics,\n cssVarPrefix: payload.cssVarPrefix,\n });\n channel.emit(TOKENS_UPDATED_EVENT, payload);\n });\n}\n"],"mappings":"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"}