@unpunnyfuns/swatchbook-addon 0.2.1 → 0.2.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"manager.mjs","names":["h"],"sources":["../src/panel.tsx","../src/manager.tsx"],"sourcesContent":["import React, {\n useCallback,\n useEffect,\n useMemo,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactElement,\n} from 'react';\nimport { Placeholder, ScrollArea } from 'storybook/internal/components';\nimport { addons, useGlobals } from 'storybook/manager-api';\nimport { AXES_GLOBAL_KEY, GLOBAL_KEY, INIT_EVENT } from '#/constants.ts';\n\n/** `React.createElement` alias so the manager bundle avoids `react/jsx-runtime`. */\nconst h = React.createElement;\n\ninterface VirtualToken {\n $type?: string;\n $value?: unknown;\n $description?: string;\n}\n\ninterface VirtualTheme {\n name: string;\n input: Record<string, string>;\n sources: string[];\n}\n\ninterface VirtualAxis {\n name: string;\n contexts: readonly string[];\n default: string;\n description?: string;\n source: 'resolver' | 'layered' | 'synthetic';\n}\n\ntype DiagnosticSeverity = 'error' | 'warn' | 'info';\n\ninterface VirtualDiagnostic {\n severity: DiagnosticSeverity;\n group: string;\n message: string;\n filename?: string;\n line?: number;\n column?: number;\n}\n\ninterface InitPayload {\n axes: VirtualAxis[];\n disabledAxes: readonly string[];\n themes: VirtualTheme[];\n defaultTheme: string | null;\n themesResolved: Record<string, Record<string, VirtualToken>>;\n diagnostics: VirtualDiagnostic[];\n cssVarPrefix: string;\n}\n\nfunction usePayload(): InitPayload | null {\n const [payload, setPayload] = useState<InitPayload | null>(null);\n useEffect(() => {\n const channel = addons.getChannel();\n const onInit = (next: InitPayload): void => setPayload(next);\n channel.on(INIT_EVENT, onInit);\n return () => {\n channel.off(INIT_EVENT, onInit);\n };\n }, []);\n return payload;\n}\n\nfunction makeCssVarName(path: string, prefix: string): string {\n const tail = path.replaceAll('.', '-');\n return prefix ? `--${prefix}-${tail}` : `--${tail}`;\n}\n\nasync function copy(text: string): Promise<void> {\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n /* clipboard access denied — no fallback, the user can read the var name from the row */\n }\n}\n\n/** Format a token `$value` into a short display string. */\nfunction formatValue(value: unknown): string {\n if (value == null) return '';\n if (typeof value === 'string' || typeof value === 'number') return String(value);\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>;\n if (typeof v['hex'] === 'string') return v['hex'] as string;\n if ('value' in v && 'unit' in v) return `${String(v['value'])}${String(v['unit'])}`;\n return JSON.stringify(value).slice(0, 80);\n }\n return String(value);\n}\n\nconst containerStyle: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n};\n\nconst headerStyle: CSSProperties = {\n padding: '8px 12px',\n borderBottom: '1px solid rgba(128,128,128,0.2)',\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n};\n\nconst axisIndicatorStyle: CSSProperties = {\n fontSize: 11,\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace',\n opacity: 0.7,\n};\n\nconst treeWrapperStyle: CSSProperties = {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n padding: 8,\n};\n\nconst groupRowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '4px 6px',\n borderRadius: 4,\n cursor: 'pointer',\n userSelect: 'none',\n outline: 'none',\n};\n\nconst leafRowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '4px 6px',\n borderRadius: 4,\n cursor: 'pointer',\n border: 'none',\n background: 'transparent',\n color: 'inherit',\n width: '100%',\n textAlign: 'left',\n fontFamily: 'inherit',\n fontSize: 'inherit',\n outline: 'none',\n};\n\nconst caretStyle: CSSProperties = {\n display: 'inline-block',\n width: 12,\n textAlign: 'center',\n opacity: 0.6,\n};\n\nconst treeUlStyle: CSSProperties = { listStyle: 'none', margin: 0, padding: 0 };\n\nconst nestedUlStyle: CSSProperties = {\n listStyle: 'none',\n margin: 0,\n paddingLeft: 18,\n borderLeft: '1px solid rgba(128,128,128,0.2)',\n};\n\nconst typePillStyle: CSSProperties = {\n display: 'inline-block',\n padding: '1px 6px',\n borderRadius: 4,\n fontSize: 10,\n letterSpacing: 0.5,\n textTransform: 'uppercase',\n background: 'rgba(128,128,128,0.15)',\n};\n\nconst valueStyle: CSSProperties = {\n marginLeft: 'auto',\n opacity: 0.7,\n fontSize: 11,\n maxWidth: '40%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\n\nconst countStyle: CSSProperties = {\n marginLeft: 'auto',\n fontSize: 11,\n opacity: 0.7,\n};\n\nconst swatchStyle: CSSProperties = {\n display: 'inline-block',\n width: 14,\n height: 14,\n borderRadius: 3,\n border: '1px solid rgba(128,128,128,0.3)',\n marginLeft: 8,\n};\n\nconst searchInputStyle: CSSProperties = {\n width: '100%',\n padding: '4px 8px',\n fontSize: 12,\n border: '1px solid rgba(128,128,128,0.3)',\n borderRadius: 4,\n background: 'transparent',\n color: 'inherit',\n};\n\ninterface LeafNode {\n kind: 'leaf';\n segment: string;\n path: string;\n token: VirtualToken;\n}\n\ninterface GroupNode {\n kind: 'group';\n segment: string;\n path: string;\n children: TreeNode[];\n}\n\ntype TreeNode = LeafNode | GroupNode;\n\nfunction buildTree(resolved: Record<string, VirtualToken>): TreeNode[] {\n const rootNode: GroupNode = { kind: 'group', segment: '', path: '', children: [] };\n for (const [path, token] of Object.entries(resolved)) {\n const segments = path.split('.');\n let node: GroupNode = rootNode;\n for (let i = 0; i < segments.length - 1; i += 1) {\n const seg = segments[i] as string;\n const prefix = segments.slice(0, i + 1).join('.');\n let child = node.children.find(\n (c): c is GroupNode => c.kind === 'group' && c.segment === seg,\n );\n if (!child) {\n child = { kind: 'group', segment: seg, path: prefix, children: [] };\n node.children.push(child);\n }\n node = child;\n }\n const leafSegment = segments[segments.length - 1] as string;\n node.children.push({ kind: 'leaf', segment: leafSegment, path, token });\n }\n sortTree(rootNode);\n return rootNode.children;\n}\n\nfunction sortTree(node: GroupNode): void {\n node.children.sort((a, b) => {\n if (a.kind !== b.kind) return a.kind === 'group' ? -1 : 1;\n return a.segment.localeCompare(b.segment);\n });\n for (const c of node.children) {\n if (c.kind === 'group') sortTree(c);\n }\n}\n\nfunction collectInitialExpanded(nodes: TreeNode[], remainingDepth: number, out: Set<string>): void {\n if (remainingDepth <= 0) return;\n for (const node of nodes) {\n if (node.kind !== 'group') continue;\n out.add(node.path);\n collectInitialExpanded(node.children, remainingDepth - 1, out);\n }\n}\n\nfunction countLeaves(node: TreeNode): number {\n if (node.kind === 'leaf') return 1;\n let n = 0;\n for (const c of node.children) n += countLeaves(c);\n return n;\n}\n\nfunction filterTree(nodes: TreeNode[], query: string): TreeNode[] {\n if (!query) return nodes;\n const out: TreeNode[] = [];\n for (const node of nodes) {\n if (node.kind === 'leaf') {\n if (node.path.toLowerCase().includes(query)) out.push(node);\n continue;\n }\n const filteredChildren = filterTree(node.children, query);\n if (filteredChildren.length > 0) {\n out.push({ ...node, children: filteredChildren });\n }\n }\n return out;\n}\n\nfunction collectAllGroupPaths(nodes: TreeNode[], out: Set<string>): void {\n for (const node of nodes) {\n if (node.kind === 'group') {\n out.add(node.path);\n collectAllGroupPaths(node.children, out);\n }\n }\n}\n\ninterface PanelProps {\n active: boolean;\n}\n\nexport function DesignTokensPanel({ active }: PanelProps): ReactElement | null {\n const payload = usePayload();\n const [globals] = useGlobals();\n const [query, setQuery] = useState('');\n\n const axes = useMemo(() => payload?.axes ?? [], [payload]);\n const themes = useMemo(() => payload?.themes ?? [], [payload]);\n const globalAxes = globals[AXES_GLOBAL_KEY] as Record<string, string> | undefined;\n const globalTheme = globals[GLOBAL_KEY] as string | undefined;\n\n const tuple: Record<string, string> = useMemo(() => {\n const out: Record<string, string> = {};\n for (const axis of axes) out[axis.name] = axis.default;\n if (globalAxes && typeof globalAxes === 'object') {\n for (const axis of axes) {\n const candidate = globalAxes[axis.name];\n if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;\n }\n return out;\n }\n if (globalTheme) {\n const match = themes.find((t) => t.name === globalTheme);\n if (match) {\n for (const axis of axes) {\n const candidate = match.input[axis.name];\n if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;\n }\n }\n }\n return out;\n }, [axes, themes, globalAxes, globalTheme]);\n\n const themeName = useMemo(() => {\n const match = themes.find((t) => {\n const input = t.input;\n return Object.keys(input).every((k) => input[k] === tuple[k]);\n });\n return match?.name ?? globalTheme ?? payload?.defaultTheme ?? '';\n }, [themes, tuple, globalTheme, payload]);\n\n const prefix = payload?.cssVarPrefix ?? '';\n const tokens = useMemo(() => payload?.themesResolved[themeName] ?? {}, [payload, themeName]);\n const tokenCount = Object.keys(tokens).length;\n\n const tree = useMemo(() => buildTree(tokens), [tokens]);\n const lowerQuery = query.toLowerCase();\n const filtered = useMemo(() => filterTree(tree, lowerQuery), [tree, lowerQuery]);\n\n const initialExpanded = useMemo(() => {\n const out = new Set<string>();\n collectInitialExpanded(tree, 1, out);\n return out;\n }, [tree]);\n\n const [expanded, setExpanded] = useState<Set<string>>(initialExpanded);\n useEffect(() => {\n setExpanded(initialExpanded);\n }, [initialExpanded]);\n\n // When searching, expand every matching group so hits are visible.\n const displayExpanded = useMemo(() => {\n if (!lowerQuery) return expanded;\n const all = new Set<string>();\n collectAllGroupPaths(filtered, all);\n return all;\n }, [expanded, filtered, lowerQuery]);\n\n const toggle = useCallback((path: string): void => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(path)) next.delete(path);\n else next.add(path);\n return next;\n });\n }, []);\n\n const handleLeafClick = useCallback(\n (path: string) => {\n const varName = makeCssVarName(path, prefix);\n void copy(`var(${varName})`);\n },\n [prefix],\n );\n\n if (!active) return null;\n\n if (!payload) {\n return h(Placeholder, null, 'Waiting for swatchbook preview…');\n }\n\n const showAxisIndicator =\n axes.length > 1 || (axes.length === 1 && axes[0]?.source !== 'synthetic');\n const axisIndicatorText = axes\n .map((axis) => `${axis.name}: ${tuple[axis.name] ?? axis.default}`)\n .join(' · ');\n const disabledAxes = payload?.disabledAxes ?? [];\n const pinnedSample = themes[0]?.input ?? {};\n const disabledIndicatorText = disabledAxes\n .map((name) => `${name}: ${pinnedSample[name] ?? '?'} · pinned`)\n .join(' · ');\n\n return h(\n 'div',\n { style: containerStyle },\n h(\n 'div',\n { style: headerStyle },\n showAxisIndicator &&\n h(\n 'div',\n {\n style: axisIndicatorStyle,\n 'data-testid': 'design-tokens-panel-axis-indicator',\n },\n axisIndicatorText,\n ),\n disabledAxes.length > 0 &&\n h(\n 'div',\n {\n style: { ...axisIndicatorStyle, opacity: 0.5 },\n 'data-testid': 'design-tokens-panel-disabled-axes-indicator',\n },\n disabledIndicatorText,\n ),\n h('input', {\n style: searchInputStyle,\n type: 'search',\n placeholder: `Search ${tokenCount} tokens in ${themeName}…`,\n 'aria-label': 'Search tokens',\n value: query,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => setQuery(e.target.value),\n }),\n ),\n h(DiagnosticsSection, { diagnostics: payload.diagnostics }),\n h(\n ScrollArea,\n { vertical: true },\n filtered.length === 0\n ? h(Placeholder, null, query ? 'No tokens match this filter.' : 'No tokens in this theme.')\n : h(\n 'div',\n { style: treeWrapperStyle },\n h(\n 'ul',\n { style: treeUlStyle, role: 'tree' },\n filtered.map((node) =>\n h(TreeRow, {\n key: node.path || node.segment,\n node,\n expanded: displayExpanded,\n onToggle: toggle,\n onLeafClick: handleLeafClick,\n prefix,\n }),\n ),\n ),\n ),\n ),\n );\n}\n\ninterface TreeRowProps {\n node: TreeNode;\n expanded: Set<string>;\n onToggle(path: string): void;\n onLeafClick(path: string): void;\n prefix: string;\n}\n\nfunction TreeRow({ node, expanded, onToggle, onLeafClick, prefix }: TreeRowProps): ReactElement {\n if (node.kind === 'leaf') {\n return h(LeafRow, { node, onLeafClick, prefix });\n }\n const isOpen = expanded.has(node.path);\n const onKey = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onToggle(node.path);\n }\n };\n return h(\n 'li',\n { role: 'treeitem', 'aria-expanded': isOpen },\n h(\n 'div',\n {\n role: 'button',\n tabIndex: 0,\n style: groupRowStyle,\n onClick: () => onToggle(node.path),\n onKeyDown: onKey,\n 'data-path': node.path,\n 'data-testid': 'design-tokens-panel-group',\n },\n h('span', { style: caretStyle, 'aria-hidden': true }, isOpen ? '▾' : '▸'),\n h('span', null, node.segment),\n h('span', { style: countStyle }, countLeaves(node)),\n ),\n isOpen &&\n h(\n 'ul',\n { style: nestedUlStyle, role: 'group' },\n node.children.map((c) =>\n h(TreeRow, {\n key: c.path || c.segment,\n node: c,\n expanded,\n onToggle,\n onLeafClick,\n prefix,\n }),\n ),\n ),\n );\n}\n\ninterface LeafRowProps {\n node: LeafNode;\n onLeafClick(path: string): void;\n prefix: string;\n}\n\nfunction LeafRow({ node, onLeafClick, prefix }: LeafRowProps): ReactElement {\n const type = node.token.$type ?? '';\n const value = node.token.$value;\n const displayValue = formatValue(value);\n const isColor = type === 'color' && typeof value === 'object' && value !== null;\n const colorPreview =\n isColor && typeof (value as Record<string, unknown>)['hex'] === 'string'\n ? ((value as Record<string, unknown>)['hex'] as string)\n : null;\n\n const varName = makeCssVarName(node.path, prefix);\n\n return h(\n 'li',\n { role: 'treeitem' },\n h(\n 'button',\n {\n type: 'button',\n style: leafRowStyle,\n onClick: () => onLeafClick(node.path),\n title: `Click to copy var(${varName})`,\n 'data-path': node.path,\n 'data-testid': 'design-tokens-panel-leaf',\n },\n h('span', { style: caretStyle, 'aria-hidden': true }, '•'),\n h('span', null, node.segment),\n type && h('span', { style: typePillStyle }, type),\n h('span', { style: valueStyle }, displayValue),\n colorPreview &&\n h('span', {\n style: { ...swatchStyle, background: colorPreview },\n 'aria-hidden': true,\n }),\n ),\n );\n}\n\nconst severityStyle: Record<DiagnosticSeverity, CSSProperties> = {\n error: { color: '#d64545' },\n warn: { color: '#b08900' },\n info: { opacity: 0.6 },\n};\n\nconst severityLabel: Record<DiagnosticSeverity, string> = {\n error: 'ERROR',\n warn: 'WARN',\n info: 'INFO',\n};\n\nconst diagnosticsSectionStyle: CSSProperties = {\n borderBottom: '1px solid rgba(128,128,128,0.2)',\n};\n\nconst diagnosticsSummaryStyle: CSSProperties = {\n padding: '10px 12px',\n fontSize: 12,\n cursor: 'pointer',\n userSelect: 'none',\n listStyle: 'none',\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n};\n\nconst diagnosticRowStyle: CSSProperties = {\n display: 'grid',\n gridTemplateColumns: '60px 1fr',\n gap: 12,\n padding: '8px 12px',\n fontSize: 12,\n borderTop: '1px solid rgba(128,128,128,0.12)',\n};\n\ninterface DiagnosticsSectionProps {\n diagnostics: readonly VirtualDiagnostic[];\n}\n\nfunction DiagnosticsSection({ diagnostics }: DiagnosticsSectionProps): ReactElement {\n const counts = diagnostics.reduce(\n (acc, d) => {\n acc[d.severity] = (acc[d.severity] ?? 0) + 1;\n return acc;\n },\n { error: 0, warn: 0, info: 0 } as Record<DiagnosticSeverity, number>,\n );\n\n const hasErrorsOrWarnings = counts.error > 0 || counts.warn > 0;\n\n const summaryText = (() => {\n if (diagnostics.length === 0) return '✔ OK · no diagnostics';\n const parts: string[] = [];\n if (counts.error > 0) parts.push(`✖ ${counts.error} error${counts.error === 1 ? '' : 's'}`);\n if (counts.warn > 0) parts.push(`⚠ ${counts.warn} warning${counts.warn === 1 ? '' : 's'}`);\n if (counts.info > 0) parts.push(`${counts.info} info`);\n return parts.join(' · ');\n })();\n\n const summaryColor = (() => {\n if (diagnostics.length === 0) return '#30a46c';\n if (counts.error > 0) return '#d64545';\n if (counts.warn > 0) return '#b08900';\n return 'inherit';\n })();\n\n return h(\n 'details',\n {\n style: diagnosticsSectionStyle,\n open: hasErrorsOrWarnings,\n 'data-testid': 'design-tokens-panel-diagnostics',\n },\n h(\n 'summary',\n { style: { ...diagnosticsSummaryStyle, color: summaryColor, fontWeight: 600 } },\n h('span', null, 'Diagnostics'),\n h('span', { style: { fontWeight: 400 } }, summaryText),\n ),\n diagnostics.length === 0\n ? null\n : h(\n 'div',\n null,\n diagnostics.map((d, i) =>\n h(\n 'div',\n { key: `${d.group}-${i}`, style: diagnosticRowStyle },\n h(\n 'span',\n { style: { ...severityStyle[d.severity], fontWeight: 600, fontSize: 10 } },\n severityLabel[d.severity],\n ),\n h(\n 'div',\n null,\n h('div', null, d.message),\n (d.filename || d.group) &&\n h(\n 'div',\n { style: { opacity: 0.5, fontSize: 10, marginTop: 4 } },\n [d.group, d.filename, d.line ? `:${d.line}` : ''].filter(Boolean).join(' · '),\n ),\n ),\n ),\n ),\n ),\n );\n}\n","import 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 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 PANEL_ID,\n TOOL_ID,\n} from '#/constants.ts';\nimport { DesignTokensPanel } from '#/panel.tsx';\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 */\nconst h = React.createElement;\n\ninterface AxisEntry {\n name: string;\n contexts: readonly string[];\n default: string;\n description?: string;\n source: 'resolver' | 'layered' | 'synthetic';\n}\n\ninterface ThemeEntry {\n name: string;\n input: Record<string, string>;\n sources: string[];\n}\n\ninterface PresetEntry {\n name: string;\n axes: Partial<Record<string, string>>;\n description?: string;\n}\n\ninterface InitPayload {\n axes: readonly AxisEntry[];\n presets: readonly PresetEntry[];\n themes: ThemeEntry[];\n defaultTheme: string | null;\n}\n\nconst EMPTY_AXES: readonly AxisEntry[] = [];\nconst EMPTY_PRESETS: readonly PresetEntry[] = [];\nconst EMPTY_THEMES: 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 * Treat the `{ name: 'theme', source: 'synthetic' }` axis — the one core\n * fabricates for single-theme projects with no resolver — as a special case\n * that uses the label \"Theme\" instead of the axis name. Authored single-axis\n * resolvers keep their real axis name (e.g. `mode`).\n */\nfunction displayLabelFor(axis: AxisEntry): string {\n if (axis.source === 'synthetic' && axis.name === 'theme') return 'Theme';\n return axis.name;\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). Matches 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 tuplesEqual(\n a: Readonly<Record<string, string>>,\n b: Readonly<Record<string, string>>,\n axes: readonly AxisEntry[],\n): boolean {\n for (const axis of axes) {\n if (a[axis.name] !== b[axis.name]) return false;\n }\n return true;\n}\n\nconst SECTION_LABEL_STYLE: React.CSSProperties = {\n padding: '8px 12px 4px',\n fontSize: 11,\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n opacity: 0.6,\n};\n\nconst SECTION_BODY_STYLE: React.CSSProperties = {\n padding: '0 12px 10px',\n display: 'flex',\n flexWrap: 'wrap',\n gap: 4,\n};\n\nconst AXIS_ROW_STYLE: React.CSSProperties = {\n padding: '0 12px 10px',\n display: 'grid',\n gridTemplateColumns: 'max-content 1fr',\n columnGap: 12,\n rowGap: 4,\n alignItems: 'center',\n};\n\nconst AXIS_LABEL_STYLE: React.CSSProperties = {\n fontSize: 12,\n fontWeight: 600,\n opacity: 0.85,\n};\n\nconst AXIS_PILLS_STYLE: React.CSSProperties = {\n display: 'flex',\n flexWrap: 'wrap',\n gap: 4,\n};\n\nconst OPTION_PILL_BASE: React.CSSProperties = {\n padding: '3px 8px',\n borderRadius: 4,\n fontSize: 12,\n lineHeight: '18px',\n // Longhand border properties (not the `border` shorthand) so\n // active → inactive only updates `borderColor`'s value instead of\n // *removing* the key from inline style. Removing it lets Storybook's\n // theme paint a stray border-color on the previously-selected pill.\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: 'transparent',\n background: 'transparent',\n cursor: 'pointer',\n color: 'inherit',\n outline: 'none',\n boxShadow: 'none',\n};\n\nconst OPTION_PILL_ACTIVE: React.CSSProperties = {\n ...OPTION_PILL_BASE,\n fontWeight: 600,\n background: 'rgba(0, 122, 255, 0.12)',\n borderColor: 'rgba(0, 122, 255, 0.45)',\n};\n\nconst PRESET_PILL_MODIFIED: React.CSSProperties = {\n display: 'inline-block',\n width: 6,\n height: 6,\n marginLeft: 6,\n borderRadius: '50%',\n background: 'currentColor',\n opacity: 0.6,\n verticalAlign: 'middle',\n};\n\nconst DIVIDER_STYLE: React.CSSProperties = {\n height: 1,\n background: 'currentColor',\n opacity: 0.1,\n margin: '2px 8px',\n};\n\ninterface OptionPillProps {\n label: string;\n active: boolean;\n title?: string;\n onClick: () => void;\n trailing?: ReactElement | null;\n}\n\nfunction OptionPill({ label, active, title, onClick, trailing }: OptionPillProps): ReactElement {\n return h(\n 'button',\n {\n type: 'button',\n title,\n onClick,\n // Skip focus on mouse click so Storybook's `:focus` border-color\n // theming doesn't stick on the previously-clicked pill. Keyboard\n // tabbing still lands focus normally — preventDefault on mousedown\n // only blocks the implicit focus-on-click behavior.\n onMouseDown: (event) => event.preventDefault(),\n style: active ? OPTION_PILL_ACTIVE : OPTION_PILL_BASE,\n },\n label,\n trailing ?? null,\n );\n}\n\ninterface PresetsSectionProps {\n presets: readonly PresetEntry[];\n axes: readonly AxisEntry[];\n defaults: Readonly<Record<string, string>>;\n activeTuple: Readonly<Record<string, string>>;\n lastApplied: string | null;\n onApply: (preset: PresetEntry) => void;\n}\n\nfunction PresetsSection({\n presets,\n axes,\n defaults,\n activeTuple,\n lastApplied,\n onApply,\n}: PresetsSectionProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { style: SECTION_LABEL_STYLE }, 'Presets'),\n h(\n 'div',\n { style: SECTION_BODY_STYLE },\n ...presets.map((preset) => {\n const tuple = presetTuple(preset, axes, defaults);\n const matches = tuplesEqual(tuple, activeTuple, axes);\n const modified = !matches && preset.name === lastApplied;\n const title = preset.description ? `${preset.name} — ${preset.description}` : preset.name;\n return h(OptionPill, {\n key: `${TOOL_ID}/preset/${preset.name}`,\n label: preset.name,\n active: matches,\n title,\n onClick: () => onApply(preset),\n trailing: modified\n ? h('span', { 'aria-hidden': true, style: PRESET_PILL_MODIFIED })\n : null,\n });\n }),\n ),\n );\n}\n\ninterface AxisSectionProps {\n axis: AxisEntry;\n active: string;\n onSelect: (next: string) => void;\n}\n\nfunction AxisSection({ axis, active, onSelect }: AxisSectionProps): ReactElement {\n const label = displayLabelFor(axis);\n return h(\n 'div',\n { style: AXIS_ROW_STYLE },\n h('div', { style: AXIS_LABEL_STYLE, title: axis.description }, label),\n h(\n 'div',\n { style: AXIS_PILLS_STYLE },\n ...axis.contexts.map((ctx) =>\n h(OptionPill, {\n key: `${TOOL_ID}/${axis.name}/${ctx}`,\n label: ctx,\n active: ctx === active,\n onClick: () => onSelect(ctx),\n }),\n ),\n ),\n );\n}\n\nconst COLOR_FORMAT_OPTIONS: readonly { id: string; 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\ninterface ColorFormatSectionProps {\n active: string;\n onSelect: (next: string) => void;\n}\n\nfunction ColorFormatSection({ active, onSelect }: ColorFormatSectionProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { style: SECTION_LABEL_STYLE }, 'Color format'),\n h(\n 'div',\n { style: SECTION_BODY_STYLE },\n ...COLOR_FORMAT_OPTIONS.map((opt) =>\n h(OptionPill, {\n key: `${TOOL_ID}/color-format/${opt.id}`,\n label: opt.label,\n active: opt.id === active,\n onClick: () => onSelect(opt.id),\n }),\n ),\n ),\n );\n}\n\ninterface PopoverBodyProps {\n axes: readonly AxisEntry[];\n presets: readonly PresetEntry[];\n defaults: Readonly<Record<string, string>>;\n activeTuple: Readonly<Record<string, string>>;\n activeColorFormat: string;\n lastApplied: string | null;\n onApplyPreset: (preset: PresetEntry) => void;\n onSelectAxis: (axisName: string, next: string) => void;\n onSelectColorFormat: (next: string) => void;\n onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n}\n\nfunction PopoverBody(props: PopoverBodyProps): ReactElement {\n const {\n axes,\n presets,\n defaults,\n activeTuple,\n activeColorFormat,\n lastApplied,\n onApplyPreset,\n onSelectAxis,\n onSelectColorFormat,\n onKeyDown,\n } = props;\n const sections: ReactElement[] = [];\n if (presets.length > 0) {\n sections.push(\n h(PresetsSection, {\n key: 'presets',\n presets,\n axes,\n defaults,\n activeTuple,\n lastApplied,\n onApply: onApplyPreset,\n }),\n h('div', { key: 'presets-divider', style: DIVIDER_STYLE }),\n );\n }\n axes.forEach((axis, idx) => {\n sections.push(\n h(AxisSection, {\n key: `axis-${axis.name}`,\n axis,\n active: activeTuple[axis.name] ?? axis.default,\n onSelect: (next) => onSelectAxis(axis.name, next),\n }),\n );\n if (idx === axes.length - 1) {\n sections.push(h('div', { key: 'axes-divider', style: DIVIDER_STYLE }));\n }\n });\n sections.push(\n h(ColorFormatSection, {\n key: 'color-format',\n active: activeColorFormat,\n onSelect: onSelectColorFormat,\n }),\n );\n return h(\n 'div',\n {\n role: 'menu',\n tabIndex: -1,\n onKeyDown,\n style: { minWidth: 260, padding: '4px 0', outline: 'none' },\n 'data-testid': 'swatchbook-toolbar-popover',\n },\n ...sections,\n );\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) ?? 'hex';\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 (axes.length === 0) return;\n /**\n * alt+T cycles the primary (first) axis's contexts, keeping the rest\n * of the tuple pinned. With multi-axis projects this makes the shortcut\n * predictable — you always know which wheel you're spinning. For\n * single-axis projects the behavior is identical to the pre-N-dropdown\n * toolbar (cycle through every theme).\n */\n const primary = axes[0];\n if (!primary) return;\n api.setAddonShortcut(ADDON_ID, {\n label: `Cycle swatchbook ${displayLabelFor(primary)}`,\n defaultShortcut: ['alt', 'T'],\n actionName: 'cycleAxis',\n showInMenu: true,\n action: () => {\n const current = activeTuple[primary.name] ?? primary.default;\n const idx = primary.contexts.indexOf(current);\n const next = primary.contexts[(idx + 1) % primary.contexts.length];\n if (next !== undefined) setAxis(primary.name, next);\n },\n });\n }, [api, axes, activeTuple, setAxis]);\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-toolbar-popover\"]')) 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(PopoverBody, {\n axes,\n presets,\n defaults,\n activeTuple,\n activeColorFormat,\n lastApplied,\n onApplyPreset: applyPreset,\n onSelectAxis: setAxis,\n onSelectColorFormat: (next: string) => updateGlobals({ [COLOR_FORMAT_GLOBAL_KEY]: next }),\n onKeyDown: handleKeyDown,\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 addons.add(PANEL_ID, {\n type: types.PANEL,\n title: 'Design Tokens',\n match: ({ viewMode }) => viewMode === 'story',\n render: ({ active }) => h(DesignTokensPanel, { active: !!active }),\n });\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"manager.mjs","names":["h"],"sources":["../src/panel.tsx","../src/manager.tsx"],"sourcesContent":["import React, {\n useCallback,\n useEffect,\n useMemo,\n useState,\n type CSSProperties,\n type KeyboardEvent,\n type ReactElement,\n} from 'react';\nimport { Placeholder, ScrollArea } from 'storybook/internal/components';\nimport { addons, useGlobals } from 'storybook/manager-api';\nimport type {\n DiagnosticSeverity,\n InitPayload,\n VirtualDiagnostic,\n VirtualToken,\n} from '#/channel-types.ts';\nimport { AXES_GLOBAL_KEY, GLOBAL_KEY, INIT_EVENT } from '#/constants.ts';\n\n/** `React.createElement` alias so the manager bundle avoids `react/jsx-runtime`. */\nconst h = React.createElement;\n\nfunction usePayload(): InitPayload | null {\n const [payload, setPayload] = useState<InitPayload | null>(null);\n useEffect(() => {\n const channel = addons.getChannel();\n const onInit = (next: InitPayload): void => setPayload(next);\n channel.on(INIT_EVENT, onInit);\n return () => {\n channel.off(INIT_EVENT, onInit);\n };\n }, []);\n return payload;\n}\n\nfunction makeCssVarName(path: string, prefix: string): string {\n const tail = path.replaceAll('.', '-');\n return prefix ? `--${prefix}-${tail}` : `--${tail}`;\n}\n\nasync function copy(text: string): Promise<void> {\n try {\n await navigator.clipboard.writeText(text);\n } catch {\n /* clipboard access denied — no fallback, the user can read the var name from the row */\n }\n}\n\n/** Format a token `$value` into a short display string. */\nfunction formatValue(value: unknown): string {\n if (value == null) return '';\n if (typeof value === 'string' || typeof value === 'number') return String(value);\n if (typeof value === 'object') {\n const v = value as Record<string, unknown>;\n if (typeof v['hex'] === 'string') return v['hex'] as string;\n if ('value' in v && 'unit' in v) return `${String(v['value'])}${String(v['unit'])}`;\n return JSON.stringify(value).slice(0, 80);\n }\n return String(value);\n}\n\nconst containerStyle: CSSProperties = {\n display: 'flex',\n flexDirection: 'column',\n height: '100%',\n};\n\nconst headerStyle: CSSProperties = {\n padding: '8px 12px',\n borderBottom: '1px solid rgba(128,128,128,0.2)',\n display: 'flex',\n flexDirection: 'column',\n gap: 6,\n};\n\nconst axisIndicatorStyle: CSSProperties = {\n fontSize: 11,\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, \"Liberation Mono\", monospace',\n opacity: 0.7,\n};\n\nconst treeWrapperStyle: CSSProperties = {\n fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace',\n fontSize: 12,\n padding: 8,\n};\n\nconst groupRowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 6,\n padding: '4px 6px',\n borderRadius: 4,\n cursor: 'pointer',\n userSelect: 'none',\n outline: 'none',\n};\n\nconst leafRowStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n padding: '4px 6px',\n borderRadius: 4,\n cursor: 'pointer',\n border: 'none',\n background: 'transparent',\n color: 'inherit',\n width: '100%',\n textAlign: 'left',\n fontFamily: 'inherit',\n fontSize: 'inherit',\n outline: 'none',\n};\n\nconst caretStyle: CSSProperties = {\n display: 'inline-block',\n width: 12,\n textAlign: 'center',\n opacity: 0.6,\n};\n\nconst treeUlStyle: CSSProperties = { listStyle: 'none', margin: 0, padding: 0 };\n\nconst nestedUlStyle: CSSProperties = {\n listStyle: 'none',\n margin: 0,\n paddingLeft: 18,\n borderLeft: '1px solid rgba(128,128,128,0.2)',\n};\n\nconst typePillStyle: CSSProperties = {\n display: 'inline-block',\n padding: '1px 6px',\n borderRadius: 4,\n fontSize: 10,\n letterSpacing: 0.5,\n textTransform: 'uppercase',\n background: 'rgba(128,128,128,0.15)',\n};\n\nconst valueStyle: CSSProperties = {\n marginLeft: 'auto',\n opacity: 0.7,\n fontSize: 11,\n maxWidth: '40%',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n whiteSpace: 'nowrap',\n};\n\nconst countStyle: CSSProperties = {\n marginLeft: 'auto',\n fontSize: 11,\n opacity: 0.7,\n};\n\nconst swatchStyle: CSSProperties = {\n display: 'inline-block',\n width: 14,\n height: 14,\n borderRadius: 3,\n border: '1px solid rgba(128,128,128,0.3)',\n marginLeft: 8,\n};\n\nconst searchInputStyle: CSSProperties = {\n width: '100%',\n padding: '4px 8px',\n fontSize: 12,\n border: '1px solid rgba(128,128,128,0.3)',\n borderRadius: 4,\n background: 'transparent',\n color: 'inherit',\n};\n\ninterface LeafNode {\n kind: 'leaf';\n segment: string;\n path: string;\n token: VirtualToken;\n}\n\ninterface GroupNode {\n kind: 'group';\n segment: string;\n path: string;\n children: TreeNode[];\n}\n\ntype TreeNode = LeafNode | GroupNode;\n\nfunction buildTree(resolved: Record<string, VirtualToken>): TreeNode[] {\n const rootNode: GroupNode = { kind: 'group', segment: '', path: '', children: [] };\n for (const [path, token] of Object.entries(resolved)) {\n const segments = path.split('.');\n let node: GroupNode = rootNode;\n for (let i = 0; i < segments.length - 1; i += 1) {\n const seg = segments[i] as string;\n const prefix = segments.slice(0, i + 1).join('.');\n let child = node.children.find(\n (c): c is GroupNode => c.kind === 'group' && c.segment === seg,\n );\n if (!child) {\n child = { kind: 'group', segment: seg, path: prefix, children: [] };\n node.children.push(child);\n }\n node = child;\n }\n const leafSegment = segments[segments.length - 1] as string;\n node.children.push({ kind: 'leaf', segment: leafSegment, path, token });\n }\n sortTree(rootNode);\n return rootNode.children;\n}\n\nfunction sortTree(node: GroupNode): void {\n node.children.sort((a, b) => {\n if (a.kind !== b.kind) return a.kind === 'group' ? -1 : 1;\n return a.segment.localeCompare(b.segment);\n });\n for (const c of node.children) {\n if (c.kind === 'group') sortTree(c);\n }\n}\n\nfunction collectInitialExpanded(nodes: TreeNode[], remainingDepth: number, out: Set<string>): void {\n if (remainingDepth <= 0) return;\n for (const node of nodes) {\n if (node.kind !== 'group') continue;\n out.add(node.path);\n collectInitialExpanded(node.children, remainingDepth - 1, out);\n }\n}\n\nfunction countLeaves(node: TreeNode): number {\n if (node.kind === 'leaf') return 1;\n let n = 0;\n for (const c of node.children) n += countLeaves(c);\n return n;\n}\n\nfunction filterTree(nodes: TreeNode[], query: string): TreeNode[] {\n if (!query) return nodes;\n const out: TreeNode[] = [];\n for (const node of nodes) {\n if (node.kind === 'leaf') {\n if (node.path.toLowerCase().includes(query)) out.push(node);\n continue;\n }\n const filteredChildren = filterTree(node.children, query);\n if (filteredChildren.length > 0) {\n out.push({ ...node, children: filteredChildren });\n }\n }\n return out;\n}\n\nfunction collectAllGroupPaths(nodes: TreeNode[], out: Set<string>): void {\n for (const node of nodes) {\n if (node.kind === 'group') {\n out.add(node.path);\n collectAllGroupPaths(node.children, out);\n }\n }\n}\n\ninterface PanelProps {\n active: boolean;\n}\n\nexport function DesignTokensPanel({ active }: PanelProps): ReactElement | null {\n const payload = usePayload();\n const [globals] = useGlobals();\n const [query, setQuery] = useState('');\n\n const axes = useMemo(() => payload?.axes ?? [], [payload]);\n const themes = useMemo(() => payload?.themes ?? [], [payload]);\n const globalAxes = globals[AXES_GLOBAL_KEY] as Record<string, string> | undefined;\n const globalTheme = globals[GLOBAL_KEY] as string | undefined;\n\n const tuple: Record<string, string> = useMemo(() => {\n const out: Record<string, string> = {};\n for (const axis of axes) out[axis.name] = axis.default;\n if (globalAxes && typeof globalAxes === 'object') {\n for (const axis of axes) {\n const candidate = globalAxes[axis.name];\n if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;\n }\n return out;\n }\n if (globalTheme) {\n const match = themes.find((t) => t.name === globalTheme);\n if (match) {\n for (const axis of axes) {\n const candidate = match.input[axis.name];\n if (candidate && axis.contexts.includes(candidate)) out[axis.name] = candidate;\n }\n }\n }\n return out;\n }, [axes, themes, globalAxes, globalTheme]);\n\n const themeName = useMemo(() => {\n const match = themes.find((t) => {\n const input = t.input;\n return Object.keys(input).every((k) => input[k] === tuple[k]);\n });\n return match?.name ?? globalTheme ?? payload?.defaultTheme ?? '';\n }, [themes, tuple, globalTheme, payload]);\n\n const prefix = payload?.cssVarPrefix ?? '';\n const tokens = useMemo(() => payload?.themesResolved[themeName] ?? {}, [payload, themeName]);\n const tokenCount = Object.keys(tokens).length;\n\n const tree = useMemo(() => buildTree(tokens), [tokens]);\n const lowerQuery = query.toLowerCase();\n const filtered = useMemo(() => filterTree(tree, lowerQuery), [tree, lowerQuery]);\n\n const initialExpanded = useMemo(() => {\n const out = new Set<string>();\n collectInitialExpanded(tree, 1, out);\n return out;\n }, [tree]);\n\n const [expanded, setExpanded] = useState<Set<string>>(initialExpanded);\n useEffect(() => {\n setExpanded(initialExpanded);\n }, [initialExpanded]);\n\n // When searching, expand every matching group so hits are visible.\n const displayExpanded = useMemo(() => {\n if (!lowerQuery) return expanded;\n const all = new Set<string>();\n collectAllGroupPaths(filtered, all);\n return all;\n }, [expanded, filtered, lowerQuery]);\n\n const toggle = useCallback((path: string): void => {\n setExpanded((prev) => {\n const next = new Set(prev);\n if (next.has(path)) next.delete(path);\n else next.add(path);\n return next;\n });\n }, []);\n\n const handleLeafClick = useCallback(\n (path: string) => {\n const varName = makeCssVarName(path, prefix);\n void copy(`var(${varName})`);\n },\n [prefix],\n );\n\n if (!active) return null;\n\n if (!payload) {\n return h(Placeholder, null, 'Waiting for swatchbook preview…');\n }\n\n const showAxisIndicator =\n axes.length > 1 || (axes.length === 1 && axes[0]?.source !== 'synthetic');\n const axisIndicatorText = axes\n .map((axis) => `${axis.name}: ${tuple[axis.name] ?? axis.default}`)\n .join(' · ');\n const disabledAxes = payload?.disabledAxes ?? [];\n const pinnedSample = themes[0]?.input ?? {};\n const disabledIndicatorText = disabledAxes\n .map((name) => `${name}: ${pinnedSample[name] ?? '?'} · pinned`)\n .join(' · ');\n\n return h(\n 'div',\n { style: containerStyle },\n h(\n 'div',\n { style: headerStyle },\n showAxisIndicator &&\n h(\n 'div',\n {\n style: axisIndicatorStyle,\n 'data-testid': 'design-tokens-panel-axis-indicator',\n },\n axisIndicatorText,\n ),\n disabledAxes.length > 0 &&\n h(\n 'div',\n {\n style: { ...axisIndicatorStyle, opacity: 0.5 },\n 'data-testid': 'design-tokens-panel-disabled-axes-indicator',\n },\n disabledIndicatorText,\n ),\n h('input', {\n style: searchInputStyle,\n type: 'search',\n placeholder: `Search ${tokenCount} tokens in ${themeName}…`,\n 'aria-label': 'Search tokens',\n value: query,\n onChange: (e: React.ChangeEvent<HTMLInputElement>) => setQuery(e.target.value),\n }),\n ),\n h(DiagnosticsSection, { diagnostics: payload.diagnostics }),\n h(\n ScrollArea,\n { vertical: true },\n filtered.length === 0\n ? h(Placeholder, null, query ? 'No tokens match this filter.' : 'No tokens in this theme.')\n : h(\n 'div',\n { style: treeWrapperStyle },\n h(\n 'ul',\n { style: treeUlStyle, role: 'tree' },\n filtered.map((node) =>\n h(TreeRow, {\n key: node.path || node.segment,\n node,\n expanded: displayExpanded,\n onToggle: toggle,\n onLeafClick: handleLeafClick,\n prefix,\n }),\n ),\n ),\n ),\n ),\n );\n}\n\ninterface TreeRowProps {\n node: TreeNode;\n expanded: Set<string>;\n onToggle(path: string): void;\n onLeafClick(path: string): void;\n prefix: string;\n}\n\nfunction TreeRow({ node, expanded, onToggle, onLeafClick, prefix }: TreeRowProps): ReactElement {\n if (node.kind === 'leaf') {\n return h(LeafRow, { node, onLeafClick, prefix });\n }\n const isOpen = expanded.has(node.path);\n const onKey = (e: KeyboardEvent<HTMLDivElement>): void => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onToggle(node.path);\n }\n };\n return h(\n 'li',\n { role: 'treeitem', 'aria-expanded': isOpen },\n h(\n 'div',\n {\n role: 'button',\n tabIndex: 0,\n style: groupRowStyle,\n onClick: () => onToggle(node.path),\n onKeyDown: onKey,\n 'data-path': node.path,\n 'data-testid': 'design-tokens-panel-group',\n },\n h('span', { style: caretStyle, 'aria-hidden': true }, isOpen ? '▾' : '▸'),\n h('span', null, node.segment),\n h('span', { style: countStyle }, countLeaves(node)),\n ),\n isOpen &&\n h(\n 'ul',\n { style: nestedUlStyle, role: 'group' },\n node.children.map((c) =>\n h(TreeRow, {\n key: c.path || c.segment,\n node: c,\n expanded,\n onToggle,\n onLeafClick,\n prefix,\n }),\n ),\n ),\n );\n}\n\ninterface LeafRowProps {\n node: LeafNode;\n onLeafClick(path: string): void;\n prefix: string;\n}\n\nfunction LeafRow({ node, onLeafClick, prefix }: LeafRowProps): ReactElement {\n const type = node.token.$type ?? '';\n const value = node.token.$value;\n const displayValue = formatValue(value);\n const isColor = type === 'color' && typeof value === 'object' && value !== null;\n const colorPreview =\n isColor && typeof (value as Record<string, unknown>)['hex'] === 'string'\n ? ((value as Record<string, unknown>)['hex'] as string)\n : null;\n\n const varName = makeCssVarName(node.path, prefix);\n\n return h(\n 'li',\n { role: 'treeitem' },\n h(\n 'button',\n {\n type: 'button',\n style: leafRowStyle,\n onClick: () => onLeafClick(node.path),\n title: `Click to copy var(${varName})`,\n 'data-path': node.path,\n 'data-testid': 'design-tokens-panel-leaf',\n },\n h('span', { style: caretStyle, 'aria-hidden': true }, '•'),\n h('span', null, node.segment),\n type && h('span', { style: typePillStyle }, type),\n h('span', { style: valueStyle }, displayValue),\n colorPreview &&\n h('span', {\n style: { ...swatchStyle, background: colorPreview },\n 'aria-hidden': true,\n }),\n ),\n );\n}\n\nconst severityStyle: Record<DiagnosticSeverity, CSSProperties> = {\n error: { color: '#d64545' },\n warn: { color: '#b08900' },\n info: { opacity: 0.6 },\n};\n\nconst severityLabel: Record<DiagnosticSeverity, string> = {\n error: 'ERROR',\n warn: 'WARN',\n info: 'INFO',\n};\n\nconst diagnosticsSectionStyle: CSSProperties = {\n borderBottom: '1px solid rgba(128,128,128,0.2)',\n};\n\nconst diagnosticsSummaryStyle: CSSProperties = {\n padding: '10px 12px',\n fontSize: 12,\n cursor: 'pointer',\n userSelect: 'none',\n listStyle: 'none',\n display: 'flex',\n alignItems: 'center',\n gap: 8,\n};\n\nconst diagnosticRowStyle: CSSProperties = {\n display: 'grid',\n gridTemplateColumns: '60px 1fr',\n gap: 12,\n padding: '8px 12px',\n fontSize: 12,\n borderTop: '1px solid rgba(128,128,128,0.12)',\n};\n\ninterface DiagnosticsSectionProps {\n diagnostics: readonly VirtualDiagnostic[];\n}\n\nfunction DiagnosticsSection({ diagnostics }: DiagnosticsSectionProps): ReactElement {\n const counts = diagnostics.reduce(\n (acc, d) => {\n acc[d.severity] = (acc[d.severity] ?? 0) + 1;\n return acc;\n },\n { error: 0, warn: 0, info: 0 } as Record<DiagnosticSeverity, number>,\n );\n\n const hasErrorsOrWarnings = counts.error > 0 || counts.warn > 0;\n\n const summaryText = (() => {\n if (diagnostics.length === 0) return '✔ OK · no diagnostics';\n const parts: string[] = [];\n if (counts.error > 0) parts.push(`✖ ${counts.error} error${counts.error === 1 ? '' : 's'}`);\n if (counts.warn > 0) parts.push(`⚠ ${counts.warn} warning${counts.warn === 1 ? '' : 's'}`);\n if (counts.info > 0) parts.push(`${counts.info} info`);\n return parts.join(' · ');\n })();\n\n const summaryColor = (() => {\n if (diagnostics.length === 0) return '#30a46c';\n if (counts.error > 0) return '#d64545';\n if (counts.warn > 0) return '#b08900';\n return 'inherit';\n })();\n\n return h(\n 'details',\n {\n style: diagnosticsSectionStyle,\n open: hasErrorsOrWarnings,\n 'data-testid': 'design-tokens-panel-diagnostics',\n },\n h(\n 'summary',\n { style: { ...diagnosticsSummaryStyle, color: summaryColor, fontWeight: 600 } },\n h('span', null, 'Diagnostics'),\n h('span', { style: { fontWeight: 400 } }, summaryText),\n ),\n diagnostics.length === 0\n ? null\n : h(\n 'div',\n null,\n diagnostics.map((d, i) =>\n h(\n 'div',\n { key: `${d.group}-${i}`, style: diagnosticRowStyle },\n h(\n 'span',\n { style: { ...severityStyle[d.severity], fontWeight: 600, fontSize: 10 } },\n severityLabel[d.severity],\n ),\n h(\n 'div',\n null,\n h('div', null, d.message),\n (d.filename || d.group) &&\n h(\n 'div',\n { style: { opacity: 0.5, fontSize: 10, marginTop: 4 } },\n [d.group, d.filename, d.line ? `:${d.line}` : ''].filter(Boolean).join(' · '),\n ),\n ),\n ),\n ),\n ),\n );\n}\n","import 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 PANEL_ID,\n TOOL_ID,\n} from '#/constants.ts';\nimport { DesignTokensPanel } from '#/panel.tsx';\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 */\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 * Treat the `{ name: 'theme', source: 'synthetic' }` axis — the one core\n * fabricates for single-theme projects with no resolver — as a special case\n * that uses the label \"Theme\" instead of the axis name. Authored single-axis\n * resolvers keep their real axis name (e.g. `mode`).\n */\nfunction displayLabelFor(axis: AxisEntry): string {\n if (axis.source === 'synthetic' && axis.name === 'theme') return 'Theme';\n return axis.name;\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). Matches 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 tuplesEqual(\n a: Readonly<Record<string, string>>,\n b: Readonly<Record<string, string>>,\n axes: readonly AxisEntry[],\n): boolean {\n for (const axis of axes) {\n if (a[axis.name] !== b[axis.name]) return false;\n }\n return true;\n}\n\nconst SECTION_LABEL_STYLE: React.CSSProperties = {\n padding: '8px 12px 4px',\n fontSize: 11,\n textTransform: 'uppercase',\n letterSpacing: 0.5,\n opacity: 0.6,\n};\n\nconst SECTION_BODY_STYLE: React.CSSProperties = {\n padding: '0 12px 10px',\n display: 'flex',\n flexWrap: 'wrap',\n gap: 4,\n};\n\nconst AXIS_ROW_STYLE: React.CSSProperties = {\n padding: '0 12px 10px',\n display: 'grid',\n gridTemplateColumns: 'max-content 1fr',\n columnGap: 12,\n rowGap: 4,\n alignItems: 'center',\n};\n\nconst AXIS_LABEL_STYLE: React.CSSProperties = {\n fontSize: 12,\n fontWeight: 600,\n opacity: 0.85,\n};\n\nconst AXIS_PILLS_STYLE: React.CSSProperties = {\n display: 'flex',\n flexWrap: 'wrap',\n gap: 4,\n};\n\nconst OPTION_PILL_BASE: React.CSSProperties = {\n padding: '3px 8px',\n borderRadius: 4,\n fontSize: 12,\n lineHeight: '18px',\n // Longhand border properties (not the `border` shorthand) so\n // active → inactive only updates `borderColor`'s value instead of\n // *removing* the key from inline style. Removing it lets Storybook's\n // theme paint a stray border-color on the previously-selected pill.\n borderWidth: 1,\n borderStyle: 'solid',\n borderColor: 'transparent',\n background: 'transparent',\n cursor: 'pointer',\n color: 'inherit',\n outline: 'none',\n boxShadow: 'none',\n};\n\nconst OPTION_PILL_ACTIVE: React.CSSProperties = {\n ...OPTION_PILL_BASE,\n fontWeight: 600,\n background: 'rgba(0, 122, 255, 0.12)',\n borderColor: 'rgba(0, 122, 255, 0.45)',\n};\n\nconst PRESET_PILL_MODIFIED: React.CSSProperties = {\n display: 'inline-block',\n width: 6,\n height: 6,\n marginLeft: 6,\n borderRadius: '50%',\n background: 'currentColor',\n opacity: 0.6,\n verticalAlign: 'middle',\n};\n\nconst DIVIDER_STYLE: React.CSSProperties = {\n height: 1,\n background: 'currentColor',\n opacity: 0.1,\n margin: '2px 8px',\n};\n\ninterface OptionPillProps {\n label: string;\n active: boolean;\n title?: string;\n onClick: () => void;\n trailing?: ReactElement | null;\n}\n\nfunction OptionPill({ label, active, title, onClick, trailing }: OptionPillProps): ReactElement {\n return h(\n 'button',\n {\n type: 'button',\n title,\n onClick,\n // Skip focus on mouse click so Storybook's `:focus` border-color\n // theming doesn't stick on the previously-clicked pill. Keyboard\n // tabbing still lands focus normally — preventDefault on mousedown\n // only blocks the implicit focus-on-click behavior.\n onMouseDown: (event) => event.preventDefault(),\n style: active ? OPTION_PILL_ACTIVE : OPTION_PILL_BASE,\n },\n label,\n trailing ?? null,\n );\n}\n\ninterface PresetsSectionProps {\n presets: readonly PresetEntry[];\n axes: readonly AxisEntry[];\n defaults: Readonly<Record<string, string>>;\n activeTuple: Readonly<Record<string, string>>;\n lastApplied: string | null;\n onApply: (preset: PresetEntry) => void;\n}\n\nfunction PresetsSection({\n presets,\n axes,\n defaults,\n activeTuple,\n lastApplied,\n onApply,\n}: PresetsSectionProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { style: SECTION_LABEL_STYLE }, 'Presets'),\n h(\n 'div',\n { style: SECTION_BODY_STYLE },\n ...presets.map((preset) => {\n const tuple = presetTuple(preset, axes, defaults);\n const matches = tuplesEqual(tuple, activeTuple, axes);\n const modified = !matches && preset.name === lastApplied;\n const title = preset.description ? `${preset.name} — ${preset.description}` : preset.name;\n return h(OptionPill, {\n key: `${TOOL_ID}/preset/${preset.name}`,\n label: preset.name,\n active: matches,\n title,\n onClick: () => onApply(preset),\n trailing: modified\n ? h('span', { 'aria-hidden': true, style: PRESET_PILL_MODIFIED })\n : null,\n });\n }),\n ),\n );\n}\n\ninterface AxisSectionProps {\n axis: AxisEntry;\n active: string;\n onSelect: (next: string) => void;\n}\n\nfunction AxisSection({ axis, active, onSelect }: AxisSectionProps): ReactElement {\n const label = displayLabelFor(axis);\n return h(\n 'div',\n { style: AXIS_ROW_STYLE },\n h('div', { style: AXIS_LABEL_STYLE, title: axis.description }, label),\n h(\n 'div',\n { style: AXIS_PILLS_STYLE },\n ...axis.contexts.map((ctx) =>\n h(OptionPill, {\n key: `${TOOL_ID}/${axis.name}/${ctx}`,\n label: ctx,\n active: ctx === active,\n onClick: () => onSelect(ctx),\n }),\n ),\n ),\n );\n}\n\nconst COLOR_FORMAT_OPTIONS: readonly { id: string; 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\ninterface ColorFormatSectionProps {\n active: string;\n onSelect: (next: string) => void;\n}\n\nfunction ColorFormatSection({ active, onSelect }: ColorFormatSectionProps): ReactElement {\n return h(\n 'div',\n null,\n h('div', { style: SECTION_LABEL_STYLE }, 'Color format'),\n h(\n 'div',\n { style: SECTION_BODY_STYLE },\n ...COLOR_FORMAT_OPTIONS.map((opt) =>\n h(OptionPill, {\n key: `${TOOL_ID}/color-format/${opt.id}`,\n label: opt.label,\n active: opt.id === active,\n onClick: () => onSelect(opt.id),\n }),\n ),\n ),\n );\n}\n\ninterface PopoverBodyProps {\n axes: readonly AxisEntry[];\n presets: readonly PresetEntry[];\n defaults: Readonly<Record<string, string>>;\n activeTuple: Readonly<Record<string, string>>;\n activeColorFormat: string;\n lastApplied: string | null;\n onApplyPreset: (preset: PresetEntry) => void;\n onSelectAxis: (axisName: string, next: string) => void;\n onSelectColorFormat: (next: string) => void;\n onKeyDown: (event: React.KeyboardEvent<HTMLDivElement>) => void;\n}\n\nfunction PopoverBody(props: PopoverBodyProps): ReactElement {\n const {\n axes,\n presets,\n defaults,\n activeTuple,\n activeColorFormat,\n lastApplied,\n onApplyPreset,\n onSelectAxis,\n onSelectColorFormat,\n onKeyDown,\n } = props;\n const sections: ReactElement[] = [];\n if (presets.length > 0) {\n sections.push(\n h(PresetsSection, {\n key: 'presets',\n presets,\n axes,\n defaults,\n activeTuple,\n lastApplied,\n onApply: onApplyPreset,\n }),\n h('div', { key: 'presets-divider', style: DIVIDER_STYLE }),\n );\n }\n axes.forEach((axis, idx) => {\n sections.push(\n h(AxisSection, {\n key: `axis-${axis.name}`,\n axis,\n active: activeTuple[axis.name] ?? axis.default,\n onSelect: (next) => onSelectAxis(axis.name, next),\n }),\n );\n if (idx === axes.length - 1) {\n sections.push(h('div', { key: 'axes-divider', style: DIVIDER_STYLE }));\n }\n });\n sections.push(\n h(ColorFormatSection, {\n key: 'color-format',\n active: activeColorFormat,\n onSelect: onSelectColorFormat,\n }),\n );\n return h(\n 'div',\n {\n role: 'menu',\n tabIndex: -1,\n onKeyDown,\n style: { minWidth: 260, padding: '4px 0', outline: 'none' },\n 'data-testid': 'swatchbook-toolbar-popover',\n },\n ...sections,\n );\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) ?? 'hex';\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 (axes.length === 0) return;\n /**\n * alt+T cycles the primary (first) axis's contexts, keeping the rest\n * of the tuple pinned. With multi-axis projects this makes the shortcut\n * predictable — you always know which wheel you're spinning. For\n * single-axis projects the behavior is identical to the pre-N-dropdown\n * toolbar (cycle through every theme).\n */\n const primary = axes[0];\n if (!primary) return;\n api.setAddonShortcut(ADDON_ID, {\n label: `Cycle swatchbook ${displayLabelFor(primary)}`,\n defaultShortcut: ['alt', 'T'],\n actionName: 'cycleAxis',\n showInMenu: true,\n action: () => {\n const current = activeTuple[primary.name] ?? primary.default;\n const idx = primary.contexts.indexOf(current);\n const next = primary.contexts[(idx + 1) % primary.contexts.length];\n if (next !== undefined) setAxis(primary.name, next);\n },\n });\n }, [api, axes, activeTuple, setAxis]);\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-toolbar-popover\"]')) 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(PopoverBody, {\n axes,\n presets,\n defaults,\n activeTuple,\n activeColorFormat,\n lastApplied,\n onApplyPreset: applyPreset,\n onSelectAxis: setAxis,\n onSelectColorFormat: (next: string) => updateGlobals({ [COLOR_FORMAT_GLOBAL_KEY]: next }),\n onKeyDown: handleKeyDown,\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 addons.add(PANEL_ID, {\n type: types.PANEL,\n title: 'Design Tokens',\n match: ({ viewMode }) => viewMode === 'story',\n render: ({ active }) => h(DesignTokensPanel, { active: !!active }),\n });\n});\n"],"mappings":"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"}
package/dist/preset.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { t as AddonOptions } from "./options-rvGQy0uV.mjs";
2
+ import { Project } from "@unpunnyfuns/swatchbook-core";
2
3
  import { InlineConfig } from "vite";
3
4
 
4
5
  //#region src/preset.d.ts
@@ -15,6 +16,8 @@ interface PresetOptions extends AddonOptions {
15
16
  declare function viteFinal(viteConfig: InlineConfig, options: PresetOptions): Promise<InlineConfig>;
16
17
  /** Storybook appends this module into the manager bundle so our toolbar tool registers. */
17
18
  declare function managerEntries(entry?: string[]): string[];
19
+ /** @internal Exported for tests; not part of the public API. */
20
+ declare function renderTokenTypes(project: Project): string;
18
21
  //#endregion
19
- export { managerEntries, viteFinal };
22
+ export { managerEntries, renderTokenTypes, viteFinal };
20
23
  //# sourceMappingURL=preset.d.mts.map
package/dist/preset.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  import { u as RESOLVED_VIRTUAL_MODULE_ID } from "./constants-CBXL7TOk.mjs";
2
2
  import { mkdir, writeFile } from "node:fs/promises";
3
- import { dirname, isAbsolute, resolve } from "node:path";
3
+ import { dirname, isAbsolute, resolve, sep } from "node:path";
4
4
  import { fileURLToPath, pathToFileURL } from "node:url";
5
5
  import { loadProject, projectCss } from "@unpunnyfuns/swatchbook-core";
6
6
  import { createJiti } from "jiti";
7
+ import picomatch from "picomatch";
7
8
  //#region src/virtual/plugin.ts
8
9
  /**
9
10
  * Vite plugin that serves the virtual `virtual:swatchbook/tokens` module —
@@ -53,14 +54,15 @@ function swatchbookTokensPlugin({ config, cwd }) {
53
54
  if (mod) server.moduleGraph.invalidateModule(mod);
54
55
  server.ws.send({ type: "full-reload" });
55
56
  };
57
+ const matches = (changed) => watchPaths.some((p) => changed === p || changed.startsWith(`${p}${sep}`));
56
58
  server.watcher.on("change", (changed) => {
57
- if (watchPaths.some((p) => changed === p || changed.startsWith(p))) invalidate();
59
+ if (matches(changed)) invalidate();
58
60
  });
59
61
  server.watcher.on("add", (changed) => {
60
- if (watchPaths.some((p) => changed.startsWith(p))) invalidate();
62
+ if (matches(changed)) invalidate();
61
63
  });
62
64
  server.watcher.on("unlink", (changed) => {
63
- if (watchPaths.some((p) => changed.startsWith(p))) invalidate();
65
+ if (matches(changed)) invalidate();
64
66
  });
65
67
  }
66
68
  };
@@ -73,11 +75,12 @@ function swatchbookTokensPlugin({ config, cwd }) {
73
75
  * tracked on `project.sourceFiles` — which stays correct as the resolver
74
76
  * evolves without requiring a parallel `tokens` glob.
75
77
  */
78
+ /** @internal Exported for tests; not part of the public API. */
76
79
  function collectWatchPaths(config, project, cwd) {
77
80
  const paths = [];
78
81
  if (config.tokens && config.tokens.length > 0) for (const glob of config.tokens) {
79
- const base = glob.replace(/\/\*.*$/, "").replace(/\/[^/]*\*.*$/, "");
80
- paths.push(resolveFromCwd(base, cwd));
82
+ const { base } = picomatch.scan(glob);
83
+ paths.push(resolveFromCwd(base || ".", cwd));
81
84
  }
82
85
  else if (project?.sourceFiles) for (const file of project.sourceFiles) paths.push(dirname(file));
83
86
  if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));
@@ -136,6 +139,7 @@ async function writeTokenCodegen(config, cwd, options) {
136
139
  const content = renderTokenTypes(project);
137
140
  await writeFile(resolve(outDir, "tokens.d.ts"), content);
138
141
  }
142
+ /** @internal Exported for tests; not part of the public API. */
139
143
  function renderTokenTypes(project) {
140
144
  const paths = /* @__PURE__ */ new Set();
141
145
  for (const theme of project.themes) {
@@ -158,6 +162,6 @@ function renderTokenTypes(project) {
158
162
  ].join("\n");
159
163
  }
160
164
  //#endregion
161
- export { managerEntries, viteFinal };
165
+ export { managerEntries, renderTokenTypes, viteFinal };
162
166
 
163
167
  //# sourceMappingURL=preset.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"preset.mjs","names":["resolvePath"],"sources":["../src/virtual/plugin.ts","../src/preset.ts"],"sourcesContent":["import type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject, projectCss } from '@unpunnyfuns/swatchbook-core';\nimport { dirname, isAbsolute, resolve as resolvePath } from 'node:path';\nimport type { Plugin } from 'vite';\nimport { RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID } from '#/constants.ts';\n\nexport interface SwatchbookPluginOptions {\n config: Config;\n cwd: string;\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({ config, cwd }: 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 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 return null;\n },\n\n load(id) {\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 ].join('\\n');\n },\n\n configureServer(server) {\n const watchPaths = collectWatchPaths(config, project, cwd);\n for (const p of watchPaths) server.watcher.add(p);\n\n const invalidate = async (): Promise<void> => {\n await refresh();\n const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n };\n\n server.watcher.on('change', (changed) => {\n if (watchPaths.some((p) => changed === p || changed.startsWith(p))) {\n void invalidate();\n }\n });\n server.watcher.on('add', (changed) => {\n if (watchPaths.some((p) => changed.startsWith(p))) void invalidate();\n });\n server.watcher.on('unlink', (changed) => {\n if (watchPaths.some((p) => changed.startsWith(p))) void invalidate();\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 */\nfunction collectWatchPaths(config: Config, project: Project | undefined, cwd: string): string[] {\n const paths: string[] = [];\n if (config.tokens && config.tokens.length > 0) {\n for (const glob of config.tokens) {\n const base = glob.replace(/\\/\\*.*$/, '').replace(/\\/[^/]*\\*.*$/, '');\n paths.push(resolveFromCwd(base, cwd));\n }\n } else if (project?.sourceFiles) {\n for (const file of project.sourceFiles) paths.push(dirname(file));\n }\n if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));\n return [...new Set(paths)];\n}\n\nfunction resolveFromCwd(p: string, cwd: string): string {\n if (isAbsolute(p)) return p;\n return resolvePath(cwd, p);\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { 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(swatchbookTokensPlugin({ config, cwd }));\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\nfunction 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":["resolvePath"],"sources":["../src/virtual/plugin.ts","../src/preset.ts"],"sourcesContent":["import type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject, projectCss } from '@unpunnyfuns/swatchbook-core';\nimport { dirname, isAbsolute, resolve as resolvePath, sep } from 'node:path';\nimport picomatch from 'picomatch';\nimport type { Plugin } from 'vite';\nimport { RESOLVED_VIRTUAL_MODULE_ID, VIRTUAL_MODULE_ID } from '#/constants.ts';\n\nexport interface SwatchbookPluginOptions {\n config: Config;\n cwd: string;\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({ config, cwd }: 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 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 return null;\n },\n\n load(id) {\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 ].join('\\n');\n },\n\n configureServer(server) {\n const watchPaths = collectWatchPaths(config, project, cwd);\n for (const p of watchPaths) server.watcher.add(p);\n\n const invalidate = async (): Promise<void> => {\n await refresh();\n const mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_MODULE_ID);\n if (mod) server.moduleGraph.invalidateModule(mod);\n server.ws.send({ type: 'full-reload' });\n };\n\n const matches = (changed: string): boolean =>\n watchPaths.some((p) => changed === p || changed.startsWith(`${p}${sep}`));\n\n server.watcher.on('change', (changed) => {\n if (matches(changed)) void invalidate();\n });\n server.watcher.on('add', (changed) => {\n if (matches(changed)) void invalidate();\n });\n server.watcher.on('unlink', (changed) => {\n if (matches(changed)) void invalidate();\n });\n },\n };\n}\n\n/**\n * Collect the set of filesystem paths the dev server should watch for\n * HMR. When `config.tokens` is set, use its globs (stripped to their\n * base directories) — users opt in to broader watching this way. When\n * absent, use the resolver file + every `$ref` target it pulled in, as\n * tracked on `project.sourceFiles` — which stays correct as the resolver\n * evolves without requiring a parallel `tokens` glob.\n */\n/** @internal Exported for tests; not part of the public API. */\nexport function collectWatchPaths(\n config: Config,\n project: Project | undefined,\n cwd: string,\n): string[] {\n const paths: string[] = [];\n if (config.tokens && config.tokens.length > 0) {\n for (const glob of config.tokens) {\n // `picomatch.scan` yields the longest literal prefix before any glob\n // metachar, so it handles brace expansion, nested globstars, and the\n // other shapes the hand-rolled regex missed.\n const { base } = picomatch.scan(glob);\n paths.push(resolveFromCwd(base || '.', cwd));\n }\n } else if (project?.sourceFiles) {\n for (const file of project.sourceFiles) paths.push(dirname(file));\n }\n if (config.resolver) paths.push(resolveFromCwd(config.resolver, cwd));\n return [...new Set(paths)];\n}\n\nfunction resolveFromCwd(p: string, cwd: string): string {\n if (isAbsolute(p)) return p;\n return resolvePath(cwd, p);\n}\n","import { mkdir, writeFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport type { Config, Project } from '@unpunnyfuns/swatchbook-core';\nimport { loadProject } from '@unpunnyfuns/swatchbook-core';\nimport { 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(swatchbookTokensPlugin({ config, cwd }));\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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unpunnyfuns/swatchbook-addon",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
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>",
@@ -69,8 +69,9 @@
69
69
  },
70
70
  "dependencies": {
71
71
  "jiti": "^2.4.0",
72
- "@unpunnyfuns/swatchbook-blocks": "0.2.1",
73
- "@unpunnyfuns/swatchbook-core": "0.2.1"
72
+ "picomatch": "^4.0.4",
73
+ "@unpunnyfuns/swatchbook-core": "0.2.2",
74
+ "@unpunnyfuns/swatchbook-blocks": "0.2.2"
74
75
  },
75
76
  "peerDependencies": {
76
77
  "react": ">=18",
@@ -80,19 +81,23 @@
80
81
  "devDependencies": {
81
82
  "@storybook/react-vite": "^10.3.5",
82
83
  "@types/node": "^25.6.0",
84
+ "@types/picomatch": "^4.0.3",
83
85
  "@types/react": "^19.2.14",
84
86
  "react": "^19.2.4",
85
87
  "react-dom": "^19.2.4",
86
88
  "storybook": "^10.3.5",
87
89
  "tsdown": "^0.21.9",
88
90
  "typescript": "^6.0.0",
89
- "vite": "^8.0.4"
91
+ "vite": "^8.0.4",
92
+ "vitest": "^4.1.4"
90
93
  },
91
94
  "scripts": {
92
95
  "build": "tsdown",
93
96
  "typecheck": "tsc --noEmit",
94
- "lint": "oxlint --deny-warnings -c ../../.oxlintrc.json src",
95
- "format": "oxfmt -c ../../.oxfmtrc.json src",
96
- "format:check": "oxfmt --check -c ../../.oxfmtrc.json src"
97
+ "test": "vitest run",
98
+ "test:watch": "vitest",
99
+ "lint": "oxlint --deny-warnings -c ../../.oxlintrc.json src test",
100
+ "format": "oxfmt -c ../../.oxfmtrc.json src test",
101
+ "format:check": "oxfmt --check -c ../../.oxfmtrc.json src test"
97
102
  }
98
103
  }