@motion-proto/live-tokens 0.1.0 → 0.3.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.
- package/README.md +160 -21
- package/dist-plugin/index.cjs +823 -336
- package/dist-plugin/index.d.cts +9 -7
- package/dist-plugin/index.d.ts +9 -7
- package/dist-plugin/index.js +822 -335
- package/package.json +51 -23
- package/src/assets/newspaper.webp +0 -0
- package/src/assets/offering.webp +0 -0
- package/src/component-editor/BadgeEditor.svelte +170 -0
- package/src/component-editor/CalloutEditor.svelte +103 -0
- package/src/component-editor/CardEditor.svelte +184 -0
- package/src/component-editor/CollapsibleSectionEditor.svelte +167 -0
- package/src/component-editor/CornerBadgeEditor.svelte +207 -0
- package/src/component-editor/DialogEditor.svelte +172 -0
- package/src/component-editor/ImageEditor.svelte +72 -0
- package/src/component-editor/InlineEditActionsEditor.svelte +83 -0
- package/src/component-editor/NotificationEditor.svelte +160 -0
- package/src/component-editor/ProgressBarEditor.svelte +124 -0
- package/src/component-editor/RadioButtonEditor.svelte +140 -0
- package/src/component-editor/SectionDividerEditor.svelte +263 -0
- package/src/component-editor/SegmentedControlEditor.svelte +154 -0
- package/src/component-editor/StandardButtonsEditor.svelte +178 -0
- package/src/component-editor/TabBarEditor.svelte +137 -0
- package/src/component-editor/TableEditor.svelte +128 -0
- package/src/component-editor/TooltipEditor.svelte +122 -0
- package/src/component-editor/editorTokens.test.ts +93 -0
- package/src/component-editor/groupKeySlots.test.ts +67 -0
- package/src/component-editor/groupKeySnapshot.test.ts +52 -0
- package/src/component-editor/index.ts +5 -0
- package/src/component-editor/registry.ts +246 -0
- package/src/component-editor/scaffolding/AngleDial.svelte +185 -0
- package/src/component-editor/scaffolding/ComponentEditorBase.svelte +96 -0
- package/src/component-editor/scaffolding/ComponentFileManager.svelte +682 -0
- package/src/component-editor/scaffolding/ComponentFileMenu.svelte +312 -0
- package/src/component-editor/scaffolding/ComponentsTab.svelte +69 -0
- package/src/component-editor/scaffolding/CopyFromMenu.svelte +246 -0
- package/src/component-editor/scaffolding/DemoHeader.svelte +21 -0
- package/src/component-editor/scaffolding/DividerEditor.svelte +81 -0
- package/src/component-editor/scaffolding/FieldsetWrapper.svelte +46 -0
- package/src/component-editor/scaffolding/GradientCard.svelte +291 -0
- package/src/component-editor/scaffolding/LinkageChart.svelte +297 -0
- package/src/component-editor/scaffolding/LinkedBlock.svelte +418 -0
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +57 -0
- package/src/component-editor/scaffolding/SaveAsDialog.svelte +177 -0
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +25 -0
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +56 -0
- package/src/component-editor/scaffolding/StateBlock.svelte +115 -0
- package/src/component-editor/scaffolding/TokenLayout.svelte +511 -0
- package/src/component-editor/scaffolding/TypeEditor.svelte +82 -0
- package/src/component-editor/scaffolding/VariantGroup.svelte +277 -0
- package/src/component-editor/scaffolding/buildTypeGroupTokens.ts +97 -0
- package/src/component-editor/scaffolding/componentSectionType.ts +8 -0
- package/src/component-editor/scaffolding/componentSources.ts +9 -0
- package/src/component-editor/scaffolding/defaultSections.ts +16 -0
- package/src/component-editor/scaffolding/editorContext.ts +44 -0
- package/src/component-editor/scaffolding/linkedBlock.ts +226 -0
- package/src/component-editor/scaffolding/siblings.ts +33 -0
- package/src/component-editor/scaffolding/types.ts +39 -0
- package/src/components/Badge.svelte +231 -42
- package/src/components/Button.svelte +324 -124
- package/src/components/Callout.svelte +145 -0
- package/src/components/Card.svelte +123 -25
- package/src/components/CollapsibleSection.svelte +213 -35
- package/src/components/CornerBadge.svelte +224 -0
- package/src/components/Dialog.svelte +137 -114
- package/src/components/Image.svelte +43 -0
- package/src/components/InlineEditActions.svelte +74 -14
- package/src/components/Notification.svelte +184 -163
- package/src/components/ProgressBar.svelte +216 -22
- package/src/components/RadioButton.svelte +110 -40
- package/src/components/SectionDivider.svelte +428 -74
- package/src/components/SegmentedControl.svelte +203 -0
- package/src/components/TabBar.svelte +146 -21
- package/src/components/Table.svelte +102 -0
- package/src/components/Tooltip.svelte +45 -19
- package/src/components/types.ts +51 -0
- package/src/data/google-fonts.json +75 -0
- package/src/lib/ColumnsOverlay.svelte +20 -7
- package/src/lib/LiveEditorOverlay.svelte +265 -82
- package/src/lib/columnsOverlay.ts +21 -17
- package/src/lib/componentConfig.test.ts +204 -0
- package/src/lib/componentConfigKeys.ts +19 -0
- package/src/lib/componentConfigService.ts +88 -0
- package/src/lib/copyPopover.ts +30 -0
- package/src/lib/cssVarSync.ts +59 -7
- package/src/lib/editorConfigStore.ts +0 -10
- package/src/lib/editorCore.ts +402 -0
- package/src/lib/editorKeybindings.ts +52 -0
- package/src/lib/editorPersistence.ts +106 -0
- package/src/lib/editorRenderer.ts +74 -0
- package/src/lib/editorStore.test.ts +328 -0
- package/src/lib/editorStore.ts +412 -0
- package/src/lib/editorTypes.ts +100 -0
- package/src/lib/editorViewStore.ts +55 -0
- package/src/lib/files/versionedFileResource.ts +140 -0
- package/src/lib/fontLoader.ts +130 -0
- package/src/lib/fontMigration.ts +140 -0
- package/src/lib/fontParse.ts +168 -0
- package/src/lib/index.ts +48 -31
- package/src/lib/lazyConfig.test.ts +54 -0
- package/src/lib/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +64 -0
- package/src/lib/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +71 -0
- package/src/lib/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +43 -0
- package/src/lib/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +68 -0
- package/src/lib/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +35 -0
- package/src/lib/migrations/2026-05-10-sectiondivider-gradient-stops.ts +50 -0
- package/src/lib/migrations/2026-05-13-primary-to-brand.ts +90 -0
- package/src/lib/migrations/index.ts +93 -0
- package/src/lib/migrations/migrations.test.ts +341 -0
- package/src/lib/navLinkTypes.ts +1 -0
- package/src/lib/overlayState.ts +3 -0
- package/src/lib/paletteDerivation.ts +300 -0
- package/src/lib/parentRouteStore.ts +42 -0
- package/src/lib/parsers/globalRootBlock.ts +32 -0
- package/src/lib/presetService.ts +94 -0
- package/src/lib/router.ts +49 -0
- package/src/lib/scrollSection.ts +45 -0
- package/src/lib/slices/columns.ts +59 -0
- package/src/lib/slices/components.ts +362 -0
- package/src/lib/slices/domainVars.ts +15 -0
- package/src/lib/slices/fonts.ts +30 -0
- package/src/lib/slices/gradients.ts +153 -0
- package/src/lib/slices/overlays.ts +132 -0
- package/src/lib/slices/palettes.ts +26 -0
- package/src/lib/slices/shadows.ts +123 -0
- package/src/lib/storage.ts +88 -0
- package/src/lib/themeInit.ts +74 -0
- package/src/lib/themeService.ts +101 -0
- package/src/lib/themeTypes.ts +146 -0
- package/src/lib/tokenRegistry.ts +148 -0
- package/src/pages/ComponentEditorPage.svelte +384 -0
- package/src/pages/ComponentEditorPage.svelte.d.ts +2 -0
- package/src/pages/Editor.svelte +98 -0
- package/src/pages/Editor.svelte.d.ts +2 -0
- package/src/pages/EditorShell.svelte +348 -0
- package/src/styles/_padding.scss +34 -0
- package/src/styles/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
- package/src/styles/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
- package/src/styles/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
- package/src/styles/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
- package/src/styles/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
- package/src/styles/fonts/Manrope/Manrope-latin.woff2 +0 -0
- package/src/styles/fonts.css +22 -10
- package/src/styles/form-controls.css +14 -16
- package/src/styles/tokens.css +1322 -0
- package/src/styles/ui-editor.css +126 -0
- package/src/{showcase → ui}/BezierCurveEditor.svelte +14 -14
- package/src/{showcase → ui}/ColorEditPanel.svelte +42 -36
- package/src/ui/EditorViewSwitcher.svelte +180 -0
- package/src/ui/FontStackEditor.svelte +360 -0
- package/src/ui/GradientEditor.svelte +461 -0
- package/src/ui/GradientStopPicker.svelte +74 -0
- package/src/ui/PaletteEditor.svelte +1590 -0
- package/src/ui/PaletteEditor.test.ts +108 -0
- package/src/ui/PresetFileManager.svelte +567 -0
- package/src/ui/ProjectFontsSection.svelte +645 -0
- package/src/{showcase → ui}/SurfacesTab.svelte +39 -41
- package/src/{showcase → ui}/TextTab.svelte +27 -29
- package/src/{showcase/TokenFileManager.svelte → ui/ThemeFileManager.svelte} +196 -112
- package/src/ui/Toggle.svelte +108 -0
- package/src/ui/UICopyPopover.svelte +78 -0
- package/src/{showcase/EditorDialog.svelte → ui/UIDialog.svelte} +66 -25
- package/src/ui/UIFontFamilySelector.svelte +309 -0
- package/src/ui/UIFontSizeSelector.svelte +165 -0
- package/src/ui/UIFontWeightSelector.svelte +52 -0
- package/src/ui/UILineHeightSelector.svelte +47 -0
- package/src/ui/UILinkToggle.svelte +60 -0
- package/src/ui/UIOptionItem.svelte +74 -0
- package/src/ui/UIOptionList.svelte +27 -0
- package/src/ui/UIPaddingSelector.svelte +661 -0
- package/src/ui/UIPaletteSelector.svelte +1084 -0
- package/src/ui/UIRadio.svelte +72 -0
- package/src/ui/UIRadioGroup.svelte +59 -0
- package/src/ui/UIRelinkConfirmPopover.svelte +235 -0
- package/src/ui/UITokenSelector.svelte +509 -0
- package/src/ui/UIVariantSelector.svelte +145 -0
- package/src/ui/VariablesTab.svelte +252 -0
- package/src/ui/index.ts +31 -0
- package/src/ui/keepInViewport.ts +84 -0
- package/src/ui/palette/GradientStopEditor.svelte +482 -0
- package/src/ui/palette/OverridesPanel.svelte +526 -0
- package/src/ui/palette/PaletteBase.svelte +165 -0
- package/src/ui/palette/ScaleCurveEditor.svelte +38 -0
- package/src/ui/palette/paletteEditorState.ts +89 -0
- package/src/ui/sections/ColumnsSection.svelte +273 -0
- package/src/ui/sections/GradientsSection.svelte +147 -0
- package/src/ui/sections/OverlaysSection.svelte +670 -0
- package/src/ui/sections/ShadowsSection.svelte +1250 -0
- package/src/ui/sections/TokenScaleTable.svelte +332 -0
- package/src/ui/sections/tokenScales.ts +81 -0
- package/src/ui/variantScales.ts +108 -0
- package/src/components/DetailNav.svelte +0 -78
- package/src/components/Toggle.svelte +0 -86
- package/src/lib/pageSource.ts +0 -6
- package/src/lib/tokenInit.ts +0 -29
- package/src/lib/tokenService.ts +0 -144
- package/src/lib/tokenTypes.ts +0 -45
- package/src/pages/Admin.svelte +0 -100
- package/src/pages/ShowcasePage.svelte +0 -146
- package/src/showcase/BackupBrowser.svelte +0 -617
- package/src/showcase/ComponentsTab.svelte +0 -107
- package/src/showcase/PaletteEditor.svelte +0 -2579
- package/src/showcase/PaletteSelector.svelte +0 -627
- package/src/showcase/TokenMap.svelte +0 -54
- package/src/showcase/VariablesTab.svelte +0 -2657
- package/src/showcase/VisualsTab.svelte +0 -233
- package/src/showcase/demos/BadgeDemo.svelte +0 -58
- package/src/showcase/demos/CardDemo.svelte +0 -52
- package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -194
- package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -56
- package/src/showcase/demos/DialogDemo.svelte +0 -42
- package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -27
- package/src/showcase/demos/NotificationDemo.svelte +0 -149
- package/src/showcase/demos/ProgressBarDemo.svelte +0 -56
- package/src/showcase/demos/RadioButtonDemo.svelte +0 -58
- package/src/showcase/demos/SectionDividerDemo.svelte +0 -79
- package/src/showcase/demos/StandardButtonsDemo.svelte +0 -457
- package/src/showcase/demos/TabBarDemo.svelte +0 -60
- package/src/showcase/demos/TooltipDemo.svelte +0 -54
- package/src/showcase/editor.css +0 -93
- package/src/showcase/index.ts +0 -17
- package/src/styles/fonts/Domine/Domine-VariableFont_wght.ttf +0 -0
- package/src/styles/fonts/Domine/OFL.txt +0 -97
- package/src/styles/fonts/Domine/README.txt +0 -66
- /package/src/{showcase → ui}/curveEngine.ts +0 -0
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { tick } from 'svelte';
|
|
2
|
+
import type { Theme, ThemeMeta } from './themeTypes';
|
|
3
|
+
import type { EditorState } from './editorTypes';
|
|
4
|
+
import {
|
|
5
|
+
versionedFileResource,
|
|
6
|
+
sanitizeFileName as sanitizeFileNameImpl,
|
|
7
|
+
} from './files/versionedFileResource';
|
|
8
|
+
import { loadFromFile as loadEditorState, toTheme, markSaved } from './editorStore';
|
|
9
|
+
import { activeFileName } from './editorConfigStore';
|
|
10
|
+
import { applyFontSources, applyFontStacks } from './fontLoader';
|
|
11
|
+
import { migrateThemeFonts } from './fontMigration';
|
|
12
|
+
|
|
13
|
+
// ── API helpers ──────────────────────────────────────────────
|
|
14
|
+
//
|
|
15
|
+
// All theme CRUD goes through `versionedFileResource('/api/themes')` —
|
|
16
|
+
// shared with `componentConfigService`'s per-component clients. Theme-specific
|
|
17
|
+
// response shapes (ThemeMeta list payload, ProductionInfo) are layered on top
|
|
18
|
+
// via the generic type parameters.
|
|
19
|
+
|
|
20
|
+
export interface ProductionInfo {
|
|
21
|
+
fileName: string;
|
|
22
|
+
name: string;
|
|
23
|
+
updatedAt: string;
|
|
24
|
+
cssVariables: Record<string, string>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const themeResource = versionedFileResource<Theme, ThemeMeta, ProductionInfo>({
|
|
28
|
+
baseUrl: '/api/themes',
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export async function listThemes(): Promise<ThemeMeta[]> {
|
|
32
|
+
const data = await themeResource.list();
|
|
33
|
+
return data.files;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export const loadTheme = (fileName: string): Promise<Theme> => themeResource.load(fileName);
|
|
37
|
+
export const saveTheme = (fileName: string, data: Theme): Promise<void> =>
|
|
38
|
+
themeResource.save(fileName, data);
|
|
39
|
+
export const deleteTheme = (fileName: string): Promise<void> => themeResource.remove(fileName);
|
|
40
|
+
|
|
41
|
+
export async function getActiveTheme(): Promise<Theme | null> {
|
|
42
|
+
return themeResource.getActive();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export const setActiveFile = (fileName: string): Promise<void> => themeResource.setActive(fileName);
|
|
46
|
+
|
|
47
|
+
// ── Production API helpers ─────────────────────────────────
|
|
48
|
+
|
|
49
|
+
export const getProductionInfo = (): Promise<ProductionInfo> => themeResource.getProductionInfo();
|
|
50
|
+
|
|
51
|
+
export async function setProductionFile(
|
|
52
|
+
fileName: string,
|
|
53
|
+
): Promise<{ ok: boolean; fileName: string; name: string }> {
|
|
54
|
+
const data = await themeResource.setProduction(fileName);
|
|
55
|
+
return { ok: data.ok, fileName: data.fileName, name: data.name };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Sanitize a display name to a safe file name. Re-exported from the shared
|
|
59
|
+
* `files/versionedFileResource` so the dev-server plugin can import the
|
|
60
|
+
* canonical pure helper without depending on this module's CSS imports. */
|
|
61
|
+
export const sanitizeFileName = sanitizeFileNameImpl;
|
|
62
|
+
|
|
63
|
+
// ── Theme save/load orchestration ──────────────────────────
|
|
64
|
+
//
|
|
65
|
+
// `persistTheme` and `hydrateTheme` are the canonical entry points for
|
|
66
|
+
// round-tripping editor state to disk. Callers (e.g. `EditorShell`) need
|
|
67
|
+
// only handle UI-level concerns (status flashing, error chrome) and
|
|
68
|
+
// delegate the actual orchestration here.
|
|
69
|
+
|
|
70
|
+
/** Snapshot the editor state to disk under `fileName`, mark the file active,
|
|
71
|
+
* and clear the dirty flag. The caller is responsible for surfacing
|
|
72
|
+
* saving / saved / error UI states around this call. */
|
|
73
|
+
export async function persistTheme(
|
|
74
|
+
state: EditorState,
|
|
75
|
+
fileName: string,
|
|
76
|
+
displayName: string,
|
|
77
|
+
): Promise<void> {
|
|
78
|
+
await tick();
|
|
79
|
+
const theme = toTheme(state, { name: displayName });
|
|
80
|
+
await saveTheme(fileName, theme);
|
|
81
|
+
await setActiveFile(fileName);
|
|
82
|
+
activeFileName.set(fileName);
|
|
83
|
+
markSaved();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Load a theme file into the editor state and re-apply font side-effects
|
|
87
|
+
* (@font-face rules + `--font-*` CSS vars on :root). */
|
|
88
|
+
export async function hydrateTheme(fileName: string): Promise<void> {
|
|
89
|
+
const theme = await loadTheme(fileName);
|
|
90
|
+
migrateThemeFonts(theme);
|
|
91
|
+
loadEditorState(theme);
|
|
92
|
+
// Font data is in state.fonts via loadEditorState; the DOM-side-effect
|
|
93
|
+
// helpers still need to run so @font-face rules and --font-* CSS vars
|
|
94
|
+
// land on :root.
|
|
95
|
+
if (theme.fontSources && theme.fontSources.length > 0) {
|
|
96
|
+
applyFontSources(theme.fontSources);
|
|
97
|
+
}
|
|
98
|
+
if (theme.fontStacks && theme.fontStacks.length > 0) {
|
|
99
|
+
applyFontStacks(theme.fontStacks, theme.fontSources ?? []);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { CurveAnchor } from '../ui/curveEngine';
|
|
2
|
+
|
|
3
|
+
export type GradientStyle = 'linear' | 'radial' | 'conic';
|
|
4
|
+
|
|
5
|
+
export interface GradientStop {
|
|
6
|
+
position: number;
|
|
7
|
+
paletteLabel: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface PaletteConfig {
|
|
11
|
+
baseColor: string;
|
|
12
|
+
tintHue: number;
|
|
13
|
+
tintChroma?: number;
|
|
14
|
+
lightnessCurve: CurveAnchor[];
|
|
15
|
+
saturationCurve: CurveAnchor[];
|
|
16
|
+
grayLightnessCurve: CurveAnchor[];
|
|
17
|
+
graySaturationCurve: CurveAnchor[];
|
|
18
|
+
scaleCurves: Record<string, { lightness: CurveAnchor[]; saturation: CurveAnchor[] }>;
|
|
19
|
+
curveOffset: Record<string, number>;
|
|
20
|
+
overrides: Record<string, string>;
|
|
21
|
+
snappedScales: string[];
|
|
22
|
+
emptyMode?: 'solid' | 'gradient';
|
|
23
|
+
emptyStep?: string;
|
|
24
|
+
gradientStyle?: GradientStyle;
|
|
25
|
+
gradientAngle?: number;
|
|
26
|
+
gradientReverse?: boolean;
|
|
27
|
+
gradientStops?: GradientStop[];
|
|
28
|
+
gradientSize?: 'page' | 'window';
|
|
29
|
+
anchorToBase?: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export type FontSourceKind = 'google' | 'typekit' | 'css-url' | 'font-face';
|
|
33
|
+
|
|
34
|
+
export interface FontFamily {
|
|
35
|
+
id: string;
|
|
36
|
+
name: string;
|
|
37
|
+
cssName: string;
|
|
38
|
+
weights?: number[];
|
|
39
|
+
italics?: boolean;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface FontSource {
|
|
43
|
+
id: string;
|
|
44
|
+
kind: FontSourceKind;
|
|
45
|
+
url?: string;
|
|
46
|
+
cssText?: string;
|
|
47
|
+
families: FontFamily[];
|
|
48
|
+
label?: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type SystemCascadePreset = 'system-ui-sans' | 'system-ui-serif' | 'system-ui-mono';
|
|
52
|
+
export type GenericFamily = 'sans-serif' | 'serif' | 'monospace' | 'cursive' | 'fantasy';
|
|
53
|
+
export type FontStackVariable = '--font-display' | '--font-sans' | '--font-serif' | '--font-mono';
|
|
54
|
+
|
|
55
|
+
export type FontStackSlot =
|
|
56
|
+
| { kind: 'project'; familyId: string }
|
|
57
|
+
| { kind: 'system'; preset: SystemCascadePreset }
|
|
58
|
+
| { kind: 'generic'; value: GenericFamily };
|
|
59
|
+
|
|
60
|
+
export interface FontStack {
|
|
61
|
+
variable: FontStackVariable;
|
|
62
|
+
slots: FontStackSlot[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface Theme {
|
|
66
|
+
name: string;
|
|
67
|
+
createdAt: string;
|
|
68
|
+
updatedAt: string;
|
|
69
|
+
editorConfigs: Record<string, PaletteConfig>;
|
|
70
|
+
cssVariables: Record<string, string>;
|
|
71
|
+
fontSources?: FontSource[];
|
|
72
|
+
fontStacks?: FontStack[];
|
|
73
|
+
/**
|
|
74
|
+
* Server-attached file-name marker for round-tripping the file identity
|
|
75
|
+
* back to the client. Set by `themeFileApi`'s GET handlers; read by
|
|
76
|
+
* `themeInit` to seed `activeFileName`. Optional and not persisted to disk.
|
|
77
|
+
*/
|
|
78
|
+
_fileName?: string;
|
|
79
|
+
/**
|
|
80
|
+
* Migration stamp. Absent on legacy files, treated as 0; the loader runs
|
|
81
|
+
* any registered theme migrations whose `fromVersion >= file.schemaVersion`.
|
|
82
|
+
* Save paths stamp the current value so resaved files skip past
|
|
83
|
+
* migrations.
|
|
84
|
+
*/
|
|
85
|
+
schemaVersion?: number;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface ThemeMeta {
|
|
89
|
+
name: string;
|
|
90
|
+
fileName: string;
|
|
91
|
+
updatedAt: string;
|
|
92
|
+
isActive: boolean;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface ComponentConfig {
|
|
96
|
+
name: string;
|
|
97
|
+
component: string;
|
|
98
|
+
createdAt: string;
|
|
99
|
+
updatedAt: string;
|
|
100
|
+
aliases: Record<string, string>;
|
|
101
|
+
config?: Record<string, unknown>;
|
|
102
|
+
/**
|
|
103
|
+
* Server-attached file-name marker. Same role as `Theme._fileName`. Set by
|
|
104
|
+
* the component-configs GET handlers; not persisted to disk.
|
|
105
|
+
*/
|
|
106
|
+
_fileName?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Migration stamp. Absent on legacy files, treated as 0. See `Theme.schemaVersion`.
|
|
109
|
+
*/
|
|
110
|
+
schemaVersion?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface ComponentConfigMeta {
|
|
114
|
+
name: string;
|
|
115
|
+
fileName: string;
|
|
116
|
+
updatedAt: string;
|
|
117
|
+
isActive: boolean;
|
|
118
|
+
isProduction: boolean;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Manifest that captures an entire site state — the active theme plus the
|
|
123
|
+
* active config for every component. Loading a preset flips the relevant
|
|
124
|
+
* `_active.json` pointers; the underlying theme + component-config files stay
|
|
125
|
+
* the source of truth, so editing them flows through any preset that
|
|
126
|
+
* references them.
|
|
127
|
+
*/
|
|
128
|
+
export interface Preset {
|
|
129
|
+
name: string;
|
|
130
|
+
createdAt: string;
|
|
131
|
+
updatedAt: string;
|
|
132
|
+
/** File basename (no `.json`) of the theme this preset pins. */
|
|
133
|
+
theme: string;
|
|
134
|
+
/** Map of componentId → config file basename. Components omitted here fall
|
|
135
|
+
* back to "default" at apply time. */
|
|
136
|
+
componentConfigs: Record<string, string>;
|
|
137
|
+
/** Server-attached file-name marker. Same role as `Theme._fileName`. */
|
|
138
|
+
_fileName?: string;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface PresetMeta {
|
|
142
|
+
name: string;
|
|
143
|
+
fileName: string;
|
|
144
|
+
updatedAt: string;
|
|
145
|
+
isActive: boolean;
|
|
146
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static registry of CSS custom-property declarations.
|
|
3
|
+
*
|
|
4
|
+
* Layer-1 design tokens live in tokens.css (the single source of truth for
|
|
5
|
+
* shared primitives). Layer-2 component tokens live inside each component's
|
|
6
|
+
* `<style>` block as `:global(:root) { ... }` declarations — owned by the
|
|
7
|
+
* component that uses them.
|
|
8
|
+
*
|
|
9
|
+
* Token-picking UIs need to recover the semantic identity of an arbitrary
|
|
10
|
+
* variable — e.g. to display "info text / primary" rather than a raw hex. For
|
|
11
|
+
* alias variables we must follow the `var(--x)` chain from the declaration to
|
|
12
|
+
* find the underlying token whose *name* the selector can parse.
|
|
13
|
+
*
|
|
14
|
+
* This module parses both sources at import time (tokens.css via Vite's
|
|
15
|
+
* ?raw loader, component files via import.meta.glob) and exposes helpers that
|
|
16
|
+
* walk those aliases. The pure `buildTokenRegistry` and `extractGlobalRootBody`
|
|
17
|
+
* helpers are also exported so tests can construct a registry from fs-loaded
|
|
18
|
+
* files (Vitest's default CSS plugin swallows `?raw` imports for .css files).
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { derived, type Readable } from 'svelte/store';
|
|
22
|
+
import tokensCss from '../styles/tokens.css?raw';
|
|
23
|
+
import { editorState } from './editorStore';
|
|
24
|
+
import type { EditorState } from './editorTypes';
|
|
25
|
+
import { extractGlobalRootBody } from './parsers/globalRootBlock';
|
|
26
|
+
|
|
27
|
+
// Re-exported for tests and downstream consumers that previously imported it
|
|
28
|
+
// from this module. The canonical implementation lives in `./parsers/globalRootBlock`
|
|
29
|
+
// so the dev-server vite plugin can share it.
|
|
30
|
+
export { extractGlobalRootBody };
|
|
31
|
+
|
|
32
|
+
const componentSources = import.meta.glob('../components/*.svelte', {
|
|
33
|
+
query: '?raw',
|
|
34
|
+
import: 'default',
|
|
35
|
+
eager: true,
|
|
36
|
+
}) as Record<string, string>;
|
|
37
|
+
|
|
38
|
+
export interface TokenRegistry {
|
|
39
|
+
getDeclaredValue(varName: string): string | null;
|
|
40
|
+
resolveAliasChain(varName: string): string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Pure constructor: parses a CSS source string (possibly concatenated from
|
|
45
|
+
* multiple files) and returns a registry bound to that snapshot.
|
|
46
|
+
*/
|
|
47
|
+
export function buildTokenRegistry(cssText: string): TokenRegistry {
|
|
48
|
+
const declarations = new Map<string, string>();
|
|
49
|
+
const re = /(--[a-z0-9-]+)\s*:\s*([^;]+);/gi;
|
|
50
|
+
let m: RegExpExecArray | null;
|
|
51
|
+
while ((m = re.exec(cssText)) !== null) {
|
|
52
|
+
declarations.set(m[1], m[2].trim());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function resolveAliasChain(varName: string): string[] {
|
|
56
|
+
const chain = [varName];
|
|
57
|
+
const visited = new Set<string>([varName]);
|
|
58
|
+
let current = varName;
|
|
59
|
+
while (true) {
|
|
60
|
+
const decl = declarations.get(current);
|
|
61
|
+
if (!decl) return chain;
|
|
62
|
+
const aliasMatch = decl.match(/var\((--[a-z0-9-]+)\)/i);
|
|
63
|
+
if (!aliasMatch) return chain;
|
|
64
|
+
const next = aliasMatch[1];
|
|
65
|
+
if (visited.has(next)) return chain;
|
|
66
|
+
visited.add(next);
|
|
67
|
+
chain.push(next);
|
|
68
|
+
current = next;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
getDeclaredValue: (v) => declarations.get(v) ?? null,
|
|
74
|
+
resolveAliasChain,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const componentTokenCss = Object.values(componentSources)
|
|
79
|
+
.map(extractGlobalRootBody)
|
|
80
|
+
.join('\n');
|
|
81
|
+
|
|
82
|
+
const defaultRegistry = buildTokenRegistry(tokensCss + '\n' + componentTokenCss);
|
|
83
|
+
|
|
84
|
+
/** Raw declared value from tokens.css, or null if not declared. */
|
|
85
|
+
export const getDeclaredValue = defaultRegistry.getDeclaredValue;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Walk the alias chain starting at `varName`, returning each successive
|
|
89
|
+
* variable reference (including the starting one). Stops when a declaration is
|
|
90
|
+
* a literal value (e.g. hex) or when the next link is absent from the
|
|
91
|
+
* registry. Guards against cycles.
|
|
92
|
+
*
|
|
93
|
+
* Example: `--notification-info-title` → `--text-info` → stops (literal hex).
|
|
94
|
+
* Returns `['--notification-info-title', '--text-info']`.
|
|
95
|
+
*
|
|
96
|
+
* This is a static snapshot: it only sees declarations from tokens.css + the
|
|
97
|
+
* baked-in `:global(:root)` blocks at module load. For state-aware resolution
|
|
98
|
+
* that includes live component-alias edits, subscribe to `tokenRegistry$`.
|
|
99
|
+
*/
|
|
100
|
+
export const resolveAliasChain = defaultRegistry.resolveAliasChain;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Build a registry that layers live component-alias overrides on top of the
|
|
104
|
+
* static base. Cheap enough to rebuild per editorState tick — components
|
|
105
|
+
* count is small and the alias walk is lazy.
|
|
106
|
+
*/
|
|
107
|
+
function buildOverlayRegistry(
|
|
108
|
+
base: TokenRegistry,
|
|
109
|
+
components: EditorState['components'],
|
|
110
|
+
): TokenRegistry {
|
|
111
|
+
const overrides = new Map<string, string>();
|
|
112
|
+
for (const slice of Object.values(components)) {
|
|
113
|
+
for (const [varName, ref] of Object.entries(slice.aliases)) {
|
|
114
|
+
overrides.set(varName, ref.kind === 'token' ? `var(${ref.name})` : ref.value);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const getDeclared = (v: string): string | null =>
|
|
118
|
+
overrides.has(v) ? overrides.get(v)! : base.getDeclaredValue(v);
|
|
119
|
+
return {
|
|
120
|
+
getDeclaredValue: getDeclared,
|
|
121
|
+
resolveAliasChain(varName: string): string[] {
|
|
122
|
+
const chain = [varName];
|
|
123
|
+
const visited = new Set<string>([varName]);
|
|
124
|
+
let current = varName;
|
|
125
|
+
while (true) {
|
|
126
|
+
const decl = getDeclared(current);
|
|
127
|
+
if (!decl) return chain;
|
|
128
|
+
const aliasMatch = decl.match(/var\((--[a-z0-9-]+)\)/i);
|
|
129
|
+
if (!aliasMatch) return chain;
|
|
130
|
+
const next = aliasMatch[1];
|
|
131
|
+
if (visited.has(next)) return chain;
|
|
132
|
+
visited.add(next);
|
|
133
|
+
chain.push(next);
|
|
134
|
+
current = next;
|
|
135
|
+
}
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* State-aware token registry: overlays live `state.components[*].aliases`
|
|
142
|
+
* on top of the static base registry. Consume with `$tokenRegistry$` in
|
|
143
|
+
* Svelte components when the UI needs to reflect in-flight alias edits
|
|
144
|
+
* (e.g. selectors displaying the currently-selected semantic token).
|
|
145
|
+
*/
|
|
146
|
+
export const tokenRegistry$: Readable<TokenRegistry> = derived(editorState, ($state) =>
|
|
147
|
+
buildOverlayRegistry(defaultRegistry, $state.components),
|
|
148
|
+
);
|