@motion-proto/live-tokens 0.1.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (224) hide show
  1. package/README.md +168 -21
  2. package/dist-plugin/index.cjs +823 -336
  3. package/dist-plugin/index.d.cts +9 -7
  4. package/dist-plugin/index.d.ts +9 -7
  5. package/dist-plugin/index.js +822 -335
  6. package/package.json +46 -20
  7. package/src/assets/newspaper.webp +0 -0
  8. package/src/assets/offering.webp +0 -0
  9. package/src/component-editor/BadgeEditor.svelte +170 -0
  10. package/src/component-editor/CalloutEditor.svelte +103 -0
  11. package/src/component-editor/CardEditor.svelte +184 -0
  12. package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
  13. package/src/component-editor/CornerBadgeEditor.svelte +207 -0
  14. package/src/component-editor/DialogEditor.svelte +172 -0
  15. package/src/component-editor/ImageEditor.svelte +72 -0
  16. package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
  17. package/src/component-editor/NotificationEditor.svelte +160 -0
  18. package/src/component-editor/ProgressBarEditor.svelte +124 -0
  19. package/src/component-editor/RadioButtonEditor.svelte +140 -0
  20. package/src/component-editor/SectionDividerEditor.svelte +263 -0
  21. package/src/component-editor/SegmentedControlEditor.svelte +154 -0
  22. package/src/component-editor/StandardButtonsEditor.svelte +178 -0
  23. package/src/component-editor/TabBarEditor.svelte +137 -0
  24. package/src/component-editor/TableEditor.svelte +128 -0
  25. package/src/component-editor/TooltipEditor.svelte +122 -0
  26. package/src/component-editor/editorTokens.test.ts +93 -0
  27. package/src/component-editor/groupKeySlots.test.ts +67 -0
  28. package/src/component-editor/groupKeySnapshot.test.ts +52 -0
  29. package/src/component-editor/index.ts +5 -0
  30. package/src/component-editor/registry.ts +246 -0
  31. package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
  32. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
  33. package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
  34. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
  35. package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
  36. package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
  37. package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
  38. package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
  39. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
  40. package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
  41. package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
  42. package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
  43. package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
  44. package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
  45. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
  46. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
  47. package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
  48. package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
  49. package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
  50. package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
  51. package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
  52. package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
  53. package/src/component-editor/scaffolding/componentSources.ts +9 -0
  54. package/src/component-editor/scaffolding/defaultSections.ts +16 -0
  55. package/src/component-editor/scaffolding/editorContext.ts +44 -0
  56. package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
  57. package/src/component-editor/scaffolding/siblings.ts +33 -0
  58. package/src/component-editor/scaffolding/types.ts +39 -0
  59. package/src/components/Badge.svelte +231 -42
  60. package/src/components/Button.svelte +324 -124
  61. package/src/components/Callout.svelte +145 -0
  62. package/src/components/Card.svelte +123 -25
  63. package/src/components/CollapsibleSection.svelte +213 -35
  64. package/src/components/CornerBadge.svelte +224 -0
  65. package/src/components/Dialog.svelte +137 -114
  66. package/src/components/Image.svelte +43 -0
  67. package/src/components/InlineEditActions.svelte +74 -14
  68. package/src/components/Notification.svelte +184 -163
  69. package/src/components/ProgressBar.svelte +216 -22
  70. package/src/components/RadioButton.svelte +110 -40
  71. package/src/components/SectionDivider.svelte +428 -74
  72. package/src/components/SegmentedControl.svelte +203 -0
  73. package/src/components/TabBar.svelte +146 -21
  74. package/src/components/Table.svelte +102 -0
  75. package/src/components/Tooltip.svelte +45 -19
  76. package/src/components/types.ts +51 -0
  77. package/src/data/google-fonts.json +75 -0
  78. package/src/lib/ColumnsOverlay.svelte +20 -7
  79. package/src/lib/LiveEditorOverlay.svelte +257 -78
  80. package/src/lib/columnsOverlay.ts +21 -17
  81. package/src/lib/componentConfig.test.ts +204 -0
  82. package/src/lib/componentConfigKeys.ts +19 -0
  83. package/src/lib/componentConfigService.ts +88 -0
  84. package/src/lib/copyPopover.ts +30 -0
  85. package/src/lib/cssVarSync.ts +59 -7
  86. package/src/lib/editorConfigStore.ts +0 -10
  87. package/src/lib/editorCore.ts +402 -0
  88. package/src/lib/editorKeybindings.ts +52 -0
  89. package/src/lib/editorPersistence.ts +106 -0
  90. package/src/lib/editorRenderer.ts +74 -0
  91. package/src/lib/editorStore.test.ts +328 -0
  92. package/src/lib/editorStore.ts +412 -0
  93. package/src/lib/editorTypes.ts +100 -0
  94. package/src/lib/editorViewStore.ts +55 -0
  95. package/src/lib/files/versionedFileResource.ts +140 -0
  96. package/src/lib/fontLoader.ts +130 -0
  97. package/src/lib/fontMigration.ts +140 -0
  98. package/src/lib/fontParse.ts +168 -0
  99. package/src/lib/index.ts +48 -30
  100. package/src/lib/lazyConfig.test.ts +54 -0
  101. package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
  102. package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
  103. package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
  104. package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
  105. package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
  106. package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
  107. package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
  108. package/src/lib/migrations/index.ts +93 -0
  109. package/src/lib/migrations/migrations.test.ts +341 -0
  110. package/src/lib/navLinkTypes.ts +1 -0
  111. package/src/lib/overlayState.ts +3 -0
  112. package/src/lib/paletteDerivation.ts +300 -0
  113. package/src/lib/parentRouteStore.ts +42 -0
  114. package/src/lib/parsers/globalRootBlock.ts +32 -0
  115. package/src/lib/presetService.ts +94 -0
  116. package/src/lib/router.ts +42 -10
  117. package/src/lib/scrollSection.ts +45 -0
  118. package/src/lib/slices/columns.ts +59 -0
  119. package/src/lib/slices/components.ts +362 -0
  120. package/src/lib/slices/domainVars.ts +15 -0
  121. package/src/lib/slices/fonts.ts +30 -0
  122. package/src/lib/slices/gradients.ts +153 -0
  123. package/src/lib/slices/overlays.ts +132 -0
  124. package/src/lib/slices/palettes.ts +26 -0
  125. package/src/lib/slices/shadows.ts +123 -0
  126. package/src/lib/storage.ts +88 -0
  127. package/src/lib/themeInit.ts +74 -0
  128. package/src/lib/themeService.ts +101 -0
  129. package/src/lib/themeTypes.ts +146 -0
  130. package/src/lib/tokenRegistry.ts +148 -0
  131. package/src/pages/ComponentEditorPage.svelte +384 -0
  132. package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
  133. package/src/pages/Editor.svelte +98 -0
  134. package/src/pages/Editor.svelte.d.ts +2 -0
  135. package/src/pages/EditorShell.svelte +348 -0
  136. package/src/styles/_padding.scss +34 -0
  137. package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
  138. package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
  139. package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
  140. package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
  141. package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
  142. package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
  143. package/src/styles/fonts.css +22 -10
  144. package/src/styles/form-controls.css +14 -16
  145. package/src/styles/tokens.css +1322 -0
  146. package/src/styles/ui-editor.css +126 -0
  147. package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
  148. package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
  149. package/src/ui/EditorViewSwitcher.svelte +180 -0
  150. package/src/ui/FontStackEditor.svelte +360 -0
  151. package/src/ui/GradientEditor.svelte +461 -0
  152. package/src/ui/GradientStopPicker.svelte +74 -0
  153. package/src/ui/PaletteEditor.svelte +1590 -0
  154. package/src/ui/PaletteEditor.test.ts +108 -0
  155. package/src/ui/PresetFileManager.svelte +567 -0
  156. package/src/ui/ProjectFontsSection.svelte +645 -0
  157. package/src/{showcase → ui}/SurfacesTab.svelte +39 -39
  158. package/src/{showcase → ui}/TextTab.svelte +27 -27
  159. package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
  160. package/src/ui/Toggle.svelte +108 -0
  161. package/src/ui/UICopyPopover.svelte +78 -0
  162. package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
  163. package/src/ui/UIFontFamilySelector.svelte +309 -0
  164. package/src/ui/UIFontSizeSelector.svelte +165 -0
  165. package/src/ui/UIFontWeightSelector.svelte +52 -0
  166. package/src/ui/UILineHeightSelector.svelte +47 -0
  167. package/src/ui/UILinkToggle.svelte +60 -0
  168. package/src/ui/UIOptionItem.svelte +74 -0
  169. package/src/ui/UIOptionList.svelte +27 -0
  170. package/src/ui/UIPaddingSelector.svelte +661 -0
  171. package/src/ui/UIPaletteSelector.svelte +1084 -0
  172. package/src/ui/UIRadio.svelte +72 -0
  173. package/src/ui/UIRadioGroup.svelte +59 -0
  174. package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
  175. package/src/ui/UITokenSelector.svelte +509 -0
  176. package/src/ui/UIVariantSelector.svelte +145 -0
  177. package/src/ui/VariablesTab.svelte +252 -0
  178. package/src/ui/index.ts +31 -0
  179. package/src/ui/keepInViewport.ts +84 -0
  180. package/src/ui/palette/GradientStopEditor.svelte +482 -0
  181. package/src/ui/palette/OverridesPanel.svelte +526 -0
  182. package/src/ui/palette/PaletteBase.svelte +165 -0
  183. package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
  184. package/src/ui/palette/paletteEditorState.ts +89 -0
  185. package/src/ui/sections/ColumnsSection.svelte +273 -0
  186. package/src/ui/sections/GradientsSection.svelte +147 -0
  187. package/src/ui/sections/OverlaysSection.svelte +670 -0
  188. package/src/ui/sections/ShadowsSection.svelte +1250 -0
  189. package/src/ui/sections/TokenScaleTable.svelte +332 -0
  190. package/src/ui/sections/tokenScales.ts +81 -0
  191. package/src/ui/variantScales.ts +108 -0
  192. package/src/components/DetailNav.svelte +0 -78
  193. package/src/components/Toggle.svelte +0 -86
  194. package/src/lib/tokenInit.ts +0 -29
  195. package/src/lib/tokenService.ts +0 -144
  196. package/src/lib/tokenTypes.ts +0 -45
  197. package/src/pages/Admin.svelte +0 -100
  198. package/src/pages/ShowcasePage.svelte +0 -144
  199. package/src/showcase/BackupBrowser.svelte +0 -617
  200. package/src/showcase/ComponentsTab.svelte +0 -105
  201. package/src/showcase/PaletteEditor.svelte +0 -2579
  202. package/src/showcase/PaletteSelector.svelte +0 -627
  203. package/src/showcase/TokenMap.svelte +0 -54
  204. package/src/showcase/VariablesTab.svelte +0 -2655
  205. package/src/showcase/VisualsTab.svelte +0 -231
  206. package/src/showcase/demos/BadgeDemo.svelte +0 -56
  207. package/src/showcase/demos/CardDemo.svelte +0 -50
  208. package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
  209. package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
  210. package/src/showcase/demos/DialogDemo.svelte +0 -42
  211. package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
  212. package/src/showcase/demos/NotificationDemo.svelte +0 -147
  213. package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
  214. package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
  215. package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
  216. package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
  217. package/src/showcase/demos/TabBarDemo.svelte +0 -58
  218. package/src/showcase/demos/TooltipDemo.svelte +0 -52
  219. package/src/showcase/editor.css +0 -93
  220. package/src/showcase/index.ts +0 -17
  221. package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
  222. package/src/styles/fonts/Domine/OFL.txt +0 -97
  223. package/src/styles/fonts/Domine/README.txt +0 -66
  224. /package/src/{showcase → ui}/curveEngine.ts +0 -0
@@ -0,0 +1,78 @@
1
+ <script lang="ts">
2
+ import { copyPopover } from '../lib/copyPopover';
3
+
4
+ let bubbleEl: HTMLDivElement | null = null;
5
+ let bubbleW = 0;
6
+ let bubbleH = 0;
7
+
8
+ $: if (bubbleEl && $copyPopover.visible) {
9
+ const r = bubbleEl.getBoundingClientRect();
10
+ bubbleW = r.width;
11
+ bubbleH = r.height;
12
+ }
13
+
14
+ const GAP = 8;
15
+
16
+ $: anchor = $copyPopover.anchor;
17
+ $: rawLeft = anchor ? anchor.left + anchor.width / 2 - bubbleW / 2 : 0;
18
+ $: rawTop = anchor ? anchor.top - bubbleH - GAP : 0;
19
+ $: maxLeft = typeof window !== 'undefined' ? window.innerWidth - bubbleW - 4 : rawLeft;
20
+ $: clampedLeft = Math.max(4, Math.min(rawLeft, maxLeft));
21
+ $: clampedTop = Math.max(4, rawTop);
22
+ $: arrowLeft = anchor ? anchor.left + anchor.width / 2 - clampedLeft : bubbleW / 2;
23
+ </script>
24
+
25
+ {#if $copyPopover.visible && anchor}
26
+ <div
27
+ class="ui-copy-popover"
28
+ bind:this={bubbleEl}
29
+ style="top: {clampedTop}px; left: {clampedLeft}px;"
30
+ role="status"
31
+ aria-live="polite"
32
+ >
33
+ <span class="ui-copy-popover-value">{$copyPopover.value}</span>
34
+ <span class="ui-copy-popover-arrow" style="left: {arrowLeft}px;"></span>
35
+ </div>
36
+ {/if}
37
+
38
+ <style>
39
+ .ui-copy-popover {
40
+ position: fixed;
41
+ z-index: 10000;
42
+ display: inline-flex;
43
+ align-items: center;
44
+ gap: var(--ui-space-8);
45
+ padding: var(--ui-space-6) var(--ui-space-10);
46
+ background: var(--ui-surface-highest);
47
+ color: var(--ui-text-primary);
48
+ border: 1px solid var(--ui-border-medium);
49
+ border-radius: var(--ui-radius-lg);
50
+ box-shadow: var(--ui-shadow-lg);
51
+ font-family: var(--ui-font-mono);
52
+ font-size: var(--ui-font-size-xs);
53
+ line-height: 1;
54
+ pointer-events: none;
55
+ white-space: nowrap;
56
+ animation: uiCopyPopoverIn 120ms ease-out;
57
+ }
58
+
59
+ .ui-copy-popover-value {
60
+ color: var(--ui-text-primary);
61
+ }
62
+
63
+ .ui-copy-popover-arrow {
64
+ position: absolute;
65
+ bottom: -5px;
66
+ width: 8px;
67
+ height: 8px;
68
+ background: var(--ui-surface-highest);
69
+ border-right: 1px solid var(--ui-border-medium);
70
+ border-bottom: 1px solid var(--ui-border-medium);
71
+ transform: translateX(-50%) rotate(45deg);
72
+ }
73
+
74
+ @keyframes uiCopyPopoverIn {
75
+ from { opacity: 0; transform: translateY(2px); }
76
+ to { opacity: 1; transform: translateY(0); }
77
+ }
78
+ </style>
@@ -1,18 +1,24 @@
1
1
  <script lang="ts">
2
- import { tick } from 'svelte';
2
+ import { tick, createEventDispatcher } from 'svelte';
3
3
 
4
4
  export let show: boolean = false;
5
5
  export let title: string = '';
6
6
  export let cancelLabel: string = 'Cancel';
7
7
  export let showCancel: boolean = true;
8
+ export let confirmLabel: string = '';
9
+ export let confirmDisabled: boolean = false;
8
10
  export let width: string = '500px';
9
11
 
12
+ const dispatch = createEventDispatcher();
13
+
10
14
  let closeButtonRef: HTMLButtonElement;
11
15
  let cancelButtonRef: HTMLButtonElement;
16
+ let confirmButtonRef: HTMLButtonElement;
12
17
 
13
18
  $: if (show) {
14
19
  tick().then(() => {
15
- if (showCancel && cancelButtonRef) cancelButtonRef.focus();
20
+ if (confirmLabel && confirmButtonRef) confirmButtonRef.focus();
21
+ else if (showCancel && cancelButtonRef) cancelButtonRef.focus();
16
22
  else if (closeButtonRef) closeButtonRef.focus();
17
23
  });
18
24
  }
@@ -20,30 +26,46 @@
20
26
  function handleClose() {
21
27
  show = false;
22
28
  }
29
+
30
+ function handleConfirm() {
31
+ dispatch('confirm');
32
+ }
23
33
  </script>
24
34
 
25
35
  {#if show}
26
36
  <!-- svelte-ignore a11y-click-events-have-key-events a11y-no-noninteractive-element-interactions -->
27
- <div class="ed-backdrop" on:click|self={handleClose}>
28
- <div class="ed-dialog" style="width: {width}; max-width: {width};">
37
+ <div class="ui-dialog-backdrop" on:click|self={handleClose}>
38
+ <div class="ui-dialog" style="width: {width}; max-width: {width};">
29
39
  {#if title}
30
- <div class="ed-header">
31
- <h3 class="ed-title">{title}</h3>
32
- <button bind:this={closeButtonRef} class="ed-close" on:click={handleClose} aria-label="Close">
40
+ <div class="ui-dialog-header">
41
+ <h3 class="ui-dialog-title">{title}</h3>
42
+ <button bind:this={closeButtonRef} class="ui-dialog-close" on:click={handleClose} aria-label="Close">
33
43
  <i class="fas fa-times"></i>
34
44
  </button>
35
45
  </div>
36
46
  {/if}
37
47
 
38
- <div class="ed-body">
48
+ <div class="ui-dialog-body">
39
49
  <slot />
40
50
  </div>
41
51
 
42
- {#if showCancel}
43
- <div class="ed-footer">
44
- <button bind:this={cancelButtonRef} class="ed-btn" on:click={handleClose}>
45
- {cancelLabel}
46
- </button>
52
+ {#if showCancel || confirmLabel}
53
+ <div class="ui-dialog-footer">
54
+ {#if showCancel}
55
+ <button bind:this={cancelButtonRef} class="ui-dialog-btn" on:click={handleClose}>
56
+ {cancelLabel}
57
+ </button>
58
+ {/if}
59
+ {#if confirmLabel}
60
+ <button
61
+ bind:this={confirmButtonRef}
62
+ class="ui-dialog-btn primary"
63
+ on:click={handleConfirm}
64
+ disabled={confirmDisabled}
65
+ >
66
+ {confirmLabel}
67
+ </button>
68
+ {/if}
47
69
  </div>
48
70
  {/if}
49
71
  </div>
@@ -51,7 +73,7 @@
51
73
  {/if}
52
74
 
53
75
  <style>
54
- .ed-backdrop {
76
+ .ui-dialog-backdrop {
55
77
  position: fixed;
56
78
  top: 0; left: 0; width: 100%; height: 100%;
57
79
  background: rgba(0, 0, 0, 0.65);
@@ -61,20 +83,20 @@
61
83
  z-index: 9999;
62
84
  }
63
85
 
64
- .ed-dialog {
86
+ .ui-dialog {
65
87
  background: #1a1a1a;
66
88
  border: 1px solid #444;
67
89
  border-radius: 6px;
68
90
  box-shadow: 0 10px 40px rgba(0, 0, 0, 0.6);
69
- animation: edSlideIn 0.15s ease-out;
91
+ animation: uiDialogSlideIn 0.15s ease-out;
70
92
  }
71
93
 
72
- @keyframes edSlideIn {
94
+ @keyframes uiDialogSlideIn {
73
95
  from { opacity: 0; transform: translateY(-10px); }
74
96
  to { opacity: 1; transform: translateY(0); }
75
97
  }
76
98
 
77
- .ed-header {
99
+ .ui-dialog-header {
78
100
  padding: 10px 16px;
79
101
  border-bottom: 1px solid #333;
80
102
  display: flex;
@@ -82,14 +104,14 @@
82
104
  align-items: center;
83
105
  }
84
106
 
85
- .ed-title {
107
+ .ui-dialog-title {
86
108
  margin: 0;
87
109
  font-size: 16px;
88
110
  font-weight: 600;
89
111
  color: #e0e0e0;
90
112
  }
91
113
 
92
- .ed-close {
114
+ .ui-dialog-close {
93
115
  background: none;
94
116
  border: none;
95
117
  color: #888;
@@ -103,23 +125,24 @@
103
125
  border-radius: 4px;
104
126
  }
105
127
 
106
- .ed-close:hover {
128
+ .ui-dialog-close:hover {
107
129
  background: rgba(255, 255, 255, 0.08);
108
130
  color: #ccc;
109
131
  }
110
132
 
111
- .ed-body {
133
+ .ui-dialog-body {
112
134
  padding: 12px 16px;
113
135
  }
114
136
 
115
- .ed-footer {
137
+ .ui-dialog-footer {
116
138
  padding: 10px 16px;
117
139
  border-top: 1px solid #333;
118
140
  display: flex;
119
141
  justify-content: flex-end;
142
+ gap: 8px;
120
143
  }
121
144
 
122
- .ed-btn {
145
+ .ui-dialog-btn {
123
146
  padding: 6px 14px;
124
147
  background: transparent;
125
148
  border: 1px solid #555;
@@ -129,9 +152,27 @@
129
152
  cursor: pointer;
130
153
  }
131
154
 
132
- .ed-btn:hover {
155
+ .ui-dialog-btn:hover:not(:disabled) {
133
156
  background: rgba(255, 255, 255, 0.06);
134
157
  border-color: #777;
135
158
  color: #e0e0e0;
136
159
  }
160
+
161
+ .ui-dialog-btn.primary {
162
+ background: #e8e8e8;
163
+ border-color: #e8e8e8;
164
+ color: #1a1a1a;
165
+ font-weight: 600;
166
+ }
167
+
168
+ .ui-dialog-btn.primary:hover:not(:disabled) {
169
+ background: #ffffff;
170
+ border-color: #ffffff;
171
+ color: #1a1a1a;
172
+ }
173
+
174
+ .ui-dialog-btn:disabled {
175
+ opacity: 0.45;
176
+ cursor: not-allowed;
177
+ }
137
178
  </style>
@@ -0,0 +1,309 @@
1
+ <script lang="ts">
2
+ import { onMount, onDestroy, createEventDispatcher } from 'svelte';
3
+ import { resolveAliasChain } from '../lib/tokenRegistry';
4
+ import { editorState } from '../lib/editorStore';
5
+ import { CSS_VAR_CHANGE_EVENT } from '../lib/cssVarSync';
6
+ import type { FontFamily, FontSource } from '../lib/themeTypes';
7
+ import UITokenSelector from './UITokenSelector.svelte';
8
+ import UIOptionList from './UIOptionList.svelte';
9
+ import UIOptionItem from './UIOptionItem.svelte';
10
+
11
+ const dispatch = createEventDispatcher();
12
+
13
+ export let variable: string;
14
+ export let component: string | undefined = undefined;
15
+ export let canBeLinked: boolean = false;
16
+ export let disabled: boolean = false;
17
+ export let selectionsLocked: boolean = false;
18
+
19
+ const options = [
20
+ { key: 'display', label: 'Display' },
21
+ { key: 'sans', label: 'Sans' },
22
+ { key: 'serif', label: 'Serif' },
23
+ { key: 'mono', label: 'Mono' },
24
+ ];
25
+
26
+ let selector: UITokenSelector;
27
+ let chosenKey: string | null = null;
28
+ let chosenFamilyId: string | null = null;
29
+ let currentStack: string = '';
30
+ let familyNameByKey: Record<string, string> = {};
31
+
32
+ $: fontSources = $editorState.fonts.sources;
33
+ $: allFamilies = (fontSources as FontSource[]).flatMap((s) => s.families);
34
+ $: hasProjectFonts = allFamilies.length > 0;
35
+
36
+ function parseRef(value: string): string | null {
37
+ const m = value.match(/var\((--font-[a-z]+)\)/);
38
+ if (!m) return null;
39
+ const key = m[1].replace(/^--font-/, '');
40
+ return options.find((o) => o.key === key) ? key : null;
41
+ }
42
+
43
+ function firstFamilyName(stack: string): string {
44
+ if (!stack) return '';
45
+ const first = stack.split(',')[0].trim();
46
+ return first.replace(/^['"]|['"]$/g, '');
47
+ }
48
+
49
+ function findProjectFamilyByCssName(name: string): FontFamily | null {
50
+ if (!name) return null;
51
+ for (const source of fontSources) {
52
+ for (const fam of source.families) {
53
+ if (fam.cssName === name) return fam;
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+
59
+ function genericFor(v: string): string {
60
+ if (v === '--font-mono') return 'monospace';
61
+ if (v === '--font-serif') return 'serif';
62
+ return 'sans-serif';
63
+ }
64
+
65
+ function readResolved() {
66
+ const root = getComputedStyle(document.documentElement);
67
+ currentStack = root.getPropertyValue(variable).trim();
68
+ const next: Record<string, string> = {};
69
+ for (const opt of options) {
70
+ const stack = root.getPropertyValue(`--font-${opt.key}`).trim();
71
+ next[opt.key] = firstFamilyName(stack);
72
+ }
73
+ familyNameByKey = next;
74
+ }
75
+
76
+ function handleVarChange(e: Event) {
77
+ const detail = (e as CustomEvent<{ name: string }>).detail;
78
+ if (detail?.name?.startsWith('--font-')) readResolved();
79
+ }
80
+
81
+ function initFromCurrent() {
82
+ readResolved();
83
+ const raw = document.documentElement.style.getPropertyValue(variable).trim();
84
+ if (raw) {
85
+ const key = parseRef(raw);
86
+ if (key) {
87
+ chosenKey = key;
88
+ chosenFamilyId = null;
89
+ return;
90
+ }
91
+ const fam = findProjectFamilyByCssName(firstFamilyName(raw));
92
+ if (fam) {
93
+ chosenKey = null;
94
+ chosenFamilyId = fam.id;
95
+ return;
96
+ }
97
+ }
98
+ for (const alias of resolveAliasChain(variable)) {
99
+ const key = parseRef(`var(${alias})`);
100
+ if (key) {
101
+ chosenKey = key;
102
+ chosenFamilyId = null;
103
+ return;
104
+ }
105
+ }
106
+ chosenKey = null;
107
+ chosenFamilyId = null;
108
+ }
109
+
110
+ function handleReset() {
111
+ chosenKey = null;
112
+ chosenFamilyId = null;
113
+ readResolved();
114
+ dispatch('change');
115
+ }
116
+
117
+ function selectOption(key: string, close: () => void) {
118
+ const target = `--font-${key}`;
119
+ if (target === variable) {
120
+ selector.writeOverride(null);
121
+ chosenKey = null;
122
+ } else {
123
+ selector.writeOverride(target);
124
+ chosenKey = key;
125
+ }
126
+ chosenFamilyId = null;
127
+ readResolved();
128
+ close();
129
+ dispatch('change');
130
+ }
131
+
132
+ function selectProjectFamily(family: FontFamily, close: () => void) {
133
+ const stack = `'${family.cssName}', ${genericFor(variable)}`;
134
+ selector.writeOverride(stack);
135
+ chosenKey = null;
136
+ chosenFamilyId = family.id;
137
+ readResolved();
138
+ close();
139
+ dispatch('change');
140
+ }
141
+
142
+ // Re-derive `chosenKey` / `chosenFamilyId` when `variable` changes (the
143
+ // VariantGroup tabs view reuses the same selector instance across states).
144
+ let lastSeenVariable: string | null = null;
145
+ $: if (variable !== lastSeenVariable) {
146
+ lastSeenVariable = variable;
147
+ initFromCurrent();
148
+ }
149
+
150
+ onMount(() => {
151
+ document.addEventListener(CSS_VAR_CHANGE_EVENT, handleVarChange);
152
+ });
153
+ onDestroy(() => {
154
+ document.removeEventListener(CSS_VAR_CHANGE_EVENT, handleVarChange);
155
+ });
156
+
157
+ $: activeLabel = chosenFamilyId
158
+ ? (allFamilies.find((f) => f.id === chosenFamilyId)?.name ?? 'Project font')
159
+ : (options.find((o) => o.key === chosenKey)?.label ?? '');
160
+ $: displayFamily = firstFamilyName(currentStack);
161
+ </script>
162
+
163
+ <UITokenSelector
164
+ bind:this={selector}
165
+ {variable}
166
+ {component}
167
+ {canBeLinked}
168
+ {disabled}
169
+ {selectionsLocked}
170
+ dropdownMinWidth="14rem"
171
+ on:reset={handleReset}
172
+ on:var-change={initFromCurrent}
173
+ >
174
+ <svelte:fragment slot="trigger-title">{activeLabel}</svelte:fragment>
175
+ <svelte:fragment slot="trigger-meta">{displayFamily || '—'}</svelte:fragment>
176
+
177
+ <svelte:fragment let:close>
178
+ <UIOptionList>
179
+ {#each options as opt}
180
+ <UIOptionItem
181
+ active={chosenKey === opt.key}
182
+ on:click={() => selectOption(opt.key, close)}
183
+ >
184
+ <span slot="preview" class="font-sample" style="font-family: var(--font-{opt.key});">Aa</span>
185
+ <svelte:fragment slot="label">{opt.label}</svelte:fragment>
186
+ <svelte:fragment slot="meta">{familyNameByKey[opt.key] ?? ''}</svelte:fragment>
187
+ </UIOptionItem>
188
+ {/each}
189
+
190
+ {#if hasProjectFonts}
191
+ <div class="pfs-divider" role="separator"></div>
192
+ <div class="pfs-section">
193
+ <div class="pfs-trigger" class:active={chosenFamilyId !== null}>
194
+ <span class="pfs-label">Project Fonts</span>
195
+ <span class="pfs-count">{allFamilies.length}</span>
196
+ <i class="fas fa-chevron-right pfs-chevron" aria-hidden="true"></i>
197
+ </div>
198
+ <div class="pfs-submenu">
199
+ {#each fontSources as source (source.id)}
200
+ {#if source.families.length > 0}
201
+ <div class="pfs-source-label">{source.label ?? source.kind}</div>
202
+ {#each source.families as fam (fam.id)}
203
+ <UIOptionItem
204
+ active={chosenFamilyId === fam.id}
205
+ on:click={() => selectProjectFamily(fam, close)}
206
+ >
207
+ <span
208
+ slot="preview"
209
+ class="font-sample"
210
+ style="font-family: '{fam.cssName}', {genericFor(variable)};"
211
+ >Aa</span>
212
+ <svelte:fragment slot="label">{fam.name}</svelte:fragment>
213
+ </UIOptionItem>
214
+ {/each}
215
+ {/if}
216
+ {/each}
217
+ </div>
218
+ </div>
219
+ {/if}
220
+ </UIOptionList>
221
+ </svelte:fragment>
222
+ </UITokenSelector>
223
+
224
+ <style>
225
+ .font-sample {
226
+ display: inline-block;
227
+ width: 2.25rem;
228
+ margin-right: var(--ui-space-8);
229
+ text-align: center;
230
+ font-size: var(--ui-font-size-2xl);
231
+ color: var(--ui-text-primary);
232
+ line-height: 1;
233
+ }
234
+
235
+ .pfs-divider {
236
+ height: 1px;
237
+ background: var(--ui-border-faint);
238
+ margin: var(--ui-space-4) 0;
239
+ }
240
+
241
+ .pfs-section {
242
+ position: relative;
243
+ }
244
+
245
+ .pfs-trigger {
246
+ display: flex;
247
+ align-items: center;
248
+ gap: var(--ui-space-6);
249
+ padding: var(--ui-space-6) var(--ui-space-8);
250
+ border: 1px solid transparent;
251
+ border-radius: var(--ui-radius-sm);
252
+ color: var(--ui-text-primary);
253
+ font-size: var(--ui-font-size-sm);
254
+ cursor: default;
255
+ }
256
+
257
+ .pfs-section:hover .pfs-trigger,
258
+ .pfs-trigger.active {
259
+ background: var(--ui-hover);
260
+ }
261
+
262
+ .pfs-label {
263
+ flex: 1;
264
+ }
265
+
266
+ .pfs-count {
267
+ color: var(--ui-text-tertiary);
268
+ font-family: var(--ui-font-mono);
269
+ font-size: var(--ui-font-size-xs);
270
+ }
271
+
272
+ .pfs-chevron {
273
+ font-size: 0.625rem;
274
+ color: var(--ui-text-secondary);
275
+ width: 0.75rem;
276
+ }
277
+
278
+ .pfs-submenu {
279
+ display: none;
280
+ position: absolute;
281
+ left: 100%;
282
+ top: calc(-1 * var(--ui-space-4));
283
+ min-width: 14rem;
284
+ max-width: 18rem;
285
+ max-height: 70vh;
286
+ overflow-y: auto;
287
+ padding: var(--ui-space-4);
288
+ background: var(--ui-surface-higher);
289
+ border: 1px solid var(--ui-border-medium);
290
+ border-radius: var(--ui-radius-md);
291
+ box-shadow: var(--ui-shadow-lg);
292
+ z-index: 1;
293
+ }
294
+
295
+ .pfs-section:hover .pfs-submenu,
296
+ .pfs-section:focus-within .pfs-submenu {
297
+ display: flex;
298
+ flex-direction: column;
299
+ }
300
+
301
+ .pfs-source-label {
302
+ padding: var(--ui-space-4) var(--ui-space-8) var(--ui-space-2);
303
+ font-family: var(--ui-font-mono);
304
+ font-size: var(--ui-font-size-xs);
305
+ color: var(--ui-text-tertiary);
306
+ text-transform: uppercase;
307
+ letter-spacing: 0.04em;
308
+ }
309
+ </style>