@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,42 @@
1
+ import { writable, type Readable } from 'svelte/store';
2
+ import { route } from './router';
3
+
4
+ // Parent-window route, viewable from inside the editor iframe. Mirrors the
5
+ // host page's route via postMessage from LiveEditorOverlay. When not in an
6
+ // iframe, falls through to the local route store.
7
+
8
+ const MSG_TYPE = 'lt:parent-route';
9
+
10
+ function buildStore(): Readable<string> {
11
+ if (typeof window === 'undefined') return route;
12
+ if (window.parent === window) return route;
13
+
14
+ const inner = writable<string>('/');
15
+
16
+ // Best-effort initial read (same-origin in dev).
17
+ try {
18
+ inner.set(window.parent.location.pathname || '/');
19
+ } catch {
20
+ // cross-origin or sandboxed — wait for the first postMessage instead
21
+ }
22
+
23
+ window.addEventListener('message', (e: MessageEvent) => {
24
+ const data = e.data;
25
+ if (data && typeof data === 'object' && data.type === MSG_TYPE && typeof data.path === 'string') {
26
+ inner.set(data.path);
27
+ }
28
+ });
29
+
30
+ return inner;
31
+ }
32
+
33
+ export const parentRoute: Readable<string> = buildStore();
34
+
35
+ export function postParentRoute(target: Window | null | undefined, path: string): void {
36
+ if (!target) return;
37
+ try {
38
+ target.postMessage({ type: MSG_TYPE, path }, '*');
39
+ } catch {
40
+ // ignore — iframe may not be ready yet
41
+ }
42
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Shared parser for `:global(:root) { ... }` declaration blocks in Svelte
3
+ * component sources.
4
+ *
5
+ * Both the browser-side `tokenRegistry` (Vite `?raw` import-time scrape) and
6
+ * the Node-side `themeFileApi` plugin (filesystem read on dev/HMR) need to
7
+ * recover Layer-2 component-token declarations from `.svelte` files. They had
8
+ * been carrying separate copies of the same regex; this is the canonical
9
+ * implementation that both targets import.
10
+ *
11
+ * Assumes no nested braces inside the block — Layer-2 token blocks are flat
12
+ * declaration lists (`--name: value;`), not nested rulesets. If a future token
13
+ * block ever needs `@media`/`@supports` nesting, this regex changes once here.
14
+ *
15
+ * Pure (no DOM, no fs) so the tsup ESM+CJS build of the vite plugin can import
16
+ * it safely.
17
+ *
18
+ * Consumer-facing implication: components must keep their `:global(:root)`
19
+ * blocks as flat literal declarations — no `@each` / SCSS loops — otherwise
20
+ * this parser sees zero tokens and the editor's alias picker / file-manager
21
+ * UI is empty for that component. See `src/components/Notification.svelte:86–92`
22
+ * for the documented reason a four-variant block stays expanded.
23
+ */
24
+ export function extractGlobalRootBody(source: string): string {
25
+ const re = /:global\(:root\)\s*\{([^}]*)\}/g;
26
+ const bodies: string[] = [];
27
+ let m: RegExpExecArray | null;
28
+ while ((m = re.exec(source)) !== null) {
29
+ bodies.push(m[1]);
30
+ }
31
+ return bodies.join('\n');
32
+ }
@@ -0,0 +1,94 @@
1
+ import type { Preset, PresetMeta, Theme, ComponentConfig } from './themeTypes';
2
+ import { versionedFileResource } from './files/versionedFileResource';
3
+ import { listComponents } from './componentConfigService';
4
+ import { getActiveTheme } from './themeService';
5
+
6
+ /**
7
+ * REST client for preset (bundle) manifest files. Each preset file references
8
+ * a theme file basename + a per-component config file basename. Loading a
9
+ * preset flips the corresponding `_active.json` pointers via `applyPreset`,
10
+ * leaving the underlying theme + component-config files as the source of
11
+ * truth.
12
+ *
13
+ * Mirrors the lifecycle of `themeService.ts` and `componentConfigService.ts`
14
+ * but adds one custom route — `PUT /api/presets/:name/apply` — that the
15
+ * server uses to atomically validate every reference and flip every pointer
16
+ * in one shot.
17
+ */
18
+
19
+ const presetsResource = versionedFileResource<Preset, PresetMeta, never>({
20
+ baseUrl: '/api/presets',
21
+ });
22
+
23
+ export const listPresets = async (): Promise<PresetMeta[]> => {
24
+ const data = await presetsResource.list();
25
+ return data.files;
26
+ };
27
+
28
+ export const loadPreset = (fileName: string): Promise<Preset> =>
29
+ presetsResource.load(fileName);
30
+ export const savePreset = (fileName: string, data: Preset): Promise<void> =>
31
+ presetsResource.save(fileName, data);
32
+ export const deletePreset = (fileName: string): Promise<void> =>
33
+ presetsResource.remove(fileName);
34
+ export const getActivePreset = (): Promise<Preset | null> => presetsResource.getActive();
35
+ export const setActivePreset = (fileName: string): Promise<void> =>
36
+ presetsResource.setActive(fileName);
37
+
38
+ export interface ApplyPresetResult {
39
+ ok: boolean;
40
+ preset: Preset;
41
+ theme: Theme;
42
+ componentConfigs: Record<string, ComponentConfig>;
43
+ }
44
+
45
+ /**
46
+ * Server-side atomic apply: validate every referenced file exists, flip the
47
+ * theme + each component's `_active.json` pointer, and return the resolved
48
+ * theme + component configs in one payload.
49
+ *
50
+ * The client typically follows this with a full page reload — loading a
51
+ * preset is a "blow up the world" action and preserving editor session
52
+ * state across that boundary is low value.
53
+ */
54
+ export async function applyPreset(fileName: string): Promise<ApplyPresetResult> {
55
+ const res = await fetch(`/api/presets/${encodeURIComponent(fileName)}/apply`, {
56
+ method: 'PUT',
57
+ });
58
+ if (!res.ok) {
59
+ const err = await res.json().catch(() => ({ error: 'Apply failed' }));
60
+ throw new Error(err.error || 'Apply failed');
61
+ }
62
+ return res.json();
63
+ }
64
+
65
+ /**
66
+ * Build a manifest from the *currently active files on disk* (not in-memory
67
+ * editor state) and persist it. Dirty editor state isn't a file yet, so it's
68
+ * not part of any preset until the user saves it. Callers should warn the
69
+ * user via the UI if `$dirty` is true before invoking this.
70
+ */
71
+ export async function captureCurrentAsPreset(
72
+ fileName: string,
73
+ displayName: string,
74
+ ): Promise<void> {
75
+ const activeTheme = await getActiveTheme();
76
+ if (!activeTheme || !activeTheme._fileName) {
77
+ throw new Error('No active theme on disk to capture');
78
+ }
79
+ const components = await listComponents();
80
+ const componentConfigs: Record<string, string> = {};
81
+ for (const c of components) {
82
+ componentConfigs[c.name] = c.activeFile || 'default';
83
+ }
84
+ const now = new Date().toISOString();
85
+ const manifest: Preset = {
86
+ name: displayName,
87
+ createdAt: now,
88
+ updatedAt: now,
89
+ theme: activeTheme._fileName,
90
+ componentConfigs,
91
+ };
92
+ await savePreset(fileName, manifest);
93
+ await setActivePreset(fileName);
94
+ }
package/src/lib/router.ts CHANGED
@@ -1,17 +1,49 @@
1
1
  import { writable } from 'svelte/store';
2
+ import { storageKey } from './editorConfig';
2
3
 
3
- export const route = writable(window.location.pathname || '/');
4
+ function prevKey(): string {
5
+ return storageKey('prev-route');
6
+ }
7
+
8
+ function rememberPrev(current: string) {
9
+ if (current === '/editor') return;
10
+ try { sessionStorage.setItem(prevKey(), current); } catch { /* ignore */ }
11
+ }
12
+
13
+ export const route = writable<string>('/');
14
+
15
+ let initialised = false;
4
16
 
17
+ /**
18
+ * Idempotent host hook — call once during boot to seed the route store from
19
+ * the current location and wire popstate handling. Module import no longer
20
+ * touches `window`, so SSR / test harnesses can import without crashing.
21
+ */
22
+ export function init(): void {
23
+ if (initialised) return;
24
+ initialised = true;
25
+ if (typeof window === 'undefined') return;
26
+ const initial = window.location.pathname || '/';
27
+ rememberPrev(initial);
28
+ route.set(initial);
29
+ window.addEventListener('popstate', () => {
30
+ route.set(window.location.pathname || '/');
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Push a new history entry and update the route store. Produces exactly one
36
+ * `route` store update per call — the popstate listener installed in `init()`
37
+ * fires only on browser back/forward, not on synthetic dispatch.
38
+ */
5
39
  export function navigate(path: string) {
6
40
  const [pathname] = path.split('#');
7
- history.pushState(null, '', path);
8
- route.set(pathname);
9
- window.dispatchEvent(new PopStateEvent('popstate'));
10
- if (!path.includes('#')) {
11
- window.scrollTo(0, 0);
41
+ if (typeof window !== 'undefined') {
42
+ rememberPrev(window.location.pathname || '/');
43
+ history.pushState(null, '', path);
44
+ if (!path.includes('#')) {
45
+ window.scrollTo(0, 0);
46
+ }
12
47
  }
48
+ route.set(pathname);
13
49
  }
14
-
15
- window.addEventListener('popstate', () => {
16
- route.set(window.location.pathname || '/');
17
- });
@@ -0,0 +1,45 @@
1
+ const PRELUDE_PX = 128;
2
+ const DURATION_MS = 400;
3
+ const SPEED_PX_PER_MS = PRELUDE_PX / DURATION_MS;
4
+
5
+ function easeOutCubic(t: number): number {
6
+ return 1 - Math.pow(1 - t, 3);
7
+ }
8
+
9
+ function findScrollParent(el: HTMLElement): HTMLElement {
10
+ let node: HTMLElement | null = el.parentElement;
11
+ while (node) {
12
+ const style = getComputedStyle(node);
13
+ if (/(auto|scroll|overlay)/.test(style.overflowY) && node.scrollHeight > node.clientHeight) {
14
+ return node;
15
+ }
16
+ node = node.parentElement;
17
+ }
18
+ return document.scrollingElement as HTMLElement ?? document.documentElement;
19
+ }
20
+
21
+ export function scrollSectionIntoView(target: HTMLElement, scroller?: HTMLElement) {
22
+ const scrollEl = scroller ?? findScrollParent(target);
23
+ const max = scrollEl.scrollHeight - scrollEl.clientHeight;
24
+ const targetTop = Math.max(0, Math.min(max, target.offsetTop));
25
+ const start = scrollEl.scrollTop;
26
+ const delta = targetTop - start;
27
+ if (delta === 0) return;
28
+
29
+ const direction = delta > 0 ? 1 : -1;
30
+ const distance = Math.abs(delta);
31
+ const animatedDistance = Math.min(distance, PRELUDE_PX);
32
+ const duration = animatedDistance / SPEED_PX_PER_MS;
33
+
34
+ const animFrom = targetTop - animatedDistance * direction;
35
+ scrollEl.scrollTop = animFrom;
36
+
37
+ const t0 = performance.now();
38
+ function step(now: number) {
39
+ const t = Math.min(1, (now - t0) / duration);
40
+ scrollEl.scrollTop = animFrom + animatedDistance * direction * easeOutCubic(t);
41
+ if (t < 1) requestAnimationFrame(step);
42
+ else scrollEl.scrollTop = targetTop;
43
+ }
44
+ requestAnimationFrame(step);
45
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Columns slice — fixed-shape (count/maxWidth/gutter/margin) state, derived to
3
+ * four CSS vars. Values default to a 12-column 1440px grid; while state matches
4
+ * the default we leave tokens.css in charge so the `clamp()` in
5
+ * `--columns-gutter` survives until the editor overrides it.
6
+ */
7
+ import type { ColumnsState } from '../editorTypes';
8
+
9
+ export const DEFAULT_COLUMNS: ColumnsState = { count: 12, maxWidth: 1440, gutter: 16, margin: 0 };
10
+
11
+ export const COLUMN_VAR_NAMES = ['--columns-count', '--columns-max-width', '--columns-gutter', '--columns-margin'] as const;
12
+
13
+ export function columnsToVars(c: ColumnsState): Record<string, string> {
14
+ return {
15
+ '--columns-count': String(c.count),
16
+ '--columns-max-width': `${c.maxWidth}px`,
17
+ '--columns-gutter': `${c.gutter}px`,
18
+ '--columns-margin': `${c.margin}px`,
19
+ };
20
+ }
21
+
22
+ /**
23
+ * Only emit column CSS vars once the user has actually modified columns.
24
+ * While columns match the default, we leave tokens.css in charge — which
25
+ * preserves the `clamp()` in `--columns-gutter` until the editor overrides it.
26
+ */
27
+ export function columnsEqualsDefault(c: ColumnsState): boolean {
28
+ return c.count === DEFAULT_COLUMNS.count
29
+ && c.maxWidth === DEFAULT_COLUMNS.maxWidth
30
+ && c.gutter === DEFAULT_COLUMNS.gutter
31
+ && c.margin === DEFAULT_COLUMNS.margin;
32
+ }
33
+
34
+ export function parseColumnVars(vars: Record<string, string>): Partial<ColumnsState> {
35
+ const out: Partial<ColumnsState> = {};
36
+ const count = parseInt(vars['--columns-count'] ?? '', 10);
37
+ if (Number.isFinite(count) && count > 0) out.count = count;
38
+ const maxWidth = parseFloat(vars['--columns-max-width'] ?? '');
39
+ if (Number.isFinite(maxWidth)) out.maxWidth = Math.round(maxWidth);
40
+ const gutter = parseFloat(vars['--columns-gutter'] ?? '');
41
+ if (Number.isFinite(gutter)) out.gutter = Math.round(gutter);
42
+ const margin = parseFloat(vars['--columns-margin'] ?? '');
43
+ if (Number.isFinite(margin)) out.margin = Math.round(margin);
44
+ return out;
45
+ }
46
+
47
+ /**
48
+ * Loader: route the relevant entries from a freshly-loaded theme's vars bag
49
+ * into `next.columns` and remove them from the bag so derivation stays
50
+ * single-source. Mutates `next` and `rawVars` in place.
51
+ */
52
+ export function loadColumnsFromVars(
53
+ next: import('../editorTypes').EditorState,
54
+ rawVars: Record<string, string>,
55
+ ): void {
56
+ const overrides = parseColumnVars(rawVars);
57
+ next.columns = { ...DEFAULT_COLUMNS, ...overrides };
58
+ for (const name of COLUMN_VAR_NAMES) delete rawVars[name];
59
+ }