@motion-proto/live-tokens 0.1.1 → 0.3.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.
Files changed (224) hide show
  1. package/README.md +168 -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 +46 -20
  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 +257 -78
  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 -30
  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 +42 -10
  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 -39
  158. package/src/{showcase → ui}/TextTab.svelte +27 -27
  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/tokenInit.ts +0 -29
  195. package/src/lib/tokenService.ts +0 -144
  196. package/src/lib/tokenTypes.ts +0 -45
  197. package/src/pages/Admin.svelte +0 -100
  198. package/src/pages/ShowcasePage.svelte +0 -144
  199. package/src/showcase/BackupBrowser.svelte +0 -617
  200. package/src/showcase/ComponentsTab.svelte +0 -105
  201. package/src/showcase/PaletteEditor.svelte +0 -2579
  202. package/src/showcase/PaletteSelector.svelte +0 -627
  203. package/src/showcase/TokenMap.svelte +0 -54
  204. package/src/showcase/VariablesTab.svelte +0 -2655
  205. package/src/showcase/VisualsTab.svelte +0 -231
  206. package/src/showcase/demos/BadgeDemo.svelte +0 -56
  207. package/src/showcase/demos/CardDemo.svelte +0 -50
  208. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
  209. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
  210. package/src/showcase/demos/DialogDemo.svelte +0 -42
  211. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
  212. package/src/showcase/demos/NotificationDemo.svelte +0 -147
  213. package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
  214. package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
  215. package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
  216. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
  217. package/src/showcase/demos/TabBarDemo.svelte +0 -58
  218. package/src/showcase/demos/TooltipDemo.svelte +0 -52
  219. package/src/showcase/editor.css +0 -93
  220. package/src/showcase/index.ts +0 -17
  221. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  222. package/src/styles/fonts/Domine/OFL.txt +0 -97
  223. package/src/styles/fonts/Domine/README.txt +0 -66
  224. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -0,0 +1,82 @@
1
+ <script lang="ts">
2
+ import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
3
+ import UIVariantSelector from '../../ui/UIVariantSelector.svelte';
4
+ import UIFontFamilySelector from '../../ui/UIFontFamilySelector.svelte';
5
+ import UIFontSizeSelector from '../../ui/UIFontSizeSelector.svelte';
6
+ import UIFontWeightSelector from '../../ui/UIFontWeightSelector.svelte';
7
+ import UILineHeightSelector from '../../ui/UILineHeightSelector.svelte';
8
+ import FieldsetWrapper from './FieldsetWrapper.svelte';
9
+ import { BORDER_WIDTH } from '../../ui/variantScales';
10
+
11
+ export let colorVariable: string;
12
+ export let colorLabel: string = 'color';
13
+ export let familyVariable: string | undefined = undefined;
14
+ export let familyLabel: string = 'family';
15
+ export let sizeVariable: string | undefined = undefined;
16
+ export let sizeLabel: string = 'size';
17
+ export let weightVariable: string | undefined = undefined;
18
+ export let weightLabel: string = 'weight';
19
+ export let lineHeightVariable: string | undefined = undefined;
20
+ export let lineHeightLabel: string = 'line-h';
21
+ /** Optional outline rows rendered under the typography rows so a text-with-
22
+ stroke group keeps stroke controls visually nested with the type they
23
+ drive (e.g. SectionDivider title outline). */
24
+ export let outlineWidthVariable: string | undefined = undefined;
25
+ export let outlineWidthLabel: string = 'outline thickness';
26
+ export let outlineColorVariable: string | undefined = undefined;
27
+ export let outlineColorLabel: string = 'outline color';
28
+ /** When set, writes persist through the editor store under this component. */
29
+ export let component: string | undefined = undefined;
30
+ /** Legend text for the fieldset. */
31
+ export let legend: string = 'type';
32
+ </script>
33
+
34
+ <FieldsetWrapper {legend}>
35
+ <div class="type-grid">
36
+ <span class="row-label">{colorLabel}</span>
37
+ <UIPaletteSelector variable={colorVariable} {component} on:change />
38
+
39
+ {#if familyVariable}
40
+ <span class="row-label">{familyLabel}</span>
41
+ <UIFontFamilySelector variable={familyVariable} {component} canBeLinked on:change />
42
+ {/if}
43
+ {#if weightVariable}
44
+ <span class="row-label">{weightLabel}</span>
45
+ <UIFontWeightSelector variable={weightVariable} {component} canBeLinked on:change />
46
+ {/if}
47
+ {#if sizeVariable}
48
+ <span class="row-label">{sizeLabel}</span>
49
+ <UIFontSizeSelector variable={sizeVariable} {component} canBeLinked on:change />
50
+ {/if}
51
+ {#if lineHeightVariable}
52
+ <span class="row-label">{lineHeightLabel}</span>
53
+ <UILineHeightSelector variable={lineHeightVariable} {component} canBeLinked on:change />
54
+ {/if}
55
+ {#if outlineWidthVariable}
56
+ <span class="row-label">{outlineWidthLabel}</span>
57
+ <UIVariantSelector variable={outlineWidthVariable} {component} canBeLinked {...BORDER_WIDTH} on:change />
58
+ {/if}
59
+ {#if outlineColorVariable}
60
+ <span class="row-label">{outlineColorLabel}</span>
61
+ <UIPaletteSelector variable={outlineColorVariable} {component} canBeLinked on:change />
62
+ {/if}
63
+ </div>
64
+ </FieldsetWrapper>
65
+
66
+ <style>
67
+ .type-grid {
68
+ display: grid;
69
+ grid-template-columns: max-content 8rem 1fr;
70
+ column-gap: var(--ui-space-10);
71
+ row-gap: var(--ui-space-6);
72
+ align-items: center;
73
+ padding: var(--ui-space-4) var(--ui-space-12);
74
+ }
75
+
76
+ .row-label {
77
+ font-size: var(--ui-font-size-sm);
78
+ color: var(--ui-text-secondary);
79
+ text-align: left;
80
+ line-height: 1;
81
+ }
82
+ </style>
@@ -0,0 +1,277 @@
1
+ <script lang="ts">
2
+ import { writable } from 'svelte/store';
3
+ import TokenLayout from './TokenLayout.svelte';
4
+ import StateBlock from './StateBlock.svelte';
5
+ import CopyFromMenu from './CopyFromMenu.svelte';
6
+ import { mutate } from '../../lib/editorStore';
7
+ import { getEditorContext } from './editorContext';
8
+ import type { Token, TypeGroupConfig } from './types';
9
+ import type { Sibling } from './siblings';
10
+
11
+ export let name: string;
12
+ export let title: string;
13
+ export let tokens: Token[] = [];
14
+ export let states: Record<string, Token[]> | null = null;
15
+ /** Per-state type groups; rendered as TypeEditor blocks alongside the state's TokenLayout. */
16
+ export let typeGroups: Record<string, TypeGroupConfig[]> = {};
17
+ /** When set, overrides are read from and cleared through the editor store. */
18
+ export let component: string | undefined = undefined;
19
+ /** Sibling variants of this component (excludes self). When non-empty,
20
+ a "Copy from" menu is rendered that lets the user pull token values from
21
+ a sibling's same-state into the current state. */
22
+ export let siblings: Sibling[] = [];
23
+ /** Forwarded to StateBlock → TokenLayout. >1 lays out the property grid
24
+ across multiple visual columns (column-major flow). Useful for
25
+ single-text components like Button whose 8-10 properties stretch the
26
+ panel vertically when stacked single-column. */
27
+ export let columns: number = 1;
28
+ /** Label rendered above the state-tab selector strip. Defaults to "Element"
29
+ because most strips mix structural parts (e.g. bar, frame) with
30
+ component states; "States" would mislabel the parts. Editors can override
31
+ when every tab on the strip really is a state. */
32
+ export let selectorLabel: string = 'Element';
33
+
34
+ const editorCtx = getEditorContext();
35
+ const linkedOrderStore = editorCtx?.linkedOrder ?? writable<Map<string, number> | null>(null);
36
+ const focusedVariantStore = editorCtx?.focusedVariant ?? writable<string | null>(null);
37
+ const focusedStateStore = editorCtx?.focusedState ?? writable<string | null>(null);
38
+ $: linkedOrder = $linkedOrderStore ?? undefined;
39
+
40
+ let activeTab: string = '';
41
+
42
+ const TYPE_PROPS = ['colorVariable', 'familyVariable', 'sizeVariable', 'weightVariable', 'lineHeightVariable', 'outlineWidthVariable', 'outlineColorVariable'] as const;
43
+
44
+ function pickCopySource(toState: string, fromVariant: string, fromState: string) {
45
+ if (!component || !states) return;
46
+ const isSelfVariant = fromVariant === name;
47
+ const sibling = isSelfVariant ? null : siblings.find((s) => s.name === fromVariant);
48
+ if (!isSelfVariant && !sibling) return;
49
+ const srcTokens = (isSelfVariant ? states[fromState] : sibling!.states[fromState]) ?? [];
50
+ const dstTokens = states[toState] ?? [];
51
+ const srcTypeGroups = (isSelfVariant ? typeGroups[fromState] : sibling!.typeGroups?.[fromState]) ?? [];
52
+ const dstTypeGroups = typeGroups[toState] ?? [];
53
+
54
+ mutate(`copy ${fromVariant}/${fromState} → ${name}/${toState}`, (s) => {
55
+ const slice = s.components[component!] ?? (s.components[component!] = { activeFile: 'default', aliases: {}, config: {} });
56
+ const apply = (srcVar: string, dstVar: string) => {
57
+ if (srcVar === dstVar) return;
58
+ if (srcVar in slice.aliases) slice.aliases[dstVar] = slice.aliases[srcVar];
59
+ else delete slice.aliases[dstVar];
60
+ };
61
+ const minLen = Math.min(srcTokens.length, dstTokens.length);
62
+ for (let i = 0; i < minLen; i++) apply(srcTokens[i].variable, dstTokens[i].variable);
63
+ const minTypeGroups = Math.min(srcTypeGroups.length, dstTypeGroups.length);
64
+ for (let g = 0; g < minTypeGroups; g++) {
65
+ const srcType = srcTypeGroups[g];
66
+ const dstType = dstTypeGroups[g];
67
+ for (const prop of TYPE_PROPS) {
68
+ const srcVar = srcType[prop];
69
+ const dstVar = dstType[prop];
70
+ if (srcVar && dstVar) apply(srcVar, dstVar);
71
+ }
72
+ }
73
+ });
74
+ }
75
+
76
+ $: copySources = (() => {
77
+ const fromSiblings = siblings.map((s) => ({
78
+ name: s.name,
79
+ label: s.label,
80
+ states: Object.keys(s.states),
81
+ }));
82
+ const ownStates = states ? Object.keys(states) : [];
83
+ if (ownStates.length >= 2) {
84
+ return [{ name, label: title, states: ownStates }, ...fromSiblings];
85
+ }
86
+ return fromSiblings;
87
+ })();
88
+
89
+ $: stateNames = states ? Object.keys(states) : [];
90
+ $: tabsStripVisible = stateNames.length >= 2;
91
+ $: if (stateNames.length > 0 && !stateNames.includes(activeTab)) {
92
+ activeTab = stateNames[0];
93
+ }
94
+ // Cross-group hint from chart row clicks: adopt it if it names one of our states.
95
+ $: if ($focusedStateStore && stateNames.includes($focusedStateStore) && activeTab !== $focusedStateStore) {
96
+ activeTab = $focusedStateStore;
97
+ }
98
+
99
+ $: inFocusMode = siblings.length > 0;
100
+ $: amIFocused = $focusedVariantStore === name;
101
+ $: shouldRender = !inFocusMode || amIFocused;
102
+ // Mirror this group's active state back to the shared store when this is the
103
+ // focused variant, so the linked-block row + chart selection track the user's
104
+ // state-tab clicks (not just chart-row clicks).
105
+ $: if (amIFocused && activeTab && stateNames.includes(activeTab) && $focusedStateStore !== activeTab) {
106
+ focusedStateStore.set(activeTab);
107
+ }
108
+
109
+ </script>
110
+
111
+ {#if shouldRender}
112
+ <div class="demo-section variant-group">
113
+ {#if !inFocusMode}
114
+ <div class="variant-header">
115
+ <h3 class="demo-subtitle">{title}</h3>
116
+ </div>
117
+ {/if}
118
+
119
+ {#if states}
120
+ <!-- Preview at top, then state tabs + Copy from, then properties for active tab. -->
121
+ <div class="tabs-preview">
122
+ <span class="section-label">Preview</span>
123
+ <slot activeState={activeTab} />
124
+ </div>
125
+
126
+ {#if tabsStripVisible || (copySources.length > 0 && activeTab)}
127
+ <div class="tabs-states-block">
128
+ {#if tabsStripVisible}
129
+ <span class="section-label">{selectorLabel}</span>
130
+ {/if}
131
+ <div class="tabs-selectors">
132
+ {#if tabsStripVisible}
133
+ <div class="state-tabs" role="tablist">
134
+ {#each stateNames as s}
135
+ <button
136
+ type="button"
137
+ class="state-tab-btn"
138
+ class:active={activeTab === s}
139
+ role="tab"
140
+ aria-selected={activeTab === s}
141
+ on:click={() => { activeTab = s; focusedStateStore.set(s); }}
142
+ >{s}</button>
143
+ {/each}
144
+ </div>
145
+ {/if}
146
+ {#if activeTab}
147
+ <slot name="state-actions" stateName={activeTab} />
148
+ {/if}
149
+ {#if copySources.length > 0 && activeTab}
150
+ <CopyFromMenu
151
+ toState={activeTab}
152
+ variantName={name}
153
+ {copySources}
154
+ on:select={(e) => pickCopySource(activeTab, e.detail.fromVariant, e.detail.fromState)}
155
+ />
156
+ {/if}
157
+ </div>
158
+ </div>
159
+ {/if}
160
+
161
+ {#if activeTab && states[activeTab]}
162
+ {@const stateName = activeTab}
163
+ <slot name="composite-controls" {stateName} />
164
+ <span class="section-label">Properties</span>
165
+ <StateBlock
166
+ tokens={states[stateName]}
167
+ typeGroups={typeGroups[stateName] ?? []}
168
+ {component}
169
+ {linkedOrder}
170
+ {columns}
171
+ on:change
172
+ />
173
+ {/if}
174
+ {:else}
175
+ <slot activeState="" />
176
+ <TokenLayout
177
+ title={name}
178
+ tokens={tokens}
179
+ {component}
180
+ {linkedOrder}
181
+ on:change
182
+ />
183
+ {/if}
184
+
185
+ </div>
186
+ {/if}
187
+
188
+ <style>
189
+ .variant-group {
190
+ padding: var(--ui-space-16);
191
+ background: var(--ui-surface-low);
192
+ border: 1px solid var(--ui-border-faint);
193
+ border-radius: var(--ui-radius-md);
194
+ gap: var(--ui-space-12);
195
+ }
196
+
197
+ .variant-header {
198
+ display: flex;
199
+ align-items: center;
200
+ justify-content: flex-start;
201
+ gap: var(--ui-space-12);
202
+ }
203
+
204
+ .variant-header .demo-subtitle {
205
+ margin: 0;
206
+ font-size: var(--ui-font-size-2xl);
207
+ font-weight: var(--ui-font-weight-semibold);
208
+ color: var(--ui-text-primary);
209
+ }
210
+
211
+ .tabs-preview {
212
+ display: flex;
213
+ flex-direction: column;
214
+ gap: var(--ui-space-8);
215
+ }
216
+
217
+ .tabs-states-block {
218
+ display: flex;
219
+ flex-direction: column;
220
+ gap: var(--ui-space-8);
221
+ }
222
+
223
+ .tabs-selectors {
224
+ display: flex;
225
+ align-items: center;
226
+ gap: var(--ui-space-12);
227
+ }
228
+
229
+ .state-tabs {
230
+ display: inline-flex;
231
+ flex-wrap: wrap;
232
+ gap: var(--ui-space-4);
233
+ padding: var(--ui-space-4);
234
+ background: var(--ui-surface-lowest);
235
+ border: 1px solid var(--ui-border-faint);
236
+ border-radius: var(--ui-radius-md);
237
+ }
238
+
239
+ .state-tab-btn {
240
+ padding: var(--ui-space-6) var(--ui-space-12);
241
+ background: none;
242
+ border: none;
243
+ border-radius: var(--ui-radius-sm);
244
+ color: var(--ui-text-secondary);
245
+ font-size: var(--ui-font-size-sm);
246
+ font-weight: var(--ui-font-weight-medium);
247
+ text-transform: capitalize;
248
+ cursor: pointer;
249
+ transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
250
+ }
251
+
252
+ .state-tab-btn:hover:not(.active) {
253
+ color: var(--ui-text-primary);
254
+ background: var(--ui-hover);
255
+ }
256
+
257
+ .state-tab-btn.active {
258
+ color: var(--ui-text-primary);
259
+ background: var(--ui-surface-high);
260
+ box-shadow: 0 0 0 1px var(--ui-border-default);
261
+ }
262
+
263
+ .section-label {
264
+ display: block;
265
+ margin: 0;
266
+ font-size: var(--ui-font-size-md);
267
+ font-weight: 500;
268
+ color: var(--ui-text-primary);
269
+ }
270
+
271
+ /* The Properties label is a direct child of .variant-group (which doesn't
272
+ apply a flex gap), so space it from the tabs strip above. */
273
+ .variant-group > .section-label {
274
+ margin-top: var(--ui-space-8);
275
+ }
276
+
277
+ </style>
@@ -0,0 +1,97 @@
1
+ import type { Token, TypeGroupConfig } from './types';
2
+
3
+ /** The four font-shape properties on a TypeGroupConfig that share-block sharing
4
+ targets. Order is the canonical render order and the groupKey order. */
5
+ export const TYPE_FONT_PROPS = [
6
+ { key: 'familyVariable', label: 'font family', defaultGroupKey: 'font-family' },
7
+ { key: 'sizeVariable', label: 'font size', defaultGroupKey: 'font-size' },
8
+ { key: 'weightVariable', label: 'font weight', defaultGroupKey: 'font-weight' },
9
+ { key: 'lineHeightVariable', label: 'line height', defaultGroupKey: 'line-height' },
10
+ ] as const satisfies ReadonlyArray<{
11
+ key: keyof TypeGroupConfig;
12
+ label: string;
13
+ defaultGroupKey: string;
14
+ }>;
15
+
16
+ export type TypeFontProp = typeof TYPE_FONT_PROPS[number];
17
+
18
+ export type BuildTypeGroupTokensOptions = {
19
+ /** Override the default groupKey per property. Receives the prop descriptor and the
20
+ type-group config it came from. Return a stable string — siblings sharing the same
21
+ groupKey are linked in the linked block. */
22
+ groupKeyFor?: (prop: TypeFontProp, group: TypeGroupConfig) => string;
23
+ };
24
+
25
+ /** Derive the Token[] schema entries for every TypeGroupConfig in `typeGroups`. Each
26
+ group emits one `colorVariable` token plus up to 4 font-shape tokens (family/size/
27
+ weight/line-height) for whichever of those are declared on the group. Font-shape
28
+ tokens carry `canBeLinked: true` and a stable `groupKey` so the linked-block linkage
29
+ sees them; the color token is emitted plain (no groupKey, not shareable) so it stays
30
+ out of the linked block while still appearing in the editor's full token surface
31
+ (used by the reset-button and the design-token resolution test).
32
+
33
+ Mirrors the `flatMap`/loop pattern in StandardButtonsEditor and RadioButtonEditor so
34
+ editors don't have to hand-list 16+ near-identical Token entries. */
35
+ export function buildTypeGroupTokens(
36
+ typeGroups: Record<string, TypeGroupConfig[]>,
37
+ options: BuildTypeGroupTokensOptions = {},
38
+ ): Token[] {
39
+ const { groupKeyFor } = options;
40
+ const tokens: Token[] = [];
41
+ for (const groups of Object.values(typeGroups)) {
42
+ for (const group of groups) {
43
+ tokens.push({ label: group.colorLabel ?? 'color', variable: group.colorVariable });
44
+ for (const prop of TYPE_FONT_PROPS) {
45
+ const variable = group[prop.key];
46
+ if (!variable) continue;
47
+ const groupKey = groupKeyFor ? groupKeyFor(prop, group) : prop.defaultGroupKey;
48
+ tokens.push({ label: prop.label, canBeLinked: true, groupKey, variable });
49
+ }
50
+ }
51
+ }
52
+ return tokens;
53
+ }
54
+
55
+ /** Color-only counterpart for editors that hand-roll their font-shape tokens (because
56
+ they use a custom groupKey scheme) but still want their `colorVariable`s in
57
+ `allTokens` — needed so the reset-button and the design-token resolution test see
58
+ them. Accepts either the full `Record` shape or a flat group array, so it slots
59
+ cleanly into both `Object.values(typeGroups).flat()` chains and per-variant
60
+ `flatMap` constructions.
61
+
62
+ Each color token gets a groupKey derived from the colorVariable's last-dash
63
+ suffix (e.g. `--badge-primary-text` → `text`) so that all variants/states of
64
+ the same slot are siblings and can be linked in the editor. They start out
65
+ divergent (one value per variant) and the user can opt in to a single shared
66
+ value via the link UI. */
67
+ export function buildTypeGroupColorTokens(
68
+ typeGroups: Record<string, TypeGroupConfig[]> | TypeGroupConfig[],
69
+ ): Token[] {
70
+ const groups: TypeGroupConfig[] = Array.isArray(typeGroups)
71
+ ? typeGroups
72
+ : Object.values(typeGroups).flat();
73
+ return groups.map((g) => {
74
+ const lastDash = g.colorVariable.lastIndexOf('-');
75
+ const groupKey = lastDash >= 0 ? g.colorVariable.slice(lastDash + 1) : g.colorVariable;
76
+ return { label: g.colorLabel ?? 'color', variable: g.colorVariable, groupKey };
77
+ });
78
+ }
79
+
80
+ /** Companion helper: derive a `linkableContexts` map mapping every typography variable in
81
+ `typeGroups` to its state name. Use to build the linked-block context map without
82
+ spelling out 16+ entries; merge with non-typography entries via spread. */
83
+ export function buildTypeGroupShareableContexts(
84
+ typeGroups: Record<string, TypeGroupConfig[]>,
85
+ ): Array<readonly [string, string]> {
86
+ const entries: Array<readonly [string, string]> = [];
87
+ for (const [stateName, groups] of Object.entries(typeGroups)) {
88
+ for (const group of groups) {
89
+ for (const prop of TYPE_FONT_PROPS) {
90
+ const variable = group[prop.key];
91
+ if (!variable) continue;
92
+ entries.push([variable, stateName] as const);
93
+ }
94
+ }
95
+ }
96
+ return entries;
97
+ }
@@ -0,0 +1,8 @@
1
+ import type { ComponentType } from 'svelte';
2
+
3
+ export type ComponentSection = {
4
+ id: string;
5
+ label: string;
6
+ component: ComponentType;
7
+ props?: Record<string, unknown>;
8
+ };
@@ -0,0 +1,9 @@
1
+ import { componentRegistry } from '../registry';
2
+
3
+ /**
4
+ * Resolve a component id to its runtime source file path. Reads from the
5
+ * single component registry — no parallel mapping to maintain.
6
+ */
7
+ export function componentSourceFile(component: string): string {
8
+ return componentRegistry[component as keyof typeof componentRegistry]?.sourceFile ?? '';
9
+ }
@@ -0,0 +1,16 @@
1
+ import type { ComponentSection } from './componentSectionType';
2
+ import { componentRegistryEntries } from '../registry';
3
+
4
+ /**
5
+ * Default editor sections — derived from the single component registry. Each
6
+ * section's `id` is the canonical lowercase component id (matches the runtime
7
+ * filename, server scan, and `setComponentAlias` key); `label` is the
8
+ * display string; `component` is the editor Svelte component.
9
+ *
10
+ * To add or reorder sections, edit `src/component-editor/registry.ts`.
11
+ */
12
+ export const defaultSections: ComponentSection[] = componentRegistryEntries.map((entry) => ({
13
+ id: entry.id,
14
+ label: entry.label,
15
+ component: entry.editorComponent,
16
+ }));
@@ -0,0 +1,44 @@
1
+ import { getContext, setContext } from 'svelte';
2
+ import { writable, type Writable, type Readable } from 'svelte/store';
3
+
4
+ const KEY = Symbol('editor-context');
5
+
6
+ export type EditorContext = {
7
+ /** Per-variable rank used by TokenLayout to align linked rows; null when no linked block. */
8
+ linkedOrder: Readable<Map<string, number> | null>;
9
+ /** Variant currently focused in the preview when multiple sibling variants exist. */
10
+ focusedVariant: Writable<string | null>;
11
+ /** Cross-group hint for which state tab to activate. VariantGroups whose `stateNames`
12
+ contain the value adopt it as their `activeTab`; others ignore it. Used to forward
13
+ LinkageChart row clicks to the state tab strip when the chart spans states. */
14
+ focusedState: Writable<string | null>;
15
+ /** Variable currently hovered in either the per-state Properties grid or the
16
+ Linked-properties block. Bidirectional cue: a hover in one surface lights up
17
+ the matching row in the other so the user can see the linkage at a glance. */
18
+ hoveredLinkedVariable: Writable<string | null>;
19
+ };
20
+
21
+ /** Internal mutable handle used by ComponentEditorBase. */
22
+ export type EditorContextInternal = EditorContext & {
23
+ _linkedOrder: Writable<Map<string, number> | null>;
24
+ };
25
+
26
+ export function createEditorContext(): EditorContextInternal {
27
+ const _linkedOrder = writable<Map<string, number> | null>(null);
28
+ const focusedVariant = writable<string | null>(null);
29
+ const focusedState = writable<string | null>(null);
30
+ const hoveredLinkedVariable = writable<string | null>(null);
31
+ const ctx: EditorContextInternal = {
32
+ linkedOrder: _linkedOrder,
33
+ focusedVariant,
34
+ focusedState,
35
+ hoveredLinkedVariable,
36
+ _linkedOrder,
37
+ };
38
+ setContext(KEY, ctx);
39
+ return ctx;
40
+ }
41
+
42
+ export function getEditorContext(): EditorContext | undefined {
43
+ return getContext(KEY);
44
+ }