@motion-proto/live-tokens 0.6.2 → 0.7.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 (212) hide show
  1. package/README.md +14 -13
  2. package/dist-plugin/index.cjs +147 -136
  3. package/dist-plugin/index.d.cts +1 -1
  4. package/dist-plugin/index.d.ts +1 -1
  5. package/dist-plugin/index.js +145 -135
  6. package/package.json +25 -40
  7. package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
  8. package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
  9. package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
  10. package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +3 -3
  11. package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
  12. package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
  13. package/src/editor/component-editor/ImageEditor.svelte +30 -0
  14. package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
  15. package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
  16. package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +64 -37
  17. package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
  18. package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
  19. package/src/{component-editor → editor/component-editor}/SectionDividerEditor.svelte +57 -84
  20. package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
  21. package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +16 -20
  22. package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
  23. package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
  24. package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
  25. package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
  26. package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +2 -2
  27. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
  28. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +144 -416
  29. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
  30. package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
  31. package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
  32. package/src/{component-editor → editor/component-editor}/scaffolding/DividerEditor.svelte +1 -1
  33. package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
  34. package/src/{component-editor → editor/component-editor}/scaffolding/GradientCard.svelte +6 -6
  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 -11
  37. package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
  38. package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
  39. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +72 -0
  40. package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
  41. package/src/editor/component-editor/scaffolding/StateBlock.svelte +257 -0
  42. package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +9 -7
  43. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +644 -0
  44. package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
  45. package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
  46. package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +14 -0
  47. package/src/{lib → editor/core/components}/componentConfigService.ts +2 -2
  48. package/src/{lib → editor/core/components}/componentPersist.ts +5 -5
  49. package/src/editor/core/flashStatus.ts +30 -0
  50. package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
  51. package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
  52. package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
  53. package/src/editor/core/manifests/manifestService.ts +116 -0
  54. package/src/{lib → editor/core/palettes}/paletteDerivation.ts +2 -2
  55. package/src/{lib → editor/core/palettes}/tokenRegistry.ts +5 -5
  56. package/src/editor/core/productionPulse.ts +37 -0
  57. package/src/{lib → editor/core/routing}/router.ts +1 -1
  58. package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
  59. package/src/{lib → editor/core/store}/editorCore.ts +24 -8
  60. package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
  61. package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
  62. package/src/{lib → editor/core/store}/editorStore.ts +17 -17
  63. package/src/{lib → editor/core/store}/editorTypes.ts +1 -1
  64. package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
  65. package/src/{lib → editor/core/themes}/slices/components.ts +2 -2
  66. package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
  67. package/src/{lib → editor/core/themes}/slices/gradients.ts +2 -2
  68. package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
  69. package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
  70. package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
  71. package/src/{lib → editor/core/themes}/themeInit.ts +6 -6
  72. package/src/{lib → editor/core/themes}/themeService.ts +6 -6
  73. package/src/{lib → editor/core/themes}/themeTypes.ts +11 -7
  74. package/src/editor/index.ts +69 -0
  75. package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +79 -125
  76. package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
  77. package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
  78. package/src/{pages → editor/pages}/Editor.svelte +4 -4
  79. package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
  80. package/src/{styles → editor/styles}/ui-editor.css +41 -21
  81. package/src/{styles → editor/styles}/ui-form-controls.css +8 -8
  82. package/src/{ui → editor/ui}/BezierCurveEditor.svelte +8 -8
  83. package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
  84. package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +8 -6
  85. package/src/editor/ui/FileLoadList.svelte +350 -0
  86. package/src/editor/ui/FilePill.svelte +80 -0
  87. package/src/{ui → editor/ui}/FontStackEditor.svelte +7 -7
  88. package/src/{ui → editor/ui}/GradientEditor.svelte +11 -11
  89. package/src/{ui → editor/ui}/GradientStopPicker.svelte +1 -1
  90. package/src/editor/ui/ManifestFileManager.svelte +371 -0
  91. package/src/{ui → editor/ui}/PaletteEditor.svelte +132 -598
  92. package/src/{ui → editor/ui}/ProjectFontsSection.svelte +102 -144
  93. package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
  94. package/src/{ui → editor/ui}/TextTab.svelte +3 -3
  95. package/src/{ui → editor/ui}/ThemeFileManager.svelte +286 -519
  96. package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
  97. package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -6
  98. package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +1 -1
  99. package/src/editor/ui/UIInfoPopover.svelte +244 -0
  100. package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
  101. package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
  102. package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
  103. package/src/{ui → editor/ui}/UIPaletteSelector.svelte +26 -26
  104. package/src/editor/ui/UIPillButton.svelte +138 -0
  105. package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
  106. package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
  107. package/src/editor/ui/UISquareButton.svelte +172 -0
  108. package/src/{ui → editor/ui}/UITokenSelector.svelte +10 -10
  109. package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
  110. package/src/{ui → editor/ui}/VariablesTab.svelte +31 -8
  111. package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
  112. package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +13 -13
  113. package/src/{ui → editor/ui}/palette/PaletteBase.svelte +8 -5
  114. package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
  115. package/src/editor/ui/palette/paletteMath.ts +275 -0
  116. package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -17
  117. package/src/{ui → editor/ui}/sections/GradientsSection.svelte +7 -7
  118. package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +17 -17
  119. package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +22 -22
  120. package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
  121. package/src/{components → system/components}/Badge.svelte +0 -36
  122. package/src/{components → system/components}/Card.svelte +8 -62
  123. package/src/{components → system/components}/CornerBadge.svelte +8 -24
  124. package/src/{components → system/components}/Dialog.svelte +1 -1
  125. package/src/system/components/FloatingTokenTags.css +256 -0
  126. package/src/system/components/FloatingTokenTags.svelte +592 -0
  127. package/src/{components → system/components}/InlineEditActions.svelte +6 -4
  128. package/src/system/components/MenuSelect.svelte +229 -0
  129. package/src/{components → system/components}/ProgressBar.svelte +29 -11
  130. package/src/{components → system/components}/SegmentedControl.svelte +49 -43
  131. package/src/{components → system/components}/TabBar.svelte +81 -65
  132. package/src/{components → system/components}/Table.svelte +17 -3
  133. package/src/{components → system/components}/Tooltip.svelte +6 -4
  134. package/src/system/styles/CONVENTIONS.md +178 -0
  135. package/src/{styles → system/styles}/fonts.css +6 -3
  136. package/src/{styles → system/styles}/tokens.css +149 -29
  137. package/src/component-editor/ImageEditor.svelte +0 -74
  138. package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
  139. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
  140. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
  141. package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
  142. package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
  143. package/src/data/google-fonts.json +0 -75
  144. package/src/lib/index.ts +0 -68
  145. package/src/lib/presetService.ts +0 -214
  146. package/src/lib/productionPulse.ts +0 -32
  147. package/src/ui/PresetFileManager.svelte +0 -1116
  148. package/src/ui/UnsavedComponentsDialog.svelte +0 -315
  149. /package/src/{styles → app}/site.css +0 -0
  150. /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
  151. /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
  152. /package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +0 -0
  153. /package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +0 -0
  154. /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
  155. /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
  156. /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
  157. /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
  158. /package/src/{lib → editor/core/components}/componentConfigKeys.ts +0 -0
  159. /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
  160. /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
  161. /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
  162. /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
  163. /package/src/{lib → editor/core/storage}/storage.ts +0 -0
  164. /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
  165. /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
  166. /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
  167. /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
  168. /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
  169. /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
  170. /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
  171. /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
  172. /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
  173. /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
  174. /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
  175. /package/src/{lib → editor/core/themes}/migrations/index.ts +0 -0
  176. /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
  177. /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
  178. /package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -0
  179. /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
  180. /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
  181. /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
  182. /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
  183. /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
  184. /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
  185. /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
  186. /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
  187. /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
  188. /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
  189. /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
  190. /package/src/{ui → editor/ui}/index.ts +0 -0
  191. /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
  192. /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
  193. /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
  194. /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
  195. /package/src/{ui → editor/ui}/variantScales.ts +0 -0
  196. /package/src/{assets → system/assets}/newspaper.webp +0 -0
  197. /package/src/{assets → system/assets}/offering.webp +0 -0
  198. /package/src/{components → system/components}/Button.svelte +0 -0
  199. /package/src/{components → system/components}/Callout.svelte +0 -0
  200. /package/src/{components → system/components}/CollapsibleSection.svelte +0 -0
  201. /package/src/{components → system/components}/Image.svelte +0 -0
  202. /package/src/{components → system/components}/Notification.svelte +0 -0
  203. /package/src/{components → system/components}/RadioButton.svelte +0 -0
  204. /package/src/{components → system/components}/SectionDivider.svelte +0 -0
  205. /package/src/{components → system/components}/types.ts +0 -0
  206. /package/src/{styles → system/styles}/_padding.scss +0 -0
  207. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  208. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  209. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  210. /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  211. /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  212. /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
@@ -1,7 +1,7 @@
1
1
  <script lang="ts">
2
2
  import { run } from 'svelte/legacy';
3
3
 
4
- import { sanitizeFileName } from '../../lib/themeService';
4
+ import { sanitizeFileName } from '../../core/themes/themeService';
5
5
  import UIDialog from '../../ui/UIDialog.svelte';
6
6
 
7
7
  interface Props {
@@ -11,16 +11,35 @@
11
11
  currentDisplayName?: string;
12
12
  /** Existing files used by the increment helper to find the next available `_NN` suffix.
13
13
  * Only `fileName` is read, so this accepts any shape with that field
14
- * (ComponentConfigMeta, PresetMeta, …). */
14
+ * (ComponentConfigMeta, ManifestMeta, …). */
15
15
  files?: { fileName: string }[];
16
16
  /** Dialog title — defaults to "Save As". Overridable so callers can use
17
- * context-specific framing (e.g. "Save Preset As"). */
17
+ * context-specific framing (e.g. "Save Manifest As"). */
18
18
  title?: string;
19
19
  /** Placeholder shown in the empty input. */
20
20
  placeholder?: string;
21
21
  /** Error message shown when the user types the reserved "default" name.
22
- * Default copy references components; presets should override. */
22
+ * Default copy references components; manifests should override. */
23
23
  reservedNameMessage?: string;
24
+ /** Optional one-line explanation rendered above the name input. Use when
25
+ * the dialog opens automatically (e.g. as a recovery prompt) so the user
26
+ * understands what's about to be saved and why the prompt appeared. */
27
+ description?: string;
28
+ /** Seed value to use when branching off the protected `default` file.
29
+ * Without this the dialog falls back to incrementing the default's display
30
+ * name (e.g. "Default Theme_01"), which reads as a derivative of the
31
+ * reserved name. Pass a fresh, neutral suggestion (e.g. "My Theme") so
32
+ * the user's first save isn't named after the slot they can't overwrite. */
33
+ branchFromDefaultName?: string;
34
+ /** Active file's basename. Authoritative source for "are we branching from
35
+ * the protected default?" — the displayName-based check below fails when
36
+ * the default's display name doesn't sanitize to "default" (e.g. themes
37
+ * whose default is named "Default Theme" → "default-theme"). */
38
+ currentFileName?: string;
39
+ /** Display names that already belong to protected/system files. Blocked
40
+ * case-insensitively so a user can't shadow the default by reusing its
41
+ * display label even when the sanitized filename would differ. */
42
+ reservedDisplayNames?: string[];
24
43
  onsave?: (payload: { displayName: string; fileName: string }) => void;
25
44
  }
26
45
 
@@ -31,6 +50,10 @@
31
50
  title = 'Save As',
32
51
  placeholder = 'Config name…',
33
52
  reservedNameMessage = 'The name "default" is reserved for the core component definition.',
53
+ description = '',
54
+ branchFromDefaultName = '',
55
+ currentFileName = '',
56
+ reservedDisplayNames = [],
34
57
  onsave,
35
58
  }: Props = $props();
36
59
 
@@ -79,10 +102,27 @@
79
102
  // ends up focused-and-selected, not the button.
80
103
  run(() => {
81
104
  if (show) {
82
- saveAsName =
83
- sanitizeFileName(currentDisplayName) === 'default'
84
- ? nextIncrementName(currentDisplayName).displayName
85
- : currentDisplayName;
105
+ const trimmedCurrent = currentDisplayName.trim().toLowerCase();
106
+ const isReservedDisplay = reservedDisplayNames.some(
107
+ (n) => n.trim().toLowerCase() === trimmedCurrent,
108
+ );
109
+ // Three paths funnel into the branch suggestion:
110
+ // 1. Active is the literal `default` file.
111
+ // 2. Current displayName itself sanitizes to "default".
112
+ // 3. Current displayName collides with a reserved label (e.g. a stray
113
+ // user file already named "Default Theme") — re-seeding with their
114
+ // old name would trip the validator on submit.
115
+ const isFromDefault =
116
+ currentFileName === 'default' ||
117
+ sanitizeFileName(currentDisplayName) === 'default' ||
118
+ isReservedDisplay;
119
+ if (isFromDefault) {
120
+ saveAsName = branchFromDefaultName
121
+ ? branchFromDefaultName
122
+ : nextIncrementName(currentDisplayName).displayName;
123
+ } else {
124
+ saveAsName = currentDisplayName;
125
+ }
86
126
  setTimeout(() => saveAsInput?.select(), 0);
87
127
  }
88
128
  });
@@ -92,6 +132,10 @@
92
132
  if (sanitizeFileName(trimmed) === 'default') {
93
133
  return reservedNameMessage;
94
134
  }
135
+ const lowered = trimmed.toLowerCase();
136
+ if (reservedDisplayNames.some((n) => n.trim().toLowerCase() === lowered)) {
137
+ return reservedNameMessage;
138
+ }
95
139
  return '';
96
140
  })());
97
141
  </script>
@@ -106,6 +150,9 @@
106
150
  width="360px"
107
151
  >
108
152
  <div class="save-as-dialog">
153
+ {#if description}
154
+ <p class="save-as-description">{description}</p>
155
+ {/if}
109
156
  <div class="save-as-row">
110
157
  <input
111
158
  class="save-as-input"
@@ -149,7 +196,7 @@
149
196
  min-width: 0;
150
197
  padding: var(--ui-space-8) var(--ui-space-10);
151
198
  background: var(--ui-surface-lowest);
152
- border: 1px solid var(--ui-border-subtle);
199
+ border: 1px solid var(--ui-border-low);
153
200
  border-radius: var(--ui-radius-md);
154
201
  color: var(--ui-text-primary);
155
202
  font-size: var(--ui-font-size-md);
@@ -163,7 +210,7 @@
163
210
  width: 2.25rem;
164
211
  padding: 0;
165
212
  background: var(--ui-surface-low);
166
- border: 1px solid var(--ui-border-subtle);
213
+ border: 1px solid var(--ui-border-low);
167
214
  border-radius: var(--ui-radius-md);
168
215
  color: var(--ui-text-secondary);
169
216
  font-size: var(--ui-font-size-md);
@@ -173,12 +220,12 @@
173
220
 
174
221
  .save-as-increment:hover {
175
222
  background: var(--ui-surface);
176
- border-color: var(--ui-border-default);
223
+ border-color: var(--ui-border);
177
224
  color: var(--ui-text-primary);
178
225
  }
179
226
 
180
227
  .save-as-input:focus {
181
- border-color: var(--ui-border-medium);
228
+ border-color: var(--ui-border-high);
182
229
  }
183
230
 
184
231
  .save-as-input.invalid,
@@ -195,4 +242,11 @@
195
242
  font-size: var(--ui-font-size-xs);
196
243
  color: var(--ui-highlight);
197
244
  }
245
+
246
+ .save-as-description {
247
+ margin: 0;
248
+ font-size: var(--ui-font-size-xs);
249
+ line-height: 1.5;
250
+ color: var(--ui-text-secondary);
251
+ }
198
252
  </style>
@@ -0,0 +1,72 @@
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};">
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
+ }
62
+
63
+ .shadow-backdrop-content {
64
+ display: grid;
65
+ place-items: center;
66
+ min-width: 0;
67
+ }
68
+
69
+ .shadow-backdrop-controls {
70
+ padding: var(--ui-space-8);
71
+ }
72
+ </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,257 @@
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 TokenLayout from './TokenLayout.svelte';
12
+ import TypeEditor from './TypeEditor.svelte';
13
+ import type { Token, TypeGroupConfig } from './types';
14
+
15
+
16
+
17
+
18
+
19
+
20
+ interface Props {
21
+ /** Tokens for this state, fed to `<TokenLayout>`. */
22
+ tokens: Token[];
23
+ /** Type groups for this state; rendered as a row of `<TypeEditor>` blocks. */
24
+ typeGroups?: TypeGroupConfig[];
25
+ /** Forwarded to TypeEditor and TokenLayout so writes persist through the editor store. */
26
+ component?: string | undefined;
27
+ /** Per-variable rank passed through to TokenLayout for linked-block alignment. */
28
+ linkedOrder?: Map<string, number> | undefined;
29
+ /** Render the token grid with N visual columns. >1 spreads a long property
30
+ list horizontally; only meaningful for state-blocks without typeGroups
31
+ (the two-col flex layout already partitions screen real estate when
32
+ typeGroups are present). */
33
+ columns?: number;
34
+ onchange?: () => void;
35
+ }
36
+
37
+ let {
38
+ tokens,
39
+ typeGroups = [],
40
+ component = undefined,
41
+ linkedOrder = undefined,
42
+ columns = 1,
43
+ onchange,
44
+ }: Props = $props();
45
+
46
+ let hasTypeGroups = $derived(typeGroups.length > 0);
47
+
48
+ /** Element-grouped mode: when 2+ distinct element tags appear across tokens
49
+ and type-groups, partition the panel into labeled subsections (e.g.
50
+ "Frame", "Header", "Body"). Element order = first-encounter across the
51
+ combined tokens + type-groups list. */
52
+ let elementSections = $derived.by(() => {
53
+ const order: string[] = [];
54
+ const seen = new Set<string>();
55
+ for (const t of tokens) {
56
+ if (t.element && !seen.has(t.element)) {
57
+ seen.add(t.element);
58
+ order.push(t.element);
59
+ }
60
+ }
61
+ for (const tg of typeGroups) {
62
+ if (tg.element && !seen.has(tg.element)) {
63
+ seen.add(tg.element);
64
+ order.push(tg.element);
65
+ }
66
+ }
67
+ if (order.length < 2) return null;
68
+ return order.map((el) => ({
69
+ element: el,
70
+ tokens: tokens.filter((t) => t.element === el),
71
+ typeGroups: typeGroups.filter((tg) => tg.element === el),
72
+ }));
73
+ });
74
+ </script>
75
+
76
+ {#if elementSections}
77
+ <div class="state-controls element-grouped">
78
+ {#each elementSections as section}
79
+ <section class="element-section">
80
+ <h4 class="element-heading">{section.element}</h4>
81
+ {#if section.typeGroups.length > 0}
82
+ <div class="state-type-groups">
83
+ {#each section.typeGroups as tg}
84
+ <TypeEditor
85
+ legend={tg.legend ?? 'type'}
86
+ colorVariable={tg.colorVariable}
87
+ colorLabel={tg.colorLabel ?? 'text color'}
88
+ familyVariable={tg.familyVariable}
89
+ familyLabel={tg.familyLabel ?? 'font family'}
90
+ sizeVariable={tg.sizeVariable}
91
+ sizeLabel={tg.sizeLabel ?? 'font size'}
92
+ weightVariable={tg.weightVariable}
93
+ weightLabel={tg.weightLabel ?? 'font weight'}
94
+ lineHeightVariable={tg.lineHeightVariable}
95
+ lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
96
+ outlineWidthVariable={tg.outlineWidthVariable}
97
+ outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
98
+ outlineColorVariable={tg.outlineColorVariable}
99
+ outlineColorLabel={tg.outlineColorLabel ?? 'outline color'}
100
+ {component}
101
+ {onchange}
102
+ />
103
+ {/each}
104
+ </div>
105
+ {/if}
106
+ {#if section.tokens.length > 0}
107
+ <TokenLayout
108
+ title=""
109
+ tokens={section.tokens}
110
+ {component}
111
+ {linkedOrder}
112
+ {columns}
113
+ {onchange}
114
+ />
115
+ {/if}
116
+ </section>
117
+ {/each}
118
+ </div>
119
+ {:else}
120
+ <div class="state-controls" class:two-col={hasTypeGroups}>
121
+ {#if hasTypeGroups}
122
+ <div class="state-type-groups">
123
+ {#each typeGroups as tg}
124
+ <TypeEditor
125
+ legend={tg.legend ?? 'type'}
126
+ colorVariable={tg.colorVariable}
127
+ colorLabel={tg.colorLabel ?? 'text color'}
128
+ familyVariable={tg.familyVariable}
129
+ familyLabel={tg.familyLabel ?? 'font family'}
130
+ sizeVariable={tg.sizeVariable}
131
+ sizeLabel={tg.sizeLabel ?? 'font size'}
132
+ weightVariable={tg.weightVariable}
133
+ weightLabel={tg.weightLabel ?? 'font weight'}
134
+ lineHeightVariable={tg.lineHeightVariable}
135
+ lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
136
+ outlineWidthVariable={tg.outlineWidthVariable}
137
+ outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
138
+ outlineColorVariable={tg.outlineColorVariable}
139
+ outlineColorLabel={tg.outlineColorLabel ?? 'outline color'}
140
+ {component}
141
+ {onchange}
142
+ />
143
+ {/each}
144
+ </div>
145
+ {/if}
146
+ <TokenLayout
147
+ title=""
148
+ {tokens}
149
+ {component}
150
+ {linkedOrder}
151
+ {columns}
152
+ {onchange}
153
+ />
154
+ </div>
155
+ {/if}
156
+
157
+ <style>
158
+ .state-controls {
159
+ display: grid;
160
+ grid-template-columns: 1fr;
161
+ gap: var(--ui-space-12);
162
+ align-items: start;
163
+ margin-top: var(--ui-space-4);
164
+ }
165
+
166
+ .state-controls.two-col {
167
+ display: flex;
168
+ flex-wrap: wrap;
169
+ gap: var(--ui-space-16) var(--ui-space-16);
170
+ align-items: flex-start;
171
+ justify-content: flex-start;
172
+ }
173
+
174
+ .state-type-groups {
175
+ display: flex;
176
+ flex-direction: row;
177
+ flex-wrap: wrap;
178
+ gap: var(--ui-space-16);
179
+ align-items: flex-start;
180
+ }
181
+
182
+ /* Inside a state's two-col layout the fieldset frame is redundant with the
183
+ surrounding state card. Flatten the border/padding but keep the legend so
184
+ each block ("title", "body text", …) is identifiable. */
185
+ .state-controls.two-col .state-type-groups :global(.fieldset-wrapper) {
186
+ border: none;
187
+ padding: 0;
188
+ }
189
+
190
+ .state-controls.two-col .state-type-groups :global(.fieldset-wrapper.active) {
191
+ outline: none;
192
+ }
193
+
194
+ .state-controls.two-col .state-type-groups :global(.fieldset-legend) {
195
+ padding: 0 var(--ui-space-4) var(--ui-space-4);
196
+ }
197
+
198
+ /* The general-properties column has no legend of its own; pad it down by
199
+ one legend-line so its first row aligns with the first row of the
200
+ adjacent type-group. */
201
+ .state-controls.two-col > :global(.token-group) {
202
+ padding-top: calc(var(--ui-font-size-xs) + var(--ui-space-4));
203
+ }
204
+
205
+ /* Element-grouped mode: vertical stack of subsections, each labeled by the
206
+ element it targets (e.g. Frame / Header / Body). Within a section the
207
+ two-col split (typography fieldsets + property grid) still applies when
208
+ the section has both. */
209
+ .state-controls.element-grouped {
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: var(--ui-space-16);
213
+ }
214
+
215
+ /* Each element section stacks typography fieldset(s) above the property
216
+ grid in a single column, so both share the section's leftmost edge and
217
+ line up with neighbouring sections that have no typography (e.g. Frame). */
218
+ .element-section {
219
+ display: flex;
220
+ flex-direction: column;
221
+ gap: var(--ui-space-8);
222
+ align-items: stretch;
223
+ }
224
+
225
+ .element-section .state-type-groups {
226
+ display: flex;
227
+ flex-direction: row;
228
+ flex-wrap: wrap;
229
+ gap: var(--ui-space-16);
230
+ align-items: flex-start;
231
+ }
232
+
233
+ /* Type-fieldsets sit chrome-less against the element-section's own
234
+ boundary — the section heading already frames the block, and an extra
235
+ fieldset border would double-line the visual. */
236
+ .element-section .state-type-groups :global(.fieldset-wrapper) {
237
+ border: none;
238
+ padding: 0;
239
+ }
240
+ .element-section .state-type-groups :global(.fieldset-wrapper.active) {
241
+ outline: none;
242
+ }
243
+ .element-section .state-type-groups :global(.fieldset-legend) {
244
+ padding: 0 var(--ui-space-4) var(--ui-space-4);
245
+ }
246
+
247
+ .element-heading {
248
+ margin: 0;
249
+ font-size: var(--ui-font-size-sm);
250
+ font-weight: var(--ui-font-weight-semibold);
251
+ text-transform: uppercase;
252
+ letter-spacing: 0.04em;
253
+ color: var(--ui-text-tertiary);
254
+ padding-bottom: var(--ui-space-4);
255
+ border-bottom: 1px solid var(--ui-border-low);
256
+ }
257
+ </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.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) {