@motion-proto/live-tokens 0.7.1 → 0.9.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 (96) hide show
  1. package/.claude/skills/live-tokens-add-component/SKILL.md +488 -0
  2. package/README.md +34 -0
  3. package/dist-plugin/index.cjs +707 -90
  4. package/dist-plugin/index.d.cts +1 -0
  5. package/dist-plugin/index.d.ts +1 -0
  6. package/dist-plugin/index.js +707 -90
  7. package/package.json +6 -2
  8. package/src/app/site.css +1 -1
  9. package/src/editor/component-editor/CollapsibleSectionEditor.svelte +34 -27
  10. package/src/editor/component-editor/DialogEditor.svelte +4 -4
  11. package/src/editor/component-editor/NotificationEditor.svelte +3 -1
  12. package/src/editor/component-editor/SectionDividerEditor.svelte +439 -112
  13. package/src/editor/component-editor/StandardButtonsEditor.svelte +13 -1
  14. package/src/editor/component-editor/editors.d.ts +10 -0
  15. package/src/editor/component-editor/index.ts +16 -1
  16. package/src/editor/component-editor/registry.ts +103 -26
  17. package/src/editor/component-editor/scaffolding/AngleDial.svelte +52 -13
  18. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -11
  19. package/src/editor/component-editor/scaffolding/ComponentsTab.svelte +2 -2
  20. package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +0 -1
  21. package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
  22. package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +15 -2
  23. package/src/editor/component-editor/scaffolding/StateBlock.svelte +103 -15
  24. package/src/editor/component-editor/scaffolding/TokenLayout.svelte +9 -6
  25. package/src/editor/component-editor/scaffolding/TypeEditor.svelte +13 -1
  26. package/src/editor/component-editor/scaffolding/VariantGroup.svelte +239 -25
  27. package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -0
  28. package/src/editor/component-editor/scaffolding/componentSources.ts +3 -3
  29. package/src/editor/component-editor/scaffolding/defaultSections.ts +15 -10
  30. package/src/editor/component-editor/scaffolding/types.ts +11 -0
  31. package/src/editor/core/components/componentConfigKeys.ts +22 -3
  32. package/src/editor/core/components/componentConfigService.ts +2 -2
  33. package/src/editor/core/components/componentPersist.ts +7 -5
  34. package/src/editor/core/manifests/manifestService.ts +58 -3
  35. package/src/editor/core/palettes/familySwap.ts +99 -0
  36. package/src/editor/core/palettes/paletteDerivation.ts +69 -0
  37. package/src/editor/core/palettes/tokenRegistry.ts +4 -1
  38. package/src/editor/core/store/editorStore.ts +206 -12
  39. package/src/editor/core/store/editorTypes.ts +55 -12
  40. package/src/editor/core/store/gradientSource.ts +192 -0
  41. package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
  42. package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
  43. package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
  44. package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
  45. package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
  46. package/src/editor/core/themes/migrations/index.ts +10 -0
  47. package/src/editor/core/themes/slices/components.ts +27 -4
  48. package/src/editor/core/themes/slices/gradients.ts +88 -13
  49. package/src/editor/core/themes/themeInit.ts +2 -2
  50. package/src/editor/core/themes/themeTypes.ts +56 -1
  51. package/src/editor/index.ts +10 -1
  52. package/src/editor/overlay/ColumnsOverlay.svelte +0 -1
  53. package/src/editor/overlay/LiveEditorOverlay.svelte +1 -4
  54. package/src/editor/pages/ComponentEditorPage.svelte +53 -3
  55. package/src/editor/pages/EditorShell.svelte +53 -3
  56. package/src/editor/styles/ui-editor.css +1 -0
  57. package/src/editor/styles/ui-form-controls.css +19 -20
  58. package/src/editor/ui/BezierCurveEditor.svelte +114 -63
  59. package/src/editor/ui/EditorViewSwitcher.svelte +0 -1
  60. package/src/editor/ui/FileLoadList.svelte +22 -5
  61. package/src/editor/ui/FontStackEditor.svelte +214 -76
  62. package/src/editor/ui/GradientEditor.svelte +435 -215
  63. package/src/editor/ui/GradientStopPicker.svelte +11 -3
  64. package/src/editor/ui/ManifestFileManager.svelte +71 -4
  65. package/src/editor/ui/PaletteEditor.svelte +52 -79
  66. package/src/editor/ui/ProjectFontsSection.svelte +328 -293
  67. package/src/editor/ui/ThemeFileManager.svelte +0 -4
  68. package/src/editor/ui/UIFontFamilySelector.svelte +0 -1
  69. package/src/editor/ui/UIFontSizeSelector.svelte +3 -0
  70. package/src/editor/ui/UIInfoPopover.svelte +0 -1
  71. package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
  72. package/src/editor/ui/UIPaletteSelector.svelte +31 -4
  73. package/src/editor/ui/UIPillButton.svelte +33 -3
  74. package/src/editor/ui/UISegmentedControl.svelte +114 -0
  75. package/src/editor/ui/UITokenSelector.svelte +4 -1
  76. package/src/editor/ui/VariablesTab.svelte +41 -35
  77. package/src/editor/ui/palette/OverridesPanel.svelte +14 -37
  78. package/src/editor/ui/palette/PaletteBase.svelte +3 -3
  79. package/src/editor/ui/sections/ColumnsSection.svelte +1 -2
  80. package/src/editor/ui/sections/GradientsSection.svelte +1 -1
  81. package/src/editor/ui/sections/OverlaysSection.svelte +1 -1
  82. package/src/editor/ui/sections/ShadowsSection.svelte +1 -1
  83. package/src/system/components/Button.svelte +2 -2
  84. package/src/system/components/Card.svelte +29 -1
  85. package/src/system/components/CollapsibleSection.svelte +25 -2
  86. package/src/system/components/Dialog.svelte +24 -4
  87. package/src/system/components/FloatingTokenTags.css +43 -24
  88. package/src/system/components/FloatingTokenTags.svelte +88 -137
  89. package/src/system/components/Notification.svelte +8 -1
  90. package/src/system/components/SectionDivider.svelte +532 -381
  91. package/src/system/styles/CONVENTIONS.md +1 -1
  92. package/src/system/styles/fonts.css +3 -16
  93. package/src/system/styles/tokens.css +356 -1199
  94. package/src/system/styles/tokens.generated.css +544 -0
  95. package/src/editor/component-editor/scaffolding/DividerEditor.svelte +0 -94
  96. package/src/editor/component-editor/scaffolding/GradientCard.svelte +0 -296
@@ -37,8 +37,20 @@
37
37
  return tokens;
38
38
  }
39
39
 
40
+ // Outline is the only variant that paints a surface tint on :active; the rest
41
+ // express press feedback through transform/shadow only. Expose just the one
42
+ // tunable property here rather than adding an active state to every variant.
43
+ const outlineActiveTokens: Token[] = [
44
+ { label: 'surface color', groupKey: 'surface', variable: '--button-outline-active-surface' },
45
+ ];
46
+
40
47
  function variantStates(v: Variant): Record<string, Token[]> {
41
- return Object.fromEntries(stateNames.map((s) => [s, variantStateTokens(v, s)]));
48
+ const out: Record<string, Token[]> = {};
49
+ out.default = variantStateTokens(v, 'default');
50
+ out.hover = variantStateTokens(v, 'hover');
51
+ if (v === 'outline') out.active = outlineActiveTokens;
52
+ out.disabled = variantStateTokens(v, 'disabled');
53
+ return out;
42
54
  }
43
55
  export const allTokens: Token[] = variants.flatMap((v) =>
44
56
  Object.values(variantStates(v)).flat(),
@@ -0,0 +1,10 @@
1
+ // Augment the global `*.svelte` ambient module to expose the named
2
+ // `allTokens` export that each `<Name>Editor.svelte` declares in its
3
+ // `<script module>` block. The default ambient (shipped via
4
+ // node_modules/svelte/types/index.d.ts) declares only a default export,
5
+ // so without this augmentation tsc fails on the named imports in
6
+ // registry.ts.
7
+
8
+ declare module '*.svelte' {
9
+ export const allTokens: import('./scaffolding/types').Token[];
10
+ }
@@ -1,5 +1,20 @@
1
1
  export { default as ComponentsTab } from './scaffolding/ComponentsTab.svelte';
2
2
  export type { ComponentSection } from './scaffolding/componentSectionType';
3
- export { defaultSections } from './scaffolding/defaultSections';
3
+ export { getDefaultSections } from './scaffolding/defaultSections';
4
4
 
5
+ // Editor primitives for consumer-authored components.
6
+ export { default as ComponentEditorBase } from './scaffolding/ComponentEditorBase.svelte';
7
+ export { default as VariantGroup } from './scaffolding/VariantGroup.svelte';
8
+ export { default as LinkedBlock } from './scaffolding/LinkedBlock.svelte';
9
+ export { default as TypeEditor } from './scaffolding/TypeEditor.svelte';
5
10
  export { default as TokenLayout } from './scaffolding/TokenLayout.svelte';
11
+
12
+ // Helpers for assembling a VariantGroup's siblings and the linked-block view.
13
+ export { buildSiblings } from './scaffolding/siblings';
14
+ export type { Sibling } from './scaffolding/siblings';
15
+ export { computeLinkedBlock, withLinkedDisabled } from './scaffolding/linkedBlock';
16
+ export type { LinkedToken, LinkedGroup, LinkedBlockResult } from './scaffolding/linkedBlock';
17
+ export { buildTypeGroupTokens } from './scaffolding/buildTypeGroupTokens';
18
+
19
+ // Token schema type — the shape of an entry in an editor's `allTokens` array.
20
+ export type { Token } from './scaffolding/types';
@@ -21,7 +21,8 @@ import TableEditor, { allTokens as tableTokens } from './TableEditor.svelte';
21
21
  import TabBarEditor, { allTokens as tabBarTokens } from './TabBarEditor.svelte';
22
22
  import TooltipEditor, { allTokens as tooltipTokens } from './TooltipEditor.svelte';
23
23
 
24
- export type ComponentId =
24
+ /** Internal narrowed union of the first-party component ids. Not exposed publicly. */
25
+ type BuiltInComponentId =
25
26
  | 'segmentedcontrol'
26
27
  | 'button'
27
28
  | 'notification'
@@ -41,6 +42,13 @@ export type ComponentId =
41
42
  | 'tooltip'
42
43
  | 'progressbar';
43
44
 
45
+ /**
46
+ * Public component id type. Widened to `string` because consumers can register
47
+ * their own components at runtime via `registerComponent()`. Internal code that
48
+ * needs to narrow to first-party ids can reference `BuiltInComponentId`.
49
+ */
50
+ export type ComponentId = string;
51
+
44
52
  export interface RegistryEntry {
45
53
  /** Canonical id — lowercase, matches the runtime component filename + server scan + `setComponentAlias` key. */
46
54
  id: ComponentId;
@@ -54,21 +62,18 @@ export interface RegistryEntry {
54
62
  editorComponent: Component<any, any, any>;
55
63
  /** Flat token list — the editor's declarative description of its token surface. */
56
64
  schema: Token[];
65
+ /** `'system'` for first-party entries; `'custom'` for entries added via `registerComponent()`. */
66
+ origin: 'system' | 'custom';
57
67
  }
58
68
 
59
69
  /**
60
- * Single source of truth for every component editor. Each entry binds the
61
- * canonical id to its label, icon, source file, editor component, and token
62
- * schema. Display order in the nav rail is sorted alphabetically by label
63
- * (see `componentRegistryEntries` below) — order in this object literal does
64
- * not affect the UI.
65
- *
66
- * Adding a component:
67
- * 1. Author `src/components/<Name>.svelte` (declares CSS vars in `:global(:root)`)
68
- * 2. Author `src/component-editor/<Name>Editor.svelte` (exports `allTokens` from a `<script context="module">` block)
70
+ * First-party registry. Frozen; runtime additions go in `customRegistry`.
71
+ * Adding a first-party component:
72
+ * 1. Author `src/system/components/<Name>.svelte` (declares CSS vars in `:global(:root)`)
73
+ * 2. Author `src/editor/component-editor/<Name>Editor.svelte` (exports `allTokens`)
69
74
  * 3. Add an entry below.
70
75
  */
71
- export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = Object.freeze({
76
+ const builtInRegistry: Readonly<Record<BuiltInComponentId, RegistryEntry>> = Object.freeze({
72
77
  segmentedcontrol: {
73
78
  id: 'segmentedcontrol',
74
79
  label: 'Segmented Control',
@@ -76,6 +81,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
76
81
  sourceFile: 'src/system/components/SegmentedControl.svelte',
77
82
  editorComponent: SegmentedControlEditor,
78
83
  schema: segmentedControlTokens,
84
+ origin: 'system',
79
85
  },
80
86
  button: {
81
87
  id: 'button',
@@ -84,6 +90,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
84
90
  sourceFile: 'src/system/components/Button.svelte',
85
91
  editorComponent: StandardButtonsEditor,
86
92
  schema: buttonTokens,
93
+ origin: 'system',
87
94
  },
88
95
  notification: {
89
96
  id: 'notification',
@@ -92,6 +99,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
92
99
  sourceFile: 'src/system/components/Notification.svelte',
93
100
  editorComponent: NotificationEditor,
94
101
  schema: notificationTokens,
102
+ origin: 'system',
95
103
  },
96
104
  dialog: {
97
105
  id: 'dialog',
@@ -100,6 +108,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
100
108
  sourceFile: 'src/system/components/Dialog.svelte',
101
109
  editorComponent: DialogEditor,
102
110
  schema: dialogTokens,
111
+ origin: 'system',
103
112
  },
104
113
  radiobutton: {
105
114
  id: 'radiobutton',
@@ -108,6 +117,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
108
117
  sourceFile: 'src/system/components/RadioButton.svelte',
109
118
  editorComponent: RadioButtonEditor,
110
119
  schema: radioButtonTokens,
120
+ origin: 'system',
111
121
  },
112
122
  card: {
113
123
  id: 'card',
@@ -116,6 +126,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
116
126
  sourceFile: 'src/system/components/Card.svelte',
117
127
  editorComponent: CardEditor,
118
128
  schema: cardTokens,
129
+ origin: 'system',
119
130
  },
120
131
  badge: {
121
132
  id: 'badge',
@@ -124,6 +135,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
124
135
  sourceFile: 'src/system/components/Badge.svelte',
125
136
  editorComponent: BadgeEditor,
126
137
  schema: badgeTokens,
138
+ origin: 'system',
127
139
  },
128
140
  callout: {
129
141
  id: 'callout',
@@ -132,6 +144,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
132
144
  sourceFile: 'src/system/components/Callout.svelte',
133
145
  editorComponent: CalloutEditor,
134
146
  schema: calloutTokens,
147
+ origin: 'system',
135
148
  },
136
149
  cornerbadge: {
137
150
  id: 'cornerbadge',
@@ -140,6 +153,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
140
153
  sourceFile: 'src/system/components/CornerBadge.svelte',
141
154
  editorComponent: CornerBadgeEditor,
142
155
  schema: cornerBadgeTokens,
156
+ origin: 'system',
143
157
  },
144
158
  image: {
145
159
  id: 'image',
@@ -148,6 +162,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
148
162
  sourceFile: 'src/system/components/Image.svelte',
149
163
  editorComponent: ImageEditor,
150
164
  schema: imageTokens,
165
+ origin: 'system',
151
166
  },
152
167
  inlineeditactions: {
153
168
  id: 'inlineeditactions',
@@ -156,6 +171,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
156
171
  sourceFile: 'src/system/components/InlineEditActions.svelte',
157
172
  editorComponent: InlineEditActionsEditor,
158
173
  schema: inlineEditActionsTokens,
174
+ origin: 'system',
159
175
  },
160
176
  menuselect: {
161
177
  id: 'menuselect',
@@ -164,6 +180,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
164
180
  sourceFile: 'src/system/components/MenuSelect.svelte',
165
181
  editorComponent: MenuSelectEditor,
166
182
  schema: menuSelectTokens,
183
+ origin: 'system',
167
184
  },
168
185
  sectiondivider: {
169
186
  id: 'sectiondivider',
@@ -172,6 +189,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
172
189
  sourceFile: 'src/system/components/SectionDivider.svelte',
173
190
  editorComponent: SectionDividerEditor,
174
191
  schema: sectionDividerTokens,
192
+ origin: 'system',
175
193
  },
176
194
  collapsiblesection: {
177
195
  id: 'collapsiblesection',
@@ -180,6 +198,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
180
198
  sourceFile: 'src/system/components/CollapsibleSection.svelte',
181
199
  editorComponent: CollapsibleSectionEditor,
182
200
  schema: collapsibleSectionTokens,
201
+ origin: 'system',
183
202
  },
184
203
  table: {
185
204
  id: 'table',
@@ -188,6 +207,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
188
207
  sourceFile: 'src/system/components/Table.svelte',
189
208
  editorComponent: TableEditor,
190
209
  schema: tableTokens,
210
+ origin: 'system',
191
211
  },
192
212
  tabbar: {
193
213
  id: 'tabbar',
@@ -196,6 +216,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
196
216
  sourceFile: 'src/system/components/TabBar.svelte',
197
217
  editorComponent: TabBarEditor,
198
218
  schema: tabBarTokens,
219
+ origin: 'system',
199
220
  },
200
221
  tooltip: {
201
222
  id: 'tooltip',
@@ -204,6 +225,7 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
204
225
  sourceFile: 'src/system/components/Tooltip.svelte',
205
226
  editorComponent: TooltipEditor,
206
227
  schema: tooltipTokens,
228
+ origin: 'system',
207
229
  },
208
230
  progressbar: {
209
231
  id: 'progressbar',
@@ -212,34 +234,89 @@ export const componentRegistry: Readonly<Record<ComponentId, RegistryEntry>> = O
212
234
  sourceFile: 'src/system/components/ProgressBar.svelte',
213
235
  editorComponent: ProgressBarEditor,
214
236
  schema: progressBarTokens,
237
+ origin: 'system',
215
238
  },
216
239
  });
217
240
 
218
- /** Display-ordered list of registry entries sorted alphabetically by label. Iteration order matches the nav rail. */
219
- export const componentRegistryEntries: ReadonlyArray<RegistryEntry> = Object.freeze(
220
- Object.values(componentRegistry).sort((a, b) => a.label.localeCompare(b.label)),
221
- );
241
+ /** Mutable map of consumer-registered components, populated by `registerComponent()`. */
242
+ const customRegistry = new Map<string, RegistryEntry>();
243
+
244
+ /** Argument shape for `registerComponent()`. `origin` is set internally to `'custom'`. */
245
+ export type RegisterComponentEntry = Omit<RegistryEntry, 'origin'>;
246
+
247
+ /**
248
+ * Register a consumer-authored component at runtime. Call from `main.ts`
249
+ * before app mount.
250
+ *
251
+ * Collision rule: if `entry.id` matches a built-in id, a warning is logged and
252
+ * the custom entry wins (the custom editor and schema replace the built-in for
253
+ * the rest of the session).
254
+ *
255
+ * Side effect: registers the schema with the editor store so reset-to-default
256
+ * and sibling-group resolution work for the new component.
257
+ */
258
+ export function registerComponent(entry: RegisterComponentEntry): void {
259
+ if (entry.id in builtInRegistry) {
260
+ console.warn(
261
+ `[registerComponent] custom component "${entry.id}" overrides a built-in. The custom editor will be used.`,
262
+ );
263
+ }
264
+ const stored: RegistryEntry = { ...entry, origin: 'custom' };
265
+ customRegistry.set(entry.id, stored);
266
+ registerComponentSchema(entry.id, entry.schema);
267
+ }
268
+
269
+ /**
270
+ * Merged registry: built-ins overlaid with customs (custom wins on id collision).
271
+ * Recomputed on each call so callers see runtime registrations made after their
272
+ * own module-load order.
273
+ */
274
+ export function getComponentRegistry(): Readonly<Record<string, RegistryEntry>> {
275
+ const merged: Record<string, RegistryEntry> = { ...builtInRegistry };
276
+ for (const [id, entry] of customRegistry) {
277
+ merged[id] = entry;
278
+ }
279
+ return merged;
280
+ }
222
281
 
223
- /** All canonical component ids, in display order. */
224
- export const componentIds: ReadonlyArray<ComponentId> = Object.freeze(
225
- componentRegistryEntries.map((e) => e.id),
226
- );
282
+ /**
283
+ * Display-ordered entries: system first (alphabetical by label), then custom
284
+ * (alphabetical by label). Iteration order matches the nav rail's grouping.
285
+ * The nav rail renders a divider between the two groups when customs exist.
286
+ */
287
+ export function getComponentRegistryEntries(): ReadonlyArray<RegistryEntry> {
288
+ const merged = getComponentRegistry();
289
+ const system: RegistryEntry[] = [];
290
+ const custom: RegistryEntry[] = [];
291
+ for (const entry of Object.values(merged)) {
292
+ (entry.origin === 'system' ? system : custom).push(entry);
293
+ }
294
+ system.sort((a, b) => a.label.localeCompare(b.label));
295
+ custom.sort((a, b) => a.label.localeCompare(b.label));
296
+ return [...system, ...custom];
297
+ }
298
+
299
+ /** All component ids, in display order. */
300
+ export function getComponentIds(): ReadonlyArray<string> {
301
+ return getComponentRegistryEntries().map((e) => e.id);
302
+ }
227
303
 
228
- // Eager schema registration. Replaces the side-effect-on-import pattern that
229
- // each editor module previously used (top-of-script `registerComponentSchema(...)`).
230
- // Runs once at module load, before any editor instance mounts.
231
- for (const entry of componentRegistryEntries) {
304
+ // Eager schema registration for built-ins. Customs register lazily inside
305
+ // `registerComponent()` so the store knows about every component before any
306
+ // editor instance mounts.
307
+ for (const entry of Object.values(builtInRegistry)) {
232
308
  registerComponentSchema(entry.id, entry.schema);
233
309
  }
234
310
 
235
311
  /**
236
- * Validate that the server's filesystem scan matches the registry's id list.
312
+ * Validate that the server's filesystem scan matches the merged registry's id list.
237
313
  * Logs a warning when ids drift. Called at boot from the editor page.
238
314
  */
239
315
  export function validateRegistryAgainstServerScan(serverIds: ReadonlyArray<string>): void {
240
- const registrySet = new Set<string>(componentIds);
316
+ const ids = getComponentIds();
317
+ const registrySet = new Set<string>(ids);
241
318
  const serverSet = new Set<string>(serverIds);
242
- const missingOnServer = componentIds.filter((id) => !serverSet.has(id));
319
+ const missingOnServer = ids.filter((id) => !serverSet.has(id));
243
320
  const extraOnServer = serverIds.filter((id) => !registrySet.has(id));
244
321
  if (missingOnServer.length > 0) {
245
322
  console.warn(
@@ -9,10 +9,14 @@
9
9
  value?: number;
10
10
  label?: string;
11
11
  size?: number;
12
+ /** 'horizontal' (default) lays the label, dial, input, and degree mark on a
13
+ * single line. 'vertical' stacks the dial above the input — used when the
14
+ * dial sits in its own column with a section header providing the label. */
15
+ orientation?: 'horizontal' | 'vertical';
12
16
  onchange?: (payload: { value: number }) => void;
13
17
  }
14
18
 
15
- let { value = $bindable(0), label = 'Angle', size = 44, onchange }: Props = $props();
19
+ let { value = $bindable(0), label = 'Angle', size = 44, orientation = 'horizontal', onchange }: Props = $props();
16
20
 
17
21
  let dialEl: HTMLDivElement | undefined = $state();
18
22
  let dragging = $state(false);
@@ -63,8 +67,8 @@
63
67
  let indicatorTransform = $derived(`rotate(${value}deg)`);
64
68
  </script>
65
69
 
66
- <div class="angle-dial-row">
67
- {#if label}
70
+ <div class="angle-dial-row" class:vertical={orientation === 'vertical'}>
71
+ {#if label && orientation === 'horizontal'}
68
72
  <span class="dial-label">{label}:</span>
69
73
  {/if}
70
74
  <div
@@ -90,16 +94,18 @@
90
94
  <div class="indicator" style="transform: {indicatorTransform}"></div>
91
95
  <div class="hub"></div>
92
96
  </div>
93
- <input
94
- class="num"
95
- type="number"
96
- min="0"
97
- max="360"
98
- step="1"
99
- value={value}
100
- onchange={onInputChange}
101
- />
102
- <span class="suffix">°</span>
97
+ <div class="num-row" style={orientation === 'vertical' ? `width: ${size}px;` : ''}>
98
+ <input
99
+ class="num"
100
+ type="number"
101
+ min="0"
102
+ max="360"
103
+ step="1"
104
+ value={value}
105
+ onchange={onInputChange}
106
+ />
107
+ <span class="suffix">°</span>
108
+ </div>
103
109
  </div>
104
110
 
105
111
  <style>
@@ -110,6 +116,39 @@
110
116
  font-size: var(--ui-font-size-sm);
111
117
  color: var(--ui-text-secondary);
112
118
  }
119
+ /* Stacked layout: dial centered, input + degree mark on the row below. Used
120
+ when the dial sits in its own grid column with an external section label. */
121
+ .angle-dial-row.vertical {
122
+ flex-direction: column;
123
+ align-items: center;
124
+ gap: var(--ui-space-6);
125
+ }
126
+
127
+ .num-row {
128
+ display: inline-flex;
129
+ align-items: center;
130
+ gap: var(--ui-space-8);
131
+ }
132
+
133
+ /* Vertical mode: lock the row to the dial's width and overlay the degree
134
+ mark inside the input's right padding so the input fills the column
135
+ instead of the row growing past the dial. */
136
+ .angle-dial-row.vertical .num-row {
137
+ position: relative;
138
+ display: block;
139
+ }
140
+ .angle-dial-row.vertical .num {
141
+ width: 100%;
142
+ box-sizing: border-box;
143
+ padding-right: 1.25rem;
144
+ }
145
+ .angle-dial-row.vertical .suffix {
146
+ position: absolute;
147
+ right: var(--ui-space-6);
148
+ top: 50%;
149
+ transform: translateY(-50%);
150
+ pointer-events: none;
151
+ }
113
152
 
114
153
  .dial-label {
115
154
  user-select: none;
@@ -6,7 +6,7 @@
6
6
  import { onMount, onDestroy } from 'svelte';
7
7
  import UIInfoPopover from '../../ui/UIInfoPopover.svelte';
8
8
  import { get } from 'svelte/store';
9
- import type { ComponentConfig, ComponentConfigMeta } from '../../core/themes/themeTypes';
9
+ import type { AliasDiskValue, ComponentConfig, ComponentConfigMeta } from '../../core/themes/themeTypes';
10
10
  import { componentSourceFile } from './componentSources';
11
11
  import {
12
12
  loadComponentConfig,
@@ -125,15 +125,17 @@
125
125
  }
126
126
  }
127
127
 
128
- function refToString(ref: CssVarRef): string {
129
- return ref.kind === 'token' ? ref.name : ref.value;
128
+ function refToDiskValue(ref: CssVarRef): AliasDiskValue {
129
+ if (ref.kind === 'token') return ref.name;
130
+ if (ref.kind === 'literal') return ref.value;
131
+ return { kind: 'gradient', value: ref.value };
130
132
  }
131
133
 
132
- function currentAliases(): Record<string, string> {
134
+ function currentAliases(): Record<string, AliasDiskValue> {
133
135
  const slice = get(editorState).components[component];
134
136
  if (!slice) return {};
135
- const out: Record<string, string> = {};
136
- for (const [k, ref] of Object.entries(slice.aliases)) out[k] = refToString(ref);
137
+ const out: Record<string, AliasDiskValue> = {};
138
+ for (const [k, ref] of Object.entries(slice.aliases)) out[k] = refToDiskValue(ref);
137
139
  return out;
138
140
  }
139
141
 
@@ -342,7 +344,7 @@
342
344
  : isApplied
343
345
  ? 'Active config is applied to production'
344
346
  : ''}
345
- style="flex: 0 0 11.25rem; width: 11.25rem;"
347
+ style="flex: 0 1 11.25rem; min-width: 0; max-width: 11.25rem;"
346
348
  />
347
349
  <div class="cfm-actions">
348
350
  <ComponentFileMenu
@@ -383,7 +385,7 @@
383
385
  name={productionInfo?.name ?? '—'}
384
386
  isProtected={productionInfo?.fileName === 'default'}
385
387
  protectedTitle="Protected system config"
386
- style="flex: 0 0 11.25rem; width: 11.25rem;"
388
+ style="flex: 0 1 11.25rem; min-width: 0; max-width: 11.25rem;"
387
389
  />
388
390
  <div class="cfm-actions">
389
391
  <UISquareButton
@@ -483,7 +485,6 @@
483
485
  font-size: var(--ui-font-size-3xl);
484
486
  font-weight: var(--ui-font-weight-semibold);
485
487
  color: var(--ui-text-primary);
486
- letter-spacing: -0.015em;
487
488
  line-height: 1.1;
488
489
  }
489
490
 
@@ -536,7 +537,6 @@
536
537
  font-size: var(--ui-font-size-xs);
537
538
  font-weight: var(--ui-font-weight-semibold);
538
539
  text-transform: uppercase;
539
- letter-spacing: 0.08em;
540
540
  color: var(--ui-text-secondary);
541
541
  line-height: 1.1;
542
542
  }
@@ -546,7 +546,6 @@
546
546
  align-items: center;
547
547
  gap: var(--ui-space-4);
548
548
  font-size: 0.75rem;
549
- letter-spacing: 0.02em;
550
549
  color: var(--ui-text-muted);
551
550
  line-height: 1;
552
551
  }
@@ -1,13 +1,13 @@
1
1
  <script lang="ts">
2
2
  import type { ComponentSection } from './componentSectionType';
3
- import { defaultSections } from './defaultSections';
3
+ import { getDefaultSections } from './defaultSections';
4
4
 
5
5
  interface Props {
6
6
  sections?: ComponentSection[];
7
7
  selectedComponent?: string;
8
8
  }
9
9
 
10
- let { sections = defaultSections, selectedComponent = sections[0]?.id ?? '' }: Props = $props();
10
+ let { sections = getDefaultSections(), selectedComponent = sections[0]?.id ?? '' }: Props = $props();
11
11
  </script>
12
12
 
13
13
  <div class="components-container">
@@ -311,7 +311,6 @@
311
311
  font-size: var(--ui-font-size-md);
312
312
  font-weight: var(--ui-font-weight-medium);
313
313
  color: var(--ui-text-primary);
314
- letter-spacing: 0.005em;
315
314
  }
316
315
 
317
316
  /* Control row hosts a single TokenLayout-rendered selector + resolved value.