@motion-proto/live-tokens 0.1.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +160 -21
  2. package/dist-plugin/index.cjs +823 -336
  3. package/dist-plugin/index.d.cts +9 -7
  4. package/dist-plugin/index.d.ts +9 -7
  5. package/dist-plugin/index.js +822 -335
  6. package/package.json +51 -23
  7. package/src/assets/newspaper.webp +0 -0
  8. package/src/assets/offering.webp +0 -0
  9. package/src/component-editor/BadgeEditor.svelte +170 -0
  10. package/src/component-editor/CalloutEditor.svelte +103 -0
  11. package/src/component-editor/CardEditor.svelte +184 -0
  12. package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
  13. package/src/component-editor/CornerBadgeEditor.svelte +207 -0
  14. package/src/component-editor/DialogEditor.svelte +172 -0
  15. package/src/component-editor/ImageEditor.svelte +72 -0
  16. package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
  17. package/src/component-editor/NotificationEditor.svelte +160 -0
  18. package/src/component-editor/ProgressBarEditor.svelte +124 -0
  19. package/src/component-editor/RadioButtonEditor.svelte +140 -0
  20. package/src/component-editor/SectionDividerEditor.svelte +263 -0
  21. package/src/component-editor/SegmentedControlEditor.svelte +154 -0
  22. package/src/component-editor/StandardButtonsEditor.svelte +178 -0
  23. package/src/component-editor/TabBarEditor.svelte +137 -0
  24. package/src/component-editor/TableEditor.svelte +128 -0
  25. package/src/component-editor/TooltipEditor.svelte +122 -0
  26. package/src/component-editor/editorTokens.test.ts +93 -0
  27. package/src/component-editor/groupKeySlots.test.ts +67 -0
  28. package/src/component-editor/groupKeySnapshot.test.ts +52 -0
  29. package/src/component-editor/index.ts +5 -0
  30. package/src/component-editor/registry.ts +246 -0
  31. package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
  32. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
  33. package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
  34. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
  35. package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
  36. package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
  37. package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
  38. package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
  39. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
  40. package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
  41. package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
  42. package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
  43. package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
  44. package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
  45. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
  46. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
  47. package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
  48. package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
  49. package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
  50. package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
  51. package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
  52. package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
  53. package/src/component-editor/scaffolding/componentSources.ts +9 -0
  54. package/src/component-editor/scaffolding/defaultSections.ts +16 -0
  55. package/src/component-editor/scaffolding/editorContext.ts +44 -0
  56. package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
  57. package/src/component-editor/scaffolding/siblings.ts +33 -0
  58. package/src/component-editor/scaffolding/types.ts +39 -0
  59. package/src/components/Badge.svelte +231 -42
  60. package/src/components/Button.svelte +324 -124
  61. package/src/components/Callout.svelte +145 -0
  62. package/src/components/Card.svelte +123 -25
  63. package/src/components/CollapsibleSection.svelte +213 -35
  64. package/src/components/CornerBadge.svelte +224 -0
  65. package/src/components/Dialog.svelte +137 -114
  66. package/src/components/Image.svelte +43 -0
  67. package/src/components/InlineEditActions.svelte +74 -14
  68. package/src/components/Notification.svelte +184 -163
  69. package/src/components/ProgressBar.svelte +216 -22
  70. package/src/components/RadioButton.svelte +110 -40
  71. package/src/components/SectionDivider.svelte +428 -74
  72. package/src/components/SegmentedControl.svelte +203 -0
  73. package/src/components/TabBar.svelte +146 -21
  74. package/src/components/Table.svelte +102 -0
  75. package/src/components/Tooltip.svelte +45 -19
  76. package/src/components/types.ts +51 -0
  77. package/src/data/google-fonts.json +75 -0
  78. package/src/lib/ColumnsOverlay.svelte +20 -7
  79. package/src/lib/LiveEditorOverlay.svelte +265 -82
  80. package/src/lib/columnsOverlay.ts +21 -17
  81. package/src/lib/componentConfig.test.ts +204 -0
  82. package/src/lib/componentConfigKeys.ts +19 -0
  83. package/src/lib/componentConfigService.ts +88 -0
  84. package/src/lib/copyPopover.ts +30 -0
  85. package/src/lib/cssVarSync.ts +59 -7
  86. package/src/lib/editorConfigStore.ts +0 -10
  87. package/src/lib/editorCore.ts +402 -0
  88. package/src/lib/editorKeybindings.ts +52 -0
  89. package/src/lib/editorPersistence.ts +106 -0
  90. package/src/lib/editorRenderer.ts +74 -0
  91. package/src/lib/editorStore.test.ts +328 -0
  92. package/src/lib/editorStore.ts +412 -0
  93. package/src/lib/editorTypes.ts +100 -0
  94. package/src/lib/editorViewStore.ts +55 -0
  95. package/src/lib/files/versionedFileResource.ts +140 -0
  96. package/src/lib/fontLoader.ts +130 -0
  97. package/src/lib/fontMigration.ts +140 -0
  98. package/src/lib/fontParse.ts +168 -0
  99. package/src/lib/index.ts +48 -31
  100. package/src/lib/lazyConfig.test.ts +54 -0
  101. package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
  102. package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
  103. package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
  104. package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
  105. package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
  106. package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
  107. package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
  108. package/src/lib/migrations/index.ts +93 -0
  109. package/src/lib/migrations/migrations.test.ts +341 -0
  110. package/src/lib/navLinkTypes.ts +1 -0
  111. package/src/lib/overlayState.ts +3 -0
  112. package/src/lib/paletteDerivation.ts +300 -0
  113. package/src/lib/parentRouteStore.ts +42 -0
  114. package/src/lib/parsers/globalRootBlock.ts +32 -0
  115. package/src/lib/presetService.ts +94 -0
  116. package/src/lib/router.ts +49 -0
  117. package/src/lib/scrollSection.ts +45 -0
  118. package/src/lib/slices/columns.ts +59 -0
  119. package/src/lib/slices/components.ts +362 -0
  120. package/src/lib/slices/domainVars.ts +15 -0
  121. package/src/lib/slices/fonts.ts +30 -0
  122. package/src/lib/slices/gradients.ts +153 -0
  123. package/src/lib/slices/overlays.ts +132 -0
  124. package/src/lib/slices/palettes.ts +26 -0
  125. package/src/lib/slices/shadows.ts +123 -0
  126. package/src/lib/storage.ts +88 -0
  127. package/src/lib/themeInit.ts +74 -0
  128. package/src/lib/themeService.ts +101 -0
  129. package/src/lib/themeTypes.ts +146 -0
  130. package/src/lib/tokenRegistry.ts +148 -0
  131. package/src/pages/ComponentEditorPage.svelte +384 -0
  132. package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
  133. package/src/pages/Editor.svelte +98 -0
  134. package/src/pages/Editor.svelte.d.ts +2 -0
  135. package/src/pages/EditorShell.svelte +348 -0
  136. package/src/styles/_padding.scss +34 -0
  137. package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  138. package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  139. package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  140. package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  141. package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  142. package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
  143. package/src/styles/fonts.css +22 -10
  144. package/src/styles/form-controls.css +14 -16
  145. package/src/styles/tokens.css +1322 -0
  146. package/src/styles/ui-editor.css +126 -0
  147. package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
  148. package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
  149. package/src/ui/EditorViewSwitcher.svelte +180 -0
  150. package/src/ui/FontStackEditor.svelte +360 -0
  151. package/src/ui/GradientEditor.svelte +461 -0
  152. package/src/ui/GradientStopPicker.svelte +74 -0
  153. package/src/ui/PaletteEditor.svelte +1590 -0
  154. package/src/ui/PaletteEditor.test.ts +108 -0
  155. package/src/ui/PresetFileManager.svelte +567 -0
  156. package/src/ui/ProjectFontsSection.svelte +645 -0
  157. package/src/{showcase → ui}/SurfacesTab.svelte +39 -41
  158. package/src/{showcase → ui}/TextTab.svelte +27 -29
  159. package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
  160. package/src/ui/Toggle.svelte +108 -0
  161. package/src/ui/UICopyPopover.svelte +78 -0
  162. package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
  163. package/src/ui/UIFontFamilySelector.svelte +309 -0
  164. package/src/ui/UIFontSizeSelector.svelte +165 -0
  165. package/src/ui/UIFontWeightSelector.svelte +52 -0
  166. package/src/ui/UILineHeightSelector.svelte +47 -0
  167. package/src/ui/UILinkToggle.svelte +60 -0
  168. package/src/ui/UIOptionItem.svelte +74 -0
  169. package/src/ui/UIOptionList.svelte +27 -0
  170. package/src/ui/UIPaddingSelector.svelte +661 -0
  171. package/src/ui/UIPaletteSelector.svelte +1084 -0
  172. package/src/ui/UIRadio.svelte +72 -0
  173. package/src/ui/UIRadioGroup.svelte +59 -0
  174. package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
  175. package/src/ui/UITokenSelector.svelte +509 -0
  176. package/src/ui/UIVariantSelector.svelte +145 -0
  177. package/src/ui/VariablesTab.svelte +252 -0
  178. package/src/ui/index.ts +31 -0
  179. package/src/ui/keepInViewport.ts +84 -0
  180. package/src/ui/palette/GradientStopEditor.svelte +482 -0
  181. package/src/ui/palette/OverridesPanel.svelte +526 -0
  182. package/src/ui/palette/PaletteBase.svelte +165 -0
  183. package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
  184. package/src/ui/palette/paletteEditorState.ts +89 -0
  185. package/src/ui/sections/ColumnsSection.svelte +273 -0
  186. package/src/ui/sections/GradientsSection.svelte +147 -0
  187. package/src/ui/sections/OverlaysSection.svelte +670 -0
  188. package/src/ui/sections/ShadowsSection.svelte +1250 -0
  189. package/src/ui/sections/TokenScaleTable.svelte +332 -0
  190. package/src/ui/sections/tokenScales.ts +81 -0
  191. package/src/ui/variantScales.ts +108 -0
  192. package/src/components/DetailNav.svelte +0 -78
  193. package/src/components/Toggle.svelte +0 -86
  194. package/src/lib/pageSource.ts +0 -6
  195. package/src/lib/tokenInit.ts +0 -29
  196. package/src/lib/tokenService.ts +0 -144
  197. package/src/lib/tokenTypes.ts +0 -45
  198. package/src/pages/Admin.svelte +0 -100
  199. package/src/pages/ShowcasePage.svelte +0 -146
  200. package/src/showcase/BackupBrowser.svelte +0 -617
  201. package/src/showcase/ComponentsTab.svelte +0 -107
  202. package/src/showcase/PaletteEditor.svelte +0 -2579
  203. package/src/showcase/PaletteSelector.svelte +0 -627
  204. package/src/showcase/TokenMap.svelte +0 -54
  205. package/src/showcase/VariablesTab.svelte +0 -2657
  206. package/src/showcase/VisualsTab.svelte +0 -233
  207. package/src/showcase/demos/BadgeDemo.svelte +0 -58
  208. package/src/showcase/demos/CardDemo.svelte +0 -52
  209. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -194
  210. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -56
  211. package/src/showcase/demos/DialogDemo.svelte +0 -42
  212. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -27
  213. package/src/showcase/demos/NotificationDemo.svelte +0 -149
  214. package/src/showcase/demos/ProgressBarDemo.svelte +0 -56
  215. package/src/showcase/demos/RadioButtonDemo.svelte +0 -58
  216. package/src/showcase/demos/SectionDividerDemo.svelte +0 -79
  217. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -457
  218. package/src/showcase/demos/TabBarDemo.svelte +0 -60
  219. package/src/showcase/demos/TooltipDemo.svelte +0 -54
  220. package/src/showcase/editor.css +0 -93
  221. package/src/showcase/index.ts +0 -17
  222. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  223. package/src/styles/fonts/Domine/OFL.txt +0 -97
  224. package/src/styles/fonts/Domine/README.txt +0 -66
  225. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -0,0 +1,101 @@
1
+ import { tick } from 'svelte';
2
+ import type { Theme, ThemeMeta } from './themeTypes';
3
+ import type { EditorState } from './editorTypes';
4
+ import {
5
+ versionedFileResource,
6
+ sanitizeFileName as sanitizeFileNameImpl,
7
+ } from './files/versionedFileResource';
8
+ import { loadFromFile as loadEditorState, toTheme, markSaved } from './editorStore';
9
+ import { activeFileName } from './editorConfigStore';
10
+ import { applyFontSources, applyFontStacks } from './fontLoader';
11
+ import { migrateThemeFonts } from './fontMigration';
12
+
13
+ // ── API helpers ──────────────────────────────────────────────
14
+ //
15
+ // All theme CRUD goes through `versionedFileResource('/api/themes')` —
16
+ // shared with `componentConfigService`'s per-component clients. Theme-specific
17
+ // response shapes (ThemeMeta list payload, ProductionInfo) are layered on top
18
+ // via the generic type parameters.
19
+
20
+ export interface ProductionInfo {
21
+ fileName: string;
22
+ name: string;
23
+ updatedAt: string;
24
+ cssVariables: Record<string, string>;
25
+ }
26
+
27
+ const themeResource = versionedFileResource<Theme, ThemeMeta, ProductionInfo>({
28
+ baseUrl: '/api/themes',
29
+ });
30
+
31
+ export async function listThemes(): Promise<ThemeMeta[]> {
32
+ const data = await themeResource.list();
33
+ return data.files;
34
+ }
35
+
36
+ export const loadTheme = (fileName: string): Promise<Theme> => themeResource.load(fileName);
37
+ export const saveTheme = (fileName: string, data: Theme): Promise<void> =>
38
+ themeResource.save(fileName, data);
39
+ export const deleteTheme = (fileName: string): Promise<void> => themeResource.remove(fileName);
40
+
41
+ export async function getActiveTheme(): Promise<Theme | null> {
42
+ return themeResource.getActive();
43
+ }
44
+
45
+ export const setActiveFile = (fileName: string): Promise<void> => themeResource.setActive(fileName);
46
+
47
+ // ── Production API helpers ─────────────────────────────────
48
+
49
+ export const getProductionInfo = (): Promise<ProductionInfo> => themeResource.getProductionInfo();
50
+
51
+ export async function setProductionFile(
52
+ fileName: string,
53
+ ): Promise<{ ok: boolean; fileName: string; name: string }> {
54
+ const data = await themeResource.setProduction(fileName);
55
+ return { ok: data.ok, fileName: data.fileName, name: data.name };
56
+ }
57
+
58
+ /** Sanitize a display name to a safe file name. Re-exported from the shared
59
+ * `files/versionedFileResource` so the dev-server plugin can import the
60
+ * canonical pure helper without depending on this module's CSS imports. */
61
+ export const sanitizeFileName = sanitizeFileNameImpl;
62
+
63
+ // ── Theme save/load orchestration ──────────────────────────
64
+ //
65
+ // `persistTheme` and `hydrateTheme` are the canonical entry points for
66
+ // round-tripping editor state to disk. Callers (e.g. `EditorShell`) need
67
+ // only handle UI-level concerns (status flashing, error chrome) and
68
+ // delegate the actual orchestration here.
69
+
70
+ /** Snapshot the editor state to disk under `fileName`, mark the file active,
71
+ * and clear the dirty flag. The caller is responsible for surfacing
72
+ * saving / saved / error UI states around this call. */
73
+ export async function persistTheme(
74
+ state: EditorState,
75
+ fileName: string,
76
+ displayName: string,
77
+ ): Promise<void> {
78
+ await tick();
79
+ const theme = toTheme(state, { name: displayName });
80
+ await saveTheme(fileName, theme);
81
+ await setActiveFile(fileName);
82
+ activeFileName.set(fileName);
83
+ markSaved();
84
+ }
85
+
86
+ /** Load a theme file into the editor state and re-apply font side-effects
87
+ * (@font-face rules + `--font-*` CSS vars on :root). */
88
+ export async function hydrateTheme(fileName: string): Promise<void> {
89
+ const theme = await loadTheme(fileName);
90
+ migrateThemeFonts(theme);
91
+ loadEditorState(theme);
92
+ // Font data is in state.fonts via loadEditorState; the DOM-side-effect
93
+ // helpers still need to run so @font-face rules and --font-* CSS vars
94
+ // land on :root.
95
+ if (theme.fontSources && theme.fontSources.length > 0) {
96
+ applyFontSources(theme.fontSources);
97
+ }
98
+ if (theme.fontStacks && theme.fontStacks.length > 0) {
99
+ applyFontStacks(theme.fontStacks, theme.fontSources ?? []);
100
+ }
101
+ }
@@ -0,0 +1,146 @@
1
+ import type { CurveAnchor } from '../ui/curveEngine';
2
+
3
+ export type GradientStyle = 'linear' | 'radial' | 'conic';
4
+
5
+ export interface GradientStop {
6
+ position: number;
7
+ paletteLabel: string;
8
+ }
9
+
10
+ export interface PaletteConfig {
11
+ baseColor: string;
12
+ tintHue: number;
13
+ tintChroma?: number;
14
+ lightnessCurve: CurveAnchor[];
15
+ saturationCurve: CurveAnchor[];
16
+ grayLightnessCurve: CurveAnchor[];
17
+ graySaturationCurve: CurveAnchor[];
18
+ scaleCurves: Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[] }>;
19
+ curveOffset: Record<string, number>;
20
+ overrides: Record<string, string>;
21
+ snappedScales: string[];
22
+ emptyMode?: 'solid' | 'gradient';
23
+ emptyStep?: string;
24
+ gradientStyle?: GradientStyle;
25
+ gradientAngle?: number;
26
+ gradientReverse?: boolean;
27
+ gradientStops?: GradientStop[];
28
+ gradientSize?: 'page' | 'window';
29
+ anchorToBase?: boolean;
30
+ }
31
+
32
+ export type FontSourceKind = 'google' | 'typekit' | 'css-url' | 'font-face';
33
+
34
+ export interface FontFamily {
35
+ id: string;
36
+ name: string;
37
+ cssName: string;
38
+ weights?: number[];
39
+ italics?: boolean;
40
+ }
41
+
42
+ export interface FontSource {
43
+ id: string;
44
+ kind: FontSourceKind;
45
+ url?: string;
46
+ cssText?: string;
47
+ families: FontFamily[];
48
+ label?: string;
49
+ }
50
+
51
+ export type SystemCascadePreset = 'system-ui-sans' | 'system-ui-serif' | 'system-ui-mono';
52
+ export type GenericFamily = 'sans-serif' | 'serif' | 'monospace' | 'cursive' | 'fantasy';
53
+ export type FontStackVariable = '--font-display' | '--font-sans' | '--font-serif' | '--font-mono';
54
+
55
+ export type FontStackSlot =
56
+ | { kind: 'project'; familyId: string }
57
+ | { kind: 'system'; preset: SystemCascadePreset }
58
+ | { kind: 'generic'; value: GenericFamily };
59
+
60
+ export interface FontStack {
61
+ variable: FontStackVariable;
62
+ slots: FontStackSlot[];
63
+ }
64
+
65
+ export interface Theme {
66
+ name: string;
67
+ createdAt: string;
68
+ updatedAt: string;
69
+ editorConfigs: Record<string, PaletteConfig>;
70
+ cssVariables: Record<string, string>;
71
+ fontSources?: FontSource[];
72
+ fontStacks?: FontStack[];
73
+ /**
74
+ * Server-attached file-name marker for round-tripping the file identity
75
+ * back to the client. Set by `themeFileApi`'s GET handlers; read by
76
+ * `themeInit` to seed `activeFileName`. Optional and not persisted to disk.
77
+ */
78
+ _fileName?: string;
79
+ /**
80
+ * Migration stamp. Absent on legacy files, treated as 0; the loader runs
81
+ * any registered theme migrations whose `fromVersion >= file.schemaVersion`.
82
+ * Save paths stamp the current value so resaved files skip past
83
+ * migrations.
84
+ */
85
+ schemaVersion?: number;
86
+ }
87
+
88
+ export interface ThemeMeta {
89
+ name: string;
90
+ fileName: string;
91
+ updatedAt: string;
92
+ isActive: boolean;
93
+ }
94
+
95
+ export interface ComponentConfig {
96
+ name: string;
97
+ component: string;
98
+ createdAt: string;
99
+ updatedAt: string;
100
+ aliases: Record<string, string>;
101
+ config?: Record<string, unknown>;
102
+ /**
103
+ * Server-attached file-name marker. Same role as `Theme._fileName`. Set by
104
+ * the component-configs GET handlers; not persisted to disk.
105
+ */
106
+ _fileName?: string;
107
+ /**
108
+ * Migration stamp. Absent on legacy files, treated as 0. See `Theme.schemaVersion`.
109
+ */
110
+ schemaVersion?: number;
111
+ }
112
+
113
+ export interface ComponentConfigMeta {
114
+ name: string;
115
+ fileName: string;
116
+ updatedAt: string;
117
+ isActive: boolean;
118
+ isProduction: boolean;
119
+ }
120
+
121
+ /**
122
+ * Manifest that captures an entire site state — the active theme plus the
123
+ * active config for every component. Loading a preset flips the relevant
124
+ * `_active.json` pointers; the underlying theme + component-config files stay
125
+ * the source of truth, so editing them flows through any preset that
126
+ * references them.
127
+ */
128
+ export interface Preset {
129
+ name: string;
130
+ createdAt: string;
131
+ updatedAt: string;
132
+ /** File basename (no `.json`) of the theme this preset pins. */
133
+ theme: string;
134
+ /** Map of componentId → config file basename. Components omitted here fall
135
+ * back to "default" at apply time. */
136
+ componentConfigs: Record<string, string>;
137
+ /** Server-attached file-name marker. Same role as `Theme._fileName`. */
138
+ _fileName?: string;
139
+ }
140
+
141
+ export interface PresetMeta {
142
+ name: string;
143
+ fileName: string;
144
+ updatedAt: string;
145
+ isActive: boolean;
146
+ }
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Static registry of CSS custom-property declarations.
3
+ *
4
+ * Layer-1 design tokens live in tokens.css (the single source of truth for
5
+ * shared primitives). Layer-2 component tokens live inside each component's
6
+ * `<style>` block as `:global(:root) { ... }` declarations — owned by the
7
+ * component that uses them.
8
+ *
9
+ * Token-picking UIs need to recover the semantic identity of an arbitrary
10
+ * variable — e.g. to display "info text / primary" rather than a raw hex. For
11
+ * alias variables we must follow the `var(--x)` chain from the declaration to
12
+ * find the underlying token whose *name* the selector can parse.
13
+ *
14
+ * This module parses both sources at import time (tokens.css via Vite's
15
+ * ?raw loader, component files via import.meta.glob) and exposes helpers that
16
+ * walk those aliases. The pure `buildTokenRegistry` and `extractGlobalRootBody`
17
+ * helpers are also exported so tests can construct a registry from fs-loaded
18
+ * files (Vitest's default CSS plugin swallows `?raw` imports for .css files).
19
+ */
20
+
21
+ import { derived, type Readable } from 'svelte/store';
22
+ import tokensCss from '../styles/tokens.css?raw';
23
+ import { editorState } from './editorStore';
24
+ import type { EditorState } from './editorTypes';
25
+ import { extractGlobalRootBody } from './parsers/globalRootBlock';
26
+
27
+ // Re-exported for tests and downstream consumers that previously imported it
28
+ // from this module. The canonical implementation lives in `./parsers/globalRootBlock`
29
+ // so the dev-server vite plugin can share it.
30
+ export { extractGlobalRootBody };
31
+
32
+ const componentSources = import.meta.glob('../components/*.svelte', {
33
+ query: '?raw',
34
+ import: 'default',
35
+ eager: true,
36
+ }) as Record<string, string>;
37
+
38
+ export interface TokenRegistry {
39
+ getDeclaredValue(varName: string): string | null;
40
+ resolveAliasChain(varName: string): string[];
41
+ }
42
+
43
+ /**
44
+ * Pure constructor: parses a CSS source string (possibly concatenated from
45
+ * multiple files) and returns a registry bound to that snapshot.
46
+ */
47
+ export function buildTokenRegistry(cssText: string): TokenRegistry {
48
+ const declarations = new Map<string, string>();
49
+ const re = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
50
+ let m: RegExpExecArray | null;
51
+ while ((m = re.exec(cssText)) !== null) {
52
+ declarations.set(m[1], m[2].trim());
53
+ }
54
+
55
+ function resolveAliasChain(varName: string): string[] {
56
+ const chain = [varName];
57
+ const visited = new Set<string>([varName]);
58
+ let current = varName;
59
+ while (true) {
60
+ const decl = declarations.get(current);
61
+ if (!decl) return chain;
62
+ const aliasMatch = decl.match(/var\((--[a-z0-9-]+)\)/i);
63
+ if (!aliasMatch) return chain;
64
+ const next = aliasMatch[1];
65
+ if (visited.has(next)) return chain;
66
+ visited.add(next);
67
+ chain.push(next);
68
+ current = next;
69
+ }
70
+ }
71
+
72
+ return {
73
+ getDeclaredValue: (v) => declarations.get(v) ?? null,
74
+ resolveAliasChain,
75
+ };
76
+ }
77
+
78
+ const componentTokenCss = Object.values(componentSources)
79
+ .map(extractGlobalRootBody)
80
+ .join('\n');
81
+
82
+ const defaultRegistry = buildTokenRegistry(tokensCss + '\n' + componentTokenCss);
83
+
84
+ /** Raw declared value from tokens.css, or null if not declared. */
85
+ export const getDeclaredValue = defaultRegistry.getDeclaredValue;
86
+
87
+ /**
88
+ * Walk the alias chain starting at `varName`, returning each successive
89
+ * variable reference (including the starting one). Stops when a declaration is
90
+ * a literal value (e.g. hex) or when the next link is absent from the
91
+ * registry. Guards against cycles.
92
+ *
93
+ * Example: `--notification-info-title` → `--text-info` → stops (literal hex).
94
+ * Returns `['--notification-info-title', '--text-info']`.
95
+ *
96
+ * This is a static snapshot: it only sees declarations from tokens.css + the
97
+ * baked-in `:global(:root)` blocks at module load. For state-aware resolution
98
+ * that includes live component-alias edits, subscribe to `tokenRegistry$`.
99
+ */
100
+ export const resolveAliasChain = defaultRegistry.resolveAliasChain;
101
+
102
+ /**
103
+ * Build a registry that layers live component-alias overrides on top of the
104
+ * static base. Cheap enough to rebuild per editorState tick — components
105
+ * count is small and the alias walk is lazy.
106
+ */
107
+ function buildOverlayRegistry(
108
+ base: TokenRegistry,
109
+ components: EditorState['components'],
110
+ ): TokenRegistry {
111
+ const overrides = new Map<string, string>();
112
+ for (const slice of Object.values(components)) {
113
+ for (const [varName, ref] of Object.entries(slice.aliases)) {
114
+ overrides.set(varName, ref.kind === 'token' ? `var(${ref.name})` : ref.value);
115
+ }
116
+ }
117
+ const getDeclared = (v: string): string | null =>
118
+ overrides.has(v) ? overrides.get(v)! : base.getDeclaredValue(v);
119
+ return {
120
+ getDeclaredValue: getDeclared,
121
+ resolveAliasChain(varName: string): string[] {
122
+ const chain = [varName];
123
+ const visited = new Set<string>([varName]);
124
+ let current = varName;
125
+ while (true) {
126
+ const decl = getDeclared(current);
127
+ if (!decl) return chain;
128
+ const aliasMatch = decl.match(/var\((--[a-z0-9-]+)\)/i);
129
+ if (!aliasMatch) return chain;
130
+ const next = aliasMatch[1];
131
+ if (visited.has(next)) return chain;
132
+ visited.add(next);
133
+ chain.push(next);
134
+ current = next;
135
+ }
136
+ },
137
+ };
138
+ }
139
+
140
+ /**
141
+ * State-aware token registry: overlays live `state.components[*].aliases`
142
+ * on top of the static base registry. Consume with `$tokenRegistry$` in
143
+ * Svelte components when the UI needs to reflect in-flight alias edits
144
+ * (e.g. selectors displaying the currently-selected semantic token).
145
+ */
146
+ export const tokenRegistry$: Readable<TokenRegistry> = derived(editorState, ($state) =>
147
+ buildOverlayRegistry(defaultRegistry, $state.components),
148
+ );