@motion-proto/live-tokens 0.6.2 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (232) hide show
  1. package/README.md +14 -13
  2. package/dist-plugin/index.cjs +854 -226
  3. package/dist-plugin/index.d.cts +2 -1
  4. package/dist-plugin/index.d.ts +2 -1
  5. package/dist-plugin/index.js +852 -225
  6. package/package.json +26 -40
  7. package/src/{styles → app}/site.css +1 -1
  8. package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
  9. package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
  10. package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
  11. package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +37 -30
  12. package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
  13. package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
  14. package/src/editor/component-editor/ImageEditor.svelte +30 -0
  15. package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
  16. package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
  17. package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +67 -38
  18. package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
  19. package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
  20. package/src/editor/component-editor/SectionDividerEditor.svelte +565 -0
  21. package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
  22. package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +29 -21
  23. package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
  24. package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
  25. package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
  26. package/src/editor/component-editor/editors.d.ts +10 -0
  27. package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
  28. package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +54 -15
  29. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
  30. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +151 -424
  31. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
  32. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
  33. package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
  34. package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
  35. package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
  36. package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -12
  37. package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
  38. package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
  39. package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
  40. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +85 -0
  41. package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
  42. package/src/editor/component-editor/scaffolding/StateBlock.svelte +345 -0
  43. package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +17 -12
  44. package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +13 -1
  45. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +858 -0
  46. package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +1 -0
  47. package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
  48. package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
  49. package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +25 -0
  50. package/src/{lib → editor/core/components}/componentConfigKeys.ts +8 -0
  51. package/src/{lib → editor/core/components}/componentConfigService.ts +3 -3
  52. package/src/{lib → editor/core/components}/componentPersist.ts +11 -9
  53. package/src/editor/core/flashStatus.ts +30 -0
  54. package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
  55. package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
  56. package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
  57. package/src/editor/core/manifests/manifestService.ts +171 -0
  58. package/src/editor/core/palettes/familySwap.ts +99 -0
  59. package/src/{lib → editor/core/palettes}/paletteDerivation.ts +71 -2
  60. package/src/{lib → editor/core/palettes}/tokenRegistry.ts +9 -6
  61. package/src/editor/core/productionPulse.ts +37 -0
  62. package/src/{lib → editor/core/routing}/router.ts +1 -1
  63. package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
  64. package/src/{lib → editor/core/store}/editorCore.ts +24 -8
  65. package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
  66. package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
  67. package/src/{lib → editor/core/store}/editorStore.ts +222 -28
  68. package/src/{lib → editor/core/store}/editorTypes.ts +56 -13
  69. package/src/editor/core/store/gradientSource.ts +192 -0
  70. package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
  71. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
  72. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
  73. package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
  74. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
  75. package/src/{lib → editor/core/themes}/migrations/index.ts +10 -0
  76. package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
  77. package/src/{lib → editor/core/themes}/slices/components.ts +20 -6
  78. package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
  79. package/src/{lib → editor/core/themes}/slices/gradients.ts +89 -14
  80. package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
  81. package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
  82. package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
  83. package/src/{lib → editor/core/themes}/themeInit.ts +8 -8
  84. package/src/{lib → editor/core/themes}/themeService.ts +6 -6
  85. package/src/{lib → editor/core/themes}/themeTypes.ts +67 -8
  86. package/src/editor/index.ts +69 -0
  87. package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -1
  88. package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +80 -129
  89. package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
  90. package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
  91. package/src/{pages → editor/pages}/Editor.svelte +4 -4
  92. package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
  93. package/src/{styles → editor/styles}/ui-editor.css +43 -22
  94. package/src/{styles → editor/styles}/ui-form-controls.css +23 -24
  95. package/src/{ui → editor/ui}/BezierCurveEditor.svelte +119 -68
  96. package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
  97. package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +7 -6
  98. package/src/editor/ui/FileLoadList.svelte +367 -0
  99. package/src/editor/ui/FilePill.svelte +80 -0
  100. package/src/editor/ui/FontStackEditor.svelte +499 -0
  101. package/src/editor/ui/GradientEditor.svelte +690 -0
  102. package/src/{ui → editor/ui}/GradientStopPicker.svelte +12 -4
  103. package/src/editor/ui/ManifestFileManager.svelte +438 -0
  104. package/src/{ui → editor/ui}/PaletteEditor.svelte +180 -673
  105. package/src/editor/ui/ProjectFontsSection.svelte +638 -0
  106. package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
  107. package/src/{ui → editor/ui}/TextTab.svelte +3 -3
  108. package/src/editor/ui/ThemeFileManager.svelte +783 -0
  109. package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
  110. package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -7
  111. package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +4 -1
  112. package/src/editor/ui/UIInfoPopover.svelte +243 -0
  113. package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
  114. package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
  115. package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
  116. package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
  117. package/src/{ui → editor/ui}/UIPaletteSelector.svelte +57 -30
  118. package/src/editor/ui/UIPillButton.svelte +168 -0
  119. package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
  120. package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
  121. package/src/editor/ui/UISegmentedControl.svelte +114 -0
  122. package/src/editor/ui/UISquareButton.svelte +172 -0
  123. package/src/{ui → editor/ui}/UITokenSelector.svelte +14 -11
  124. package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
  125. package/src/{ui → editor/ui}/VariablesTab.svelte +46 -17
  126. package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
  127. package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +24 -47
  128. package/src/{ui → editor/ui}/palette/PaletteBase.svelte +11 -8
  129. package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
  130. package/src/editor/ui/palette/paletteMath.ts +275 -0
  131. package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -18
  132. package/src/{ui → editor/ui}/sections/GradientsSection.svelte +8 -8
  133. package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +18 -18
  134. package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +23 -23
  135. package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
  136. package/src/{components → system/components}/Badge.svelte +0 -36
  137. package/src/{components → system/components}/Button.svelte +2 -2
  138. package/src/{components → system/components}/Card.svelte +34 -60
  139. package/src/{components → system/components}/CollapsibleSection.svelte +25 -2
  140. package/src/{components → system/components}/CornerBadge.svelte +8 -24
  141. package/src/{components → system/components}/Dialog.svelte +1 -1
  142. package/src/system/components/FloatingTokenTags.css +275 -0
  143. package/src/system/components/FloatingTokenTags.svelte +543 -0
  144. package/src/{components → system/components}/InlineEditActions.svelte +6 -4
  145. package/src/system/components/MenuSelect.svelte +229 -0
  146. package/src/{components → system/components}/Notification.svelte +8 -1
  147. package/src/{components → system/components}/ProgressBar.svelte +29 -11
  148. package/src/system/components/SectionDivider.svelte +560 -0
  149. package/src/{components → system/components}/SegmentedControl.svelte +49 -43
  150. package/src/{components → system/components}/TabBar.svelte +81 -65
  151. package/src/{components → system/components}/Table.svelte +17 -3
  152. package/src/{components → system/components}/Tooltip.svelte +6 -4
  153. package/src/system/styles/CONVENTIONS.md +178 -0
  154. package/src/system/styles/fonts.css +20 -0
  155. package/src/system/styles/tokens.css +601 -0
  156. package/src/system/styles/tokens.generated.css +544 -0
  157. package/src/component-editor/ImageEditor.svelte +0 -74
  158. package/src/component-editor/SectionDividerEditor.svelte +0 -265
  159. package/src/component-editor/scaffolding/DividerEditor.svelte +0 -94
  160. package/src/component-editor/scaffolding/GradientCard.svelte +0 -296
  161. package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
  162. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
  163. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
  164. package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
  165. package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
  166. package/src/components/SectionDivider.svelte +0 -483
  167. package/src/data/google-fonts.json +0 -75
  168. package/src/lib/index.ts +0 -68
  169. package/src/lib/presetService.ts +0 -214
  170. package/src/lib/productionPulse.ts +0 -32
  171. package/src/styles/fonts.css +0 -30
  172. package/src/styles/tokens.css +0 -1324
  173. package/src/ui/FontStackEditor.svelte +0 -361
  174. package/src/ui/GradientEditor.svelte +0 -470
  175. package/src/ui/PresetFileManager.svelte +0 -1116
  176. package/src/ui/ProjectFontsSection.svelte +0 -645
  177. package/src/ui/ThemeFileManager.svelte +0 -1020
  178. package/src/ui/UnsavedComponentsDialog.svelte +0 -315
  179. /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
  180. /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
  181. /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
  182. /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
  183. /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
  184. /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
  185. /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
  186. /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
  187. /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
  188. /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
  189. /package/src/{lib → editor/core/storage}/storage.ts +0 -0
  190. /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
  191. /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
  192. /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
  193. /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
  194. /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
  195. /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
  196. /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
  197. /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
  198. /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
  199. /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
  200. /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
  201. /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
  202. /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
  203. /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
  204. /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
  205. /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
  206. /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
  207. /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
  208. /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
  209. /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
  210. /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
  211. /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
  212. /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
  213. /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
  214. /package/src/{ui → editor/ui}/index.ts +0 -0
  215. /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
  216. /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
  217. /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
  218. /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
  219. /package/src/{ui → editor/ui}/variantScales.ts +0 -0
  220. /package/src/{assets → system/assets}/newspaper.webp +0 -0
  221. /package/src/{assets → system/assets}/offering.webp +0 -0
  222. /package/src/{components → system/components}/Callout.svelte +0 -0
  223. /package/src/{components → system/components}/Image.svelte +0 -0
  224. /package/src/{components → system/components}/RadioButton.svelte +0 -0
  225. /package/src/{components → system/components}/types.ts +0 -0
  226. /package/src/{styles → system/styles}/_padding.scss +0 -0
  227. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  228. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  229. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  230. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  231. /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  232. /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
@@ -0,0 +1,85 @@
1
+ <script lang="ts">
2
+ import newspaperBg from '../../../system/assets/newspaper.webp';
3
+
4
+
5
+
6
+ interface Props {
7
+ mode?: 'default' | 'image' | 'color';
8
+ /** CSS var name (set by ShadowBackdropControls) the backdrop reads when in color mode. */
9
+ colorVariable?: string;
10
+ /** Padding around the slotted preview content. Set to '0' when the slotted component should cover the full backdrop area (e.g. dialog overlay). */
11
+ padding?: string;
12
+ children?: import('svelte').Snippet;
13
+ /** Optional right-rail snippet rendered inside the backdrop as a fixed-width column. Used for canvas-scoped settings (e.g. Background controls). */
14
+ controls?: import('svelte').Snippet;
15
+ }
16
+
17
+ let {
18
+ mode = 'default',
19
+ colorVariable,
20
+ padding = '4rem',
21
+ children,
22
+ controls
23
+ }: Props = $props();
24
+
25
+ let backgroundStyle = $derived.by(() => {
26
+ if (mode === 'image') {
27
+ return `background-image: url(${newspaperBg}); background-size: cover; background-position: center; background-repeat: no-repeat;`;
28
+ }
29
+ if (mode === 'color' && colorVariable) {
30
+ return `background: var(${colorVariable}, #1a1a1a);`;
31
+ }
32
+ return `background: var(--ui-surface-lowest); border: 1px solid var(--ui-border-low);`;
33
+ });
34
+ </script>
35
+
36
+ <div class="shadow-backdrop" class:with-controls={!!controls} style={backgroundStyle}>
37
+ <div class="shadow-backdrop-content" style="padding: {padding} {padding} {padding} 1.5rem;">
38
+ {@render children?.()}
39
+ </div>
40
+ {#if controls}
41
+ <div class="shadow-backdrop-controls">
42
+ {@render controls?.()}
43
+ </div>
44
+ {/if}
45
+ </div>
46
+
47
+ <style>
48
+ .shadow-backdrop {
49
+ display: grid;
50
+ grid-template-columns: minmax(0, 1fr);
51
+ width: 100%;
52
+ min-width: 0;
53
+ min-height: 12rem;
54
+ border-radius: var(--ui-radius-md);
55
+ box-sizing: border-box;
56
+ overflow: hidden;
57
+ }
58
+
59
+ .shadow-backdrop.with-controls {
60
+ grid-template-columns: minmax(0, 1fr) auto;
61
+ grid-template-areas: "preview controls";
62
+ }
63
+
64
+ .shadow-backdrop-content {
65
+ display: grid;
66
+ align-items: center;
67
+ justify-items: start;
68
+ min-width: 0;
69
+ grid-area: preview;
70
+ }
71
+
72
+ .shadow-backdrop-controls {
73
+ padding: var(--ui-space-8);
74
+ grid-area: controls;
75
+ }
76
+
77
+ @container variant-group (max-width: 32rem) {
78
+ .shadow-backdrop.with-controls {
79
+ grid-template-columns: minmax(0, 1fr);
80
+ grid-template-areas:
81
+ "controls"
82
+ "preview";
83
+ }
84
+ }
85
+ </style>
@@ -0,0 +1,132 @@
1
+ <script lang="ts">
2
+ import { onMount } from 'svelte';
3
+ import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
4
+ import { setCssVar } from '../../core/cssVarSync';
5
+
6
+ type Mode = 'default' | 'image' | 'color';
7
+
8
+ interface Props {
9
+ mode?: Mode;
10
+ /** Editor-scoped CSS var the picker writes to (must end with `-surface` to allow gradients). */
11
+ colorVariable: string;
12
+ /** Which modes to expose. Defaults to all three. */
13
+ modes?: ReadonlyArray<Mode>;
14
+ }
15
+
16
+ let { mode = $bindable('default'), colorVariable, modes = ['default', 'image', 'color'] }: Props = $props();
17
+
18
+ const ALL_OPTIONS: ReadonlyArray<{ value: Mode; label: string }> = [
19
+ { value: 'default', label: 'Default' },
20
+ { value: 'image', label: 'Image' },
21
+ { value: 'color', label: 'Color' },
22
+ ];
23
+ let options = $derived(ALL_OPTIONS.filter((o) => modes.includes(o.value)));
24
+
25
+ onMount(() => {
26
+ if (!document.documentElement.style.getPropertyValue(colorVariable)) {
27
+ setCssVar(colorVariable, 'var(--surface-canvas)');
28
+ }
29
+ });
30
+ </script>
31
+
32
+ <div class="backdrop-grid" role="radiogroup">
33
+ {#each options as opt (opt.value)}
34
+ <div
35
+ class="backdrop-option"
36
+ class:checked={mode === opt.value}
37
+ role="radio"
38
+ aria-checked={mode === opt.value}
39
+ aria-label={opt.label}
40
+ tabindex={mode === opt.value ? 0 : -1}
41
+ onclick={() => (mode = opt.value)}
42
+ onkeydown={(e) => {
43
+ if (e.key === ' ' || e.key === 'Enter') {
44
+ e.preventDefault();
45
+ mode = opt.value;
46
+ }
47
+ }}
48
+ >
49
+ <span class="backdrop-dot" aria-hidden="true"></span>
50
+ {#if opt.value !== 'color'}
51
+ <span>{opt.label}</span>
52
+ {:else}
53
+ <div class="picker-slot">
54
+ <UIPaletteSelector variable={colorVariable} />
55
+ </div>
56
+ {/if}
57
+ </div>
58
+ {/each}
59
+ </div>
60
+
61
+ <style>
62
+ .backdrop-grid {
63
+ display: flex;
64
+ flex-direction: column;
65
+ gap: var(--ui-space-4);
66
+ }
67
+
68
+ /* Reserve room for the swatch's absolute-positioned token-name caption so it
69
+ doesn't bleed onto whatever sits below the controls. */
70
+ .backdrop-grid:has(.picker-slot) {
71
+ padding-bottom: var(--ui-space-16);
72
+ }
73
+
74
+ /* Each row keeps the same vertical rhythm; the trigger's height defines
75
+ the line, so the radio-only rows match it via min-height. The whole row
76
+ is clickable via the onclick handler — selecting on any pointer hit. */
77
+ .backdrop-option {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: var(--ui-space-6);
81
+ min-height: 1.75rem;
82
+ font-size: var(--ui-font-size-sm);
83
+ color: var(--ui-text-tertiary);
84
+ cursor: pointer;
85
+ user-select: none;
86
+ transition: color var(--ui-transition-fast);
87
+ }
88
+ .backdrop-option.checked {
89
+ color: var(--ui-text-primary);
90
+ }
91
+ .backdrop-option:focus-visible {
92
+ outline: 2px solid currentColor;
93
+ outline-offset: 2px;
94
+ }
95
+
96
+ /* Custom radio dot replaces native input so the entire row's onclick
97
+ handler runs without label/input dual-activation quirks. */
98
+ .backdrop-dot {
99
+ flex-shrink: 0;
100
+ width: 12px;
101
+ height: 12px;
102
+ border-radius: 50%;
103
+ border: 1.5px solid currentColor;
104
+ background: transparent;
105
+ opacity: 0.6;
106
+ transition: opacity var(--ui-transition-fast), background var(--ui-transition-fast);
107
+ }
108
+ .backdrop-option.checked .backdrop-dot {
109
+ opacity: 1;
110
+ background:
111
+ radial-gradient(circle, currentColor 0 35%, transparent 38%);
112
+ }
113
+
114
+ .picker-slot {
115
+ position: relative;
116
+ flex: 1;
117
+ min-width: 0;
118
+ }
119
+ .picker-slot :global(.ui-token-selector) {
120
+ width: 100%;
121
+ }
122
+ /* Hang the token name caption under the swatch trigger so it doesn't
123
+ stretch the row's height. */
124
+ .picker-slot :global(.ui-ts-meta-text) {
125
+ position: absolute;
126
+ top: 100%;
127
+ left: 0;
128
+ margin-top: 2px;
129
+ white-space: nowrap;
130
+ pointer-events: none;
131
+ }
132
+ </style>
@@ -0,0 +1,345 @@
1
+ <script lang="ts">
2
+ /**
3
+ * Shared inner block rendered inside a single state in VariantGroup.
4
+ *
5
+ * Both the tabs branch and the list branch of VariantGroup render the same
6
+ * `<TypeEditor>` (when a state has type groups) followed by `<TokenLayout>`,
7
+ * differing only in the surrounding chrome (preview placement, toggles,
8
+ * tab strip). This component owns the duplicated inner block so a per-state
9
+ * control change happens in exactly one place.
10
+ */
11
+ import type { Snippet } from 'svelte';
12
+ import TokenLayout from './TokenLayout.svelte';
13
+ import TypeEditor from './TypeEditor.svelte';
14
+ import type { Token, TypeGroupConfig } from './types';
15
+
16
+
17
+
18
+
19
+
20
+
21
+ interface ElementToggle {
22
+ checked: boolean;
23
+ label?: string;
24
+ onchange: (checked: boolean) => void;
25
+ }
26
+
27
+ interface Props {
28
+ /** Tokens for this state, fed to `<TokenLayout>`. */
29
+ tokens: Token[];
30
+ /** Type groups for this state; rendered as a row of `<TypeEditor>` blocks. */
31
+ typeGroups?: TypeGroupConfig[];
32
+ /** Forwarded to TypeEditor and TokenLayout so writes persist through the editor store. */
33
+ component?: string | undefined;
34
+ /** Per-variable rank passed through to TokenLayout for linked-block alignment. */
35
+ linkedOrder?: Map<string, number> | undefined;
36
+ /** Render the token grid with N visual columns. >1 spreads a long property
37
+ list horizontally; only meaningful for state-blocks without typeGroups
38
+ (the two-col flex layout already partitions screen real estate when
39
+ typeGroups are present). */
40
+ columns?: number;
41
+ /** Per-element Show toggle. When provided for an element in element-grouped
42
+ mode, renders a checkbox next to that section's heading. The token rows
43
+ below stay visible regardless — the toggle drives preview visibility,
44
+ not editor visibility. */
45
+ elementToggles?: Record<string, ElementToggle>;
46
+ /** Explicit element ordering. Defaults to first-encounter across tokens
47
+ then typeGroups, which works when structural elements (frame, container)
48
+ come before named typography elements. Pass an explicit order when the
49
+ natural first-encounter wouldn't produce the right reading order. */
50
+ elementOrder?: string[];
51
+ /** Element-keyed snippet rendered between the section heading and the
52
+ section's typography/tokens. Lets callers inject per-element controls
53
+ (e.g. a hairline position dropdown that conceptually belongs in the
54
+ hairline section but isn't a CSS token). */
55
+ elementExtras?: Snippet<[string]>;
56
+ onchange?: () => void;
57
+ }
58
+
59
+ let {
60
+ tokens,
61
+ typeGroups = [],
62
+ component = undefined,
63
+ linkedOrder = undefined,
64
+ columns = 1,
65
+ elementToggles = {},
66
+ elementOrder,
67
+ elementExtras,
68
+ onchange,
69
+ }: Props = $props();
70
+
71
+ let hasTypeGroups = $derived(typeGroups.length > 0);
72
+
73
+ /** Element-grouped mode: when 2+ distinct element tags appear across tokens
74
+ and type-groups, partition the panel into labeled subsections (e.g.
75
+ "Frame", "Header", "Body"). Element order = first-encounter across the
76
+ combined tokens + type-groups list. */
77
+ let elementSections = $derived.by(() => {
78
+ const seen = new Set<string>();
79
+ const order: string[] = [];
80
+ if (elementOrder) {
81
+ for (const el of elementOrder) {
82
+ if (!seen.has(el)) { seen.add(el); order.push(el); }
83
+ }
84
+ }
85
+ for (const t of tokens) {
86
+ if (t.element && !seen.has(t.element)) {
87
+ seen.add(t.element);
88
+ order.push(t.element);
89
+ }
90
+ }
91
+ for (const tg of typeGroups) {
92
+ if (tg.element && !seen.has(tg.element)) {
93
+ seen.add(tg.element);
94
+ order.push(tg.element);
95
+ }
96
+ }
97
+ if (order.length < 2) return null;
98
+ return order.map((el) => ({
99
+ element: el,
100
+ tokens: tokens.filter((t) => t.element === el),
101
+ typeGroups: typeGroups.filter((tg) => tg.element === el),
102
+ }));
103
+ });
104
+ </script>
105
+
106
+ {#if elementSections}
107
+ <div class="state-controls element-grouped">
108
+ {#each elementSections as section}
109
+ {@const toggle = elementToggles[section.element]}
110
+ <section class="element-section">
111
+ <div class="element-heading-row">
112
+ <h4 class="element-heading">{section.element}</h4>
113
+ {#if toggle}
114
+ <label class="element-show-toggle">
115
+ <input
116
+ type="checkbox"
117
+ checked={toggle.checked}
118
+ onchange={(e) => toggle.onchange((e.currentTarget as HTMLInputElement).checked)}
119
+ />
120
+ <span>{toggle.label ?? `Show ${section.element}`}</span>
121
+ </label>
122
+ {/if}
123
+ </div>
124
+ {@render elementExtras?.(section.element)}
125
+ {#if section.typeGroups.length > 0}
126
+ <div class="state-type-groups">
127
+ {#each section.typeGroups as tg}
128
+ <TypeEditor
129
+ legend={tg.legend ?? ''}
130
+ colorVariable={tg.colorVariable}
131
+ colorLabel={tg.colorLabel ?? 'text color'}
132
+ familyVariable={tg.familyVariable}
133
+ familyLabel={tg.familyLabel ?? 'font family'}
134
+ sizeVariable={tg.sizeVariable}
135
+ sizeLabel={tg.sizeLabel ?? 'font size'}
136
+ weightVariable={tg.weightVariable}
137
+ weightLabel={tg.weightLabel ?? 'font weight'}
138
+ lineHeightVariable={tg.lineHeightVariable}
139
+ lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
140
+ letterSpacingVariable={tg.letterSpacingVariable}
141
+ letterSpacingLabel={tg.letterSpacingLabel ?? 'letter spacing'}
142
+ outlineWidthVariable={tg.outlineWidthVariable}
143
+ outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
144
+ outlineColorVariable={tg.outlineColorVariable}
145
+ outlineColorLabel={tg.outlineColorLabel ?? 'outline color'}
146
+ {component}
147
+ {onchange}
148
+ />
149
+ {/each}
150
+ </div>
151
+ {/if}
152
+ {#if section.tokens.length > 0}
153
+ <TokenLayout
154
+ title=""
155
+ tokens={section.tokens}
156
+ {component}
157
+ {linkedOrder}
158
+ {columns}
159
+ {onchange}
160
+ />
161
+ {/if}
162
+ </section>
163
+ {/each}
164
+ </div>
165
+ {:else}
166
+ <div class="state-controls" class:two-col={hasTypeGroups}>
167
+ {#if hasTypeGroups}
168
+ <div class="state-type-groups">
169
+ {#each typeGroups as tg}
170
+ <TypeEditor
171
+ legend={tg.legend ?? 'type'}
172
+ colorVariable={tg.colorVariable}
173
+ colorLabel={tg.colorLabel ?? 'text color'}
174
+ familyVariable={tg.familyVariable}
175
+ familyLabel={tg.familyLabel ?? 'font family'}
176
+ sizeVariable={tg.sizeVariable}
177
+ sizeLabel={tg.sizeLabel ?? 'font size'}
178
+ weightVariable={tg.weightVariable}
179
+ weightLabel={tg.weightLabel ?? 'font weight'}
180
+ lineHeightVariable={tg.lineHeightVariable}
181
+ lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
182
+ letterSpacingVariable={tg.letterSpacingVariable}
183
+ letterSpacingLabel={tg.letterSpacingLabel ?? 'letter spacing'}
184
+ outlineWidthVariable={tg.outlineWidthVariable}
185
+ outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
186
+ outlineColorVariable={tg.outlineColorVariable}
187
+ outlineColorLabel={tg.outlineColorLabel ?? 'outline color'}
188
+ {component}
189
+ {onchange}
190
+ />
191
+ {/each}
192
+ </div>
193
+ {/if}
194
+ <TokenLayout
195
+ title=""
196
+ {tokens}
197
+ {component}
198
+ {linkedOrder}
199
+ {columns}
200
+ {onchange}
201
+ />
202
+ </div>
203
+ {/if}
204
+
205
+ <style>
206
+ .state-controls {
207
+ display: grid;
208
+ grid-template-columns: 1fr;
209
+ gap: var(--ui-space-12);
210
+ align-items: start;
211
+ margin-top: var(--ui-space-4);
212
+ }
213
+
214
+ .state-controls.two-col {
215
+ display: flex;
216
+ flex-wrap: wrap;
217
+ gap: var(--ui-space-16) var(--ui-space-16);
218
+ align-items: flex-start;
219
+ justify-content: flex-start;
220
+ }
221
+
222
+ .state-type-groups {
223
+ display: flex;
224
+ flex-direction: row;
225
+ flex-wrap: wrap;
226
+ gap: var(--ui-space-16);
227
+ align-items: flex-start;
228
+ }
229
+
230
+ /* Inside a state's two-col layout the fieldset frame is redundant with the
231
+ surrounding state card. Flatten the border/padding but keep the legend so
232
+ each block ("title", "body text", …) is identifiable. */
233
+ .state-controls.two-col .state-type-groups :global(.fieldset-wrapper) {
234
+ border: none;
235
+ padding: 0;
236
+ }
237
+
238
+ .state-controls.two-col .state-type-groups :global(.fieldset-wrapper.active) {
239
+ outline: none;
240
+ }
241
+
242
+ .state-controls.two-col .state-type-groups :global(.fieldset-legend) {
243
+ padding: 0 var(--ui-space-4) var(--ui-space-4);
244
+ }
245
+
246
+ /* The general-properties column has no legend of its own; pad it down by
247
+ one legend-line so its first row aligns with the first row of the
248
+ adjacent type-group. */
249
+ .state-controls.two-col > :global(.token-group) {
250
+ padding-top: calc(var(--ui-font-size-xs) + var(--ui-space-4));
251
+ }
252
+
253
+ /* Element-grouped mode: subsections fan out across available width, each
254
+ labeled by the element it targets (e.g. Frame / Header / Body). Three
255
+ columns at typical editor widths, dropping to two then one as the panel
256
+ narrows — the auto-fit + minmax does the responsive work without media
257
+ queries. `align-items: start` keeps columns of different heights aligned
258
+ to their top edge instead of stretching the shorter ones.
259
+ Within a section the two-col split (typography fieldsets + property grid)
260
+ still applies when the section has both. */
261
+ .state-controls.element-grouped {
262
+ display: grid;
263
+ grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
264
+ gap: var(--ui-space-20) var(--ui-space-32);
265
+ align-items: start;
266
+ }
267
+
268
+ /* Each element section stacks typography fieldset(s) above the property
269
+ grid in a single column, so both share the section's leftmost edge and
270
+ line up with neighbouring sections that have no typography (e.g. Frame). */
271
+ .element-section {
272
+ display: flex;
273
+ flex-direction: column;
274
+ gap: var(--ui-space-8);
275
+ align-items: stretch;
276
+ }
277
+
278
+ .element-section .state-type-groups {
279
+ display: flex;
280
+ flex-direction: row;
281
+ flex-wrap: wrap;
282
+ gap: var(--ui-space-16);
283
+ align-items: flex-start;
284
+ }
285
+
286
+ /* Type-fieldsets sit chrome-less against the element-section's own
287
+ boundary — the section heading already frames the block, and an extra
288
+ fieldset border would double-line the visual. */
289
+ .element-section .state-type-groups :global(.fieldset-wrapper) {
290
+ border: none;
291
+ padding: 0;
292
+ }
293
+ .element-section .state-type-groups :global(.fieldset-wrapper.active) {
294
+ outline: none;
295
+ }
296
+ .element-section .state-type-groups :global(.fieldset-legend) {
297
+ padding: 0 var(--ui-space-4) var(--ui-space-4);
298
+ }
299
+
300
+ /* TypeEditor's `.type-grid` and TokenLayout's `.token-grid` are independent
301
+ grids stacked inside the same element-section. Each sizes its label
302
+ column from its own `max-content`, so when one grid has long labels
303
+ ("letter spacing") and the other has short ones ("padding") their
304
+ dropdowns land at different x positions. Both grids honour
305
+ `--editor-label-col` for their first column track; setting it here pins
306
+ the label column to a shared minimum so short labels pad out and the
307
+ dropdowns line up across the section. The `max-content` upper bound
308
+ still lets exceptionally long labels grow.
309
+ Cascades into the inner grids; @container narrow-width overrides in
310
+ TokenLayout (which redefine `grid-template-columns` wholesale) still
311
+ win when the panel is too tight to honour the wider track. */
312
+ .element-section {
313
+ --editor-label-col: minmax(7.5rem, max-content);
314
+ }
315
+
316
+ .element-heading-row {
317
+ display: flex;
318
+ align-items: center;
319
+ gap: var(--ui-space-12);
320
+ padding-bottom: var(--ui-space-4);
321
+ border-bottom: 1px solid var(--ui-border-low);
322
+ }
323
+
324
+ .element-heading {
325
+ margin: 0;
326
+ font-size: var(--ui-font-size-sm);
327
+ font-weight: var(--ui-font-weight-medium);
328
+ color: var(--ui-text-tertiary);
329
+ }
330
+
331
+ /* Show toggle next to the section heading — drives preview visibility for
332
+ the element. Property rows below stay visible so users can still tune the
333
+ hidden element's tokens. */
334
+ .element-show-toggle {
335
+ display: inline-flex;
336
+ align-items: center;
337
+ gap: var(--ui-space-6);
338
+ font-size: var(--ui-font-size-sm);
339
+ color: var(--ui-text-secondary);
340
+ cursor: pointer;
341
+ user-select: none;
342
+ }
343
+ .element-show-toggle:hover { color: var(--ui-text-primary); }
344
+ .element-show-toggle input { margin: 0; cursor: pointer; }
345
+ </style>
@@ -13,7 +13,7 @@
13
13
  getComponentPropertySiblings,
14
14
  setComponentAliasLinked,
15
15
  clearComponentAliasLinked,
16
- } from '../../lib/editorStore';
16
+ } from '../../core/store/editorStore';
17
17
  import { getEditorContext } from './editorContext';
18
18
  import type { Token } from './types';
19
19
 
@@ -92,7 +92,7 @@
92
92
  { kind: 'shadow', matches: (v) => v.endsWith('-shadow') || v.startsWith('--shadow-') },
93
93
  { kind: 'padding', matches: (v) => v.endsWith('-padding') || v.endsWith('-margin') },
94
94
  { kind: 'gap', matches: (v) => v.endsWith('-gap') },
95
- { kind: 'border-width', matches: (v) => v.endsWith('-border-width') || v.startsWith('--border-width-') },
95
+ { kind: 'border-width', matches: (v) => v.endsWith('-border-width') || v.endsWith('-accent-width') || v.endsWith('-hairline-thickness') || v.startsWith('--border-width-') },
96
96
  { kind: 'border', matches: (v) => v.endsWith('-border') || v.startsWith('--border-') },
97
97
  { kind: 'surface', matches: (v) => v.endsWith('-surface') || v.startsWith('--surface-') },
98
98
  ];
@@ -139,9 +139,11 @@
139
139
  return sides.some((s) => !!document.documentElement.style.getPropertyValue(`${varName}-${s}`).trim());
140
140
  }
141
141
 
142
- function categorize(v: string, comp: string | undefined, state: typeof $editorState): Kind {
143
- const k = rawKind(v);
144
- if (k === 'padding' && paddingIsSplit(v, comp, state)) return 'padding-split';
142
+ function categorize(token: Token, comp: string | undefined, state: typeof $editorState): Kind {
143
+ const k = rawKind(token.variable);
144
+ if (k === 'padding' && token.splittable !== false && paddingIsSplit(token.variable, comp, state)) {
145
+ return 'padding-split';
146
+ }
145
147
  return k;
146
148
  }
147
149
 
@@ -170,7 +172,7 @@
170
172
  'divider-height': { component: UIVariantSelector, extra: () => ({ ...DIVIDER_HEIGHT }) },
171
173
  'dot-size': { component: UIVariantSelector, extra: () => ({ ...DOT_SIZE }) },
172
174
  'radius': { component: UIVariantSelector, extra: () => ({ ...RADIUS }) },
173
- 'padding': { component: UIPaddingSelector, extra: () => ({ mode: 'single' }) },
175
+ 'padding': { component: UIPaddingSelector, extra: (t) => ({ mode: 'single', splittable: t.splittable !== false }) },
174
176
  /* padding-split is NOT standalone: TokenLayout renders the .token-label
175
177
  (e.g. "padding") in col 1 and the wrapper provides the [label][trigger][value]
176
178
  subgrid. UIPaddingSelector's sides template fills cols 2-3 of row 1 with
@@ -217,7 +219,7 @@
217
219
  })();
218
220
 
219
221
  function buildEntries(list: Token[], order: Map<string, number> | undefined, linked: Set<Kind>, comp: string | undefined, state: typeof $editorState, multiCol: boolean): Entry[] {
220
- const indexed = list.map((token, i) => ({ e: { kind: categorize(token.variable, comp, state), token }, i }));
222
+ const indexed = list.map((token, i) => ({ e: { kind: categorize(token, comp, state), token }, i }));
221
223
  const rank = multiCol ? multiColRank : orderRank;
222
224
  indexed.sort((a, b) => {
223
225
  if (!multiCol) {
@@ -385,7 +387,10 @@
385
387
  --token-selector-w: 8rem;
386
388
  --columns: 1;
387
389
  display: grid;
388
- grid-template-columns: repeat(var(--columns), max-content var(--token-selector-w) 1fr);
390
+ /* Label column tracks `--editor-label-col` when an ancestor sets one
391
+ (e.g. StateBlock's `.element-section`), so this grid lines up with a
392
+ sibling `.type-grid` whose `max-content` would otherwise differ. */
393
+ grid-template-columns: repeat(var(--columns), var(--editor-label-col, max-content) var(--token-selector-w) 1fr);
389
394
  column-gap: var(--ui-space-10);
390
395
  row-gap: var(--ui-space-6);
391
396
  align-items: center;
@@ -412,13 +417,13 @@
412
417
  padding-left: var(--ui-space-20);
413
418
  }
414
419
 
415
- @container (max-width: 480px) {
420
+ @container variant-group (max-width: 480px) {
416
421
  .token-grid { --token-selector-w: 6rem; }
417
422
  }
418
423
 
419
424
  /* Narrow multi-col: shrink selector + inter-set gap further before giving
420
425
  up the second column. Targets the overlay's typical docked width range. */
421
- @container (max-width: 640px) {
426
+ @container variant-group (max-width: 640px) {
422
427
  .token-grid.multi-col {
423
428
  --token-selector-w: 6rem;
424
429
  column-gap: var(--ui-space-6);
@@ -434,7 +439,7 @@
434
439
  `1fr` so the lone column fills the panel like single-col mode, and the
435
440
  inter-set padding is suppressed so wrapped "set 2" rows don't sit
436
441
  indented. */
437
- @container (max-width: 520px) {
442
+ @container variant-group (max-width: 520px) {
438
443
  .token-grid.multi-col {
439
444
  --columns: 1;
440
445
  grid-template-columns: max-content var(--token-selector-w) 1fr;
@@ -447,7 +452,7 @@
447
452
  }
448
453
  }
449
454
 
450
- @container (max-width: 380px) {
455
+ @container variant-group (max-width: 380px) {
451
456
  .token-grid {
452
457
  grid-template-columns: max-content 1fr;
453
458
  column-gap: var(--ui-space-6);