@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.
- package/README.md +168 -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 +46 -20
- 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 +257 -78
- 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 -30
- 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 +42 -10
- 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 -39
- package/src/{showcase → ui}/TextTab.svelte +27 -27
- 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/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 -144
- package/src/showcase/BackupBrowser.svelte +0 -617
- package/src/showcase/ComponentsTab.svelte +0 -105
- 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 -2655
- package/src/showcase/VisualsTab.svelte +0 -231
- package/src/showcase/demos/BadgeDemo.svelte +0 -56
- package/src/showcase/demos/CardDemo.svelte +0 -50
- package/src/showcase/demos/ChoiceButtonsDemo.svelte +0 -192
- package/src/showcase/demos/CollapsibleSectionDemo.svelte +0 -54
- package/src/showcase/demos/DialogDemo.svelte +0 -42
- package/src/showcase/demos/InlineEditActionsDemo.svelte +0 -25
- package/src/showcase/demos/NotificationDemo.svelte +0 -147
- package/src/showcase/demos/ProgressBarDemo.svelte +0 -54
- package/src/showcase/demos/RadioButtonDemo.svelte +0 -56
- package/src/showcase/demos/SectionDividerDemo.svelte +0 -77
- package/src/showcase/demos/StandardButtonsDemo.svelte +0 -455
- package/src/showcase/demos/TabBarDemo.svelte +0 -58
- package/src/showcase/demos/TooltipDemo.svelte +0 -52
- 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,130 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
FontFamily,
|
|
3
|
+
FontSource,
|
|
4
|
+
FontStack,
|
|
5
|
+
FontStackSlot,
|
|
6
|
+
FontStackVariable,
|
|
7
|
+
SystemCascadePreset,
|
|
8
|
+
} from './themeTypes';
|
|
9
|
+
import { setCssVar, getSyncedDocuments } from './cssVarSync';
|
|
10
|
+
|
|
11
|
+
export const SYSTEM_CASCADES: Record<SystemCascadePreset, string> = {
|
|
12
|
+
'system-ui-sans':
|
|
13
|
+
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
14
|
+
'system-ui-serif': 'Georgia, "Times New Roman", serif',
|
|
15
|
+
'system-ui-mono': 'ui-monospace, "SF Mono", Menlo, Consolas, monospace',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const LINK_ATTR = 'data-font-source-id';
|
|
19
|
+
const STYLE_ATTR = 'data-font-source-id';
|
|
20
|
+
|
|
21
|
+
function syncedHeads(): HTMLHeadElement[] {
|
|
22
|
+
return getSyncedDocuments().map((d) => d.head);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function findExistingNode(head: HTMLHeadElement, sourceId: string): Element | null {
|
|
26
|
+
return head.querySelector(`[${LINK_ATTR}="${CSS.escape(sourceId)}"]`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildNodeFor(source: FontSource, doc: Document): Element | null {
|
|
30
|
+
if (source.kind === 'font-face') {
|
|
31
|
+
if (!source.cssText) return null;
|
|
32
|
+
const style = doc.createElement('style');
|
|
33
|
+
style.setAttribute(STYLE_ATTR, source.id);
|
|
34
|
+
style.textContent = source.cssText;
|
|
35
|
+
return style;
|
|
36
|
+
}
|
|
37
|
+
if (!source.url) return null;
|
|
38
|
+
const link = doc.createElement('link');
|
|
39
|
+
link.rel = 'stylesheet';
|
|
40
|
+
link.href = source.url;
|
|
41
|
+
link.setAttribute(LINK_ATTR, source.id);
|
|
42
|
+
return link;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Inject/update <link>/<style> elements in <head> (and parent <head> when in
|
|
47
|
+
* an iframe) to match the given sources. Existing nodes are diffed by id so
|
|
48
|
+
* repeated calls don't thrash the DOM. Sources present in the DOM but absent
|
|
49
|
+
* from the list are removed.
|
|
50
|
+
*/
|
|
51
|
+
export function applyFontSources(sources: FontSource[]): void {
|
|
52
|
+
const wanted = new Map(sources.map((s) => [s.id, s]));
|
|
53
|
+
for (const head of syncedHeads()) {
|
|
54
|
+
const existing = head.querySelectorAll(`[${LINK_ATTR}]`);
|
|
55
|
+
const seen = new Set<string>();
|
|
56
|
+
existing.forEach((node) => {
|
|
57
|
+
const id = node.getAttribute(LINK_ATTR);
|
|
58
|
+
if (!id) return;
|
|
59
|
+
const next = wanted.get(id);
|
|
60
|
+
if (!next) {
|
|
61
|
+
node.remove();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
seen.add(id);
|
|
65
|
+
if (next.kind === 'font-face') {
|
|
66
|
+
if (node.tagName !== 'STYLE' || node.textContent !== (next.cssText ?? '')) {
|
|
67
|
+
node.remove();
|
|
68
|
+
const replacement = buildNodeFor(next, head.ownerDocument!);
|
|
69
|
+
if (replacement) head.appendChild(replacement);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
if (node.tagName !== 'LINK' || (node as HTMLLinkElement).href !== (next.url ?? '')) {
|
|
73
|
+
node.remove();
|
|
74
|
+
const replacement = buildNodeFor(next, head.ownerDocument!);
|
|
75
|
+
if (replacement) head.appendChild(replacement);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
for (const [id, source] of wanted) {
|
|
80
|
+
if (seen.has(id)) continue;
|
|
81
|
+
const node = buildNodeFor(source, head.ownerDocument!);
|
|
82
|
+
if (node) head.appendChild(node);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function resolveSlot(slot: FontStackSlot, familyById: Map<string, FontFamily>): string | null {
|
|
88
|
+
if (slot.kind === 'project') {
|
|
89
|
+
const fam = familyById.get(slot.familyId);
|
|
90
|
+
return fam ? fam.cssName : null;
|
|
91
|
+
}
|
|
92
|
+
if (slot.kind === 'system') return SYSTEM_CASCADES[slot.preset];
|
|
93
|
+
return slot.value;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Resolve a fontStacks list to a flat var-name → css-value map. Exported so
|
|
98
|
+
* callers (e.g. the stack editor) can preview composed values without writing.
|
|
99
|
+
*/
|
|
100
|
+
export function resolveFontStackValues(
|
|
101
|
+
stacks: FontStack[],
|
|
102
|
+
sources: FontSource[],
|
|
103
|
+
): Record<FontStackVariable, string> {
|
|
104
|
+
const familyById = new Map<string, FontFamily>();
|
|
105
|
+
for (const src of sources) {
|
|
106
|
+
for (const f of src.families) familyById.set(f.id, f);
|
|
107
|
+
}
|
|
108
|
+
const out: Partial<Record<FontStackVariable, string>> = {};
|
|
109
|
+
for (const stack of stacks) {
|
|
110
|
+
const parts: string[] = [];
|
|
111
|
+
for (const slot of stack.slots) {
|
|
112
|
+
const v = resolveSlot(slot, familyById);
|
|
113
|
+
if (v) parts.push(v);
|
|
114
|
+
}
|
|
115
|
+
if (parts.length > 0) out[stack.variable] = parts.join(', ');
|
|
116
|
+
}
|
|
117
|
+
return out as Record<FontStackVariable, string>;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Compose each stack into its resolved "family1, family2, ..." string and
|
|
122
|
+
* write it to the matching --font-* variable on :root (and parent :root when
|
|
123
|
+
* in an iframe) via the same pipeline used for color variables.
|
|
124
|
+
*/
|
|
125
|
+
export function applyFontStacks(stacks: FontStack[], sources: FontSource[]): void {
|
|
126
|
+
const resolved = resolveFontStackValues(stacks, sources);
|
|
127
|
+
for (const [name, value] of Object.entries(resolved)) {
|
|
128
|
+
setCssVar(name, value);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import type { FontFamily, FontSource, FontStack, Theme } from './themeTypes';
|
|
2
|
+
import frauncesRomanLatin from '../styles/fonts/Fraunces/Fraunces-roman-latin.woff2?url';
|
|
3
|
+
import frauncesItalicLatin from '../styles/fonts/Fraunces/Fraunces-italic-latin.woff2?url';
|
|
4
|
+
import manropeLatin from '../styles/fonts/Manrope/Manrope-latin.woff2?url';
|
|
5
|
+
|
|
6
|
+
function makeId(prefix: string): string {
|
|
7
|
+
return `${prefix}_${Math.random().toString(36).slice(2, 10)}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function familyId(sourceId: string, name: string): string {
|
|
11
|
+
const slug = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '');
|
|
12
|
+
return `${sourceId}:${slug}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function fam(sourceId: string, name: string, cssName?: string, weights?: number[]): FontFamily {
|
|
16
|
+
return {
|
|
17
|
+
id: familyId(sourceId, name),
|
|
18
|
+
name,
|
|
19
|
+
cssName: cssName ?? `"${name}"`,
|
|
20
|
+
...(weights ? { weights } : {}),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Build the default fontSources that match the hand-written src/styles/fonts.css.
|
|
26
|
+
* Used when a theme has no fontSources yet.
|
|
27
|
+
*/
|
|
28
|
+
export function defaultFontSources(): FontSource[] {
|
|
29
|
+
const typekitId = 'src_typekit_jes8oow';
|
|
30
|
+
const frauncesId = 'src_fraunces_local';
|
|
31
|
+
const manropeId = 'src_manrope_local';
|
|
32
|
+
|
|
33
|
+
return [
|
|
34
|
+
{
|
|
35
|
+
id: typekitId,
|
|
36
|
+
kind: 'typekit',
|
|
37
|
+
url: 'https://use.typekit.net/jes8oow.css',
|
|
38
|
+
label: 'Adobe Typekit',
|
|
39
|
+
families: [
|
|
40
|
+
fam(typekitId, 'fira-code', '"fira-code"'),
|
|
41
|
+
],
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
id: frauncesId,
|
|
45
|
+
kind: 'font-face',
|
|
46
|
+
cssText: `@font-face {\n font-family: "Fraunces";\n src: url('${frauncesRomanLatin}') format('woff2');\n font-weight: 100 900;\n font-style: normal;\n font-display: swap;\n}\n@font-face {\n font-family: "Fraunces";\n src: url('${frauncesItalicLatin}') format('woff2');\n font-weight: 100 900;\n font-style: italic;\n font-display: swap;\n}`,
|
|
47
|
+
label: 'Local',
|
|
48
|
+
families: [
|
|
49
|
+
fam(frauncesId, 'Fraunces', '"Fraunces"', [100, 200, 300, 400, 500, 600, 700, 800, 900]),
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: manropeId,
|
|
54
|
+
kind: 'font-face',
|
|
55
|
+
cssText: `@font-face {\n font-family: "Manrope";\n src: url('${manropeLatin}') format('woff2');\n font-weight: 200 800;\n font-style: normal;\n font-display: swap;\n}`,
|
|
56
|
+
label: 'Local',
|
|
57
|
+
families: [
|
|
58
|
+
fam(manropeId, 'Manrope', '"Manrope"', [200, 300, 400, 500, 600, 700, 800]),
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Build the default fontStacks matching the previous hard-coded display in
|
|
66
|
+
* VariablesTab.svelte plus tokens.css. Each stack references families from
|
|
67
|
+
* defaultFontSources() by id.
|
|
68
|
+
*/
|
|
69
|
+
export function defaultFontStacks(sources: FontSource[]): FontStack[] {
|
|
70
|
+
const byName = new Map<string, string>();
|
|
71
|
+
for (const src of sources) {
|
|
72
|
+
for (const f of src.families) byName.set(f.name.toLowerCase(), f.id);
|
|
73
|
+
}
|
|
74
|
+
const p = (name: string): { kind: 'project'; familyId: string } | null => {
|
|
75
|
+
const id = byName.get(name.toLowerCase());
|
|
76
|
+
return id ? { kind: 'project', familyId: id } : null;
|
|
77
|
+
};
|
|
78
|
+
const pick = (...names: string[]) => names.map(p).filter((x): x is { kind: 'project'; familyId: string } => !!x);
|
|
79
|
+
|
|
80
|
+
return [
|
|
81
|
+
{
|
|
82
|
+
variable: '--font-display',
|
|
83
|
+
slots: [
|
|
84
|
+
...pick('Fraunces'),
|
|
85
|
+
{ kind: 'generic', value: 'serif' },
|
|
86
|
+
],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
variable: '--font-sans',
|
|
90
|
+
slots: [
|
|
91
|
+
...pick('Manrope'),
|
|
92
|
+
{ kind: 'system', preset: 'system-ui-sans' },
|
|
93
|
+
{ kind: 'generic', value: 'sans-serif' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
variable: '--font-serif',
|
|
98
|
+
slots: [
|
|
99
|
+
...pick('Fraunces'),
|
|
100
|
+
{ kind: 'generic', value: 'serif' },
|
|
101
|
+
],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
variable: '--font-mono',
|
|
105
|
+
slots: [
|
|
106
|
+
...pick('fira-code'),
|
|
107
|
+
{ kind: 'system', preset: 'system-ui-mono' },
|
|
108
|
+
{ kind: 'generic', value: 'monospace' },
|
|
109
|
+
],
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Ensure the loaded Theme has fontSources and fontStacks. Mutates in place
|
|
116
|
+
* only when missing; safe to call on already-migrated themes. Also strips any
|
|
117
|
+
* stale --font-* entries from cssVariables since those are now derived.
|
|
118
|
+
*/
|
|
119
|
+
export function migrateThemeFonts(theme: Theme): { migrated: boolean } {
|
|
120
|
+
let migrated = false;
|
|
121
|
+
if (!theme.fontSources || theme.fontSources.length === 0) {
|
|
122
|
+
theme.fontSources = defaultFontSources();
|
|
123
|
+
migrated = true;
|
|
124
|
+
}
|
|
125
|
+
if (!theme.fontStacks || theme.fontStacks.length === 0) {
|
|
126
|
+
theme.fontStacks = defaultFontStacks(theme.fontSources);
|
|
127
|
+
migrated = true;
|
|
128
|
+
}
|
|
129
|
+
if (theme.cssVariables) {
|
|
130
|
+
for (const key of ['--font-display', '--font-sans', '--font-serif', '--font-mono']) {
|
|
131
|
+
if (key in theme.cssVariables) {
|
|
132
|
+
delete theme.cssVariables[key];
|
|
133
|
+
migrated = true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return { migrated };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export { makeId };
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { FontFamily, FontSource, FontSourceKind } from './themeTypes';
|
|
2
|
+
|
|
3
|
+
export interface ParsedFamily {
|
|
4
|
+
name: string;
|
|
5
|
+
weights?: number[];
|
|
6
|
+
italics?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface ParsedSource {
|
|
10
|
+
kind: FontSourceKind;
|
|
11
|
+
families: ParsedFamily[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Parse a Google Fonts CSS2 URL (`?family=Inter:wght@400;700&family=Roboto`)
|
|
16
|
+
* into the families and weights it declares. Pure; no network.
|
|
17
|
+
*/
|
|
18
|
+
export function parseGoogleFontsUrl(url: string): ParsedFamily[] | null {
|
|
19
|
+
let u: URL;
|
|
20
|
+
try { u = new URL(url); } catch { return null; }
|
|
21
|
+
if (!u.hostname.includes('fonts.googleapis.com')) return null;
|
|
22
|
+
const familyParams = u.searchParams.getAll('family');
|
|
23
|
+
if (familyParams.length === 0) return null;
|
|
24
|
+
|
|
25
|
+
return familyParams.map((raw) => {
|
|
26
|
+
const [rawName, spec] = raw.split(':');
|
|
27
|
+
const name = decodeURIComponent(rawName).replace(/\+/g, ' ');
|
|
28
|
+
const parsed: ParsedFamily = { name };
|
|
29
|
+
|
|
30
|
+
if (spec) {
|
|
31
|
+
const axisMatch = spec.match(/(?:ital,)?wght@([^&]+)/);
|
|
32
|
+
const italics = spec.startsWith('ital,');
|
|
33
|
+
if (italics) parsed.italics = true;
|
|
34
|
+
if (axisMatch) {
|
|
35
|
+
const tokens = axisMatch[1].split(';');
|
|
36
|
+
const weights = new Set<number>();
|
|
37
|
+
for (const tok of tokens) {
|
|
38
|
+
const parts = tok.split(',');
|
|
39
|
+
const wghtStr = parts.length === 2 ? parts[1] : parts[0];
|
|
40
|
+
if (wghtStr.includes('..')) {
|
|
41
|
+
const [lo, hi] = wghtStr.split('..').map((n) => parseInt(n, 10));
|
|
42
|
+
if (!isNaN(lo) && !isNaN(hi)) {
|
|
43
|
+
for (let w = lo; w <= hi; w += 100) weights.add(w);
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
const w = parseInt(wghtStr, 10);
|
|
47
|
+
if (!isNaN(w)) weights.add(w);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (weights.size > 0) parsed.weights = [...weights].sort((a, b) => a - b);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return parsed;
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Regex-extract family name + weight from arbitrary CSS text containing
|
|
59
|
+
* @font-face rules. Used for pasted raw @font-face blocks and for
|
|
60
|
+
* fetch-and-parse of opaque URLs (Typekit, generic).
|
|
61
|
+
*/
|
|
62
|
+
export function parseFontFaceText(css: string): ParsedFamily[] {
|
|
63
|
+
const rules = [...css.matchAll(/@font-face\s*\{([^}]*)\}/g)];
|
|
64
|
+
const byName = new Map<string, { weights: Set<number>; italics: boolean }>();
|
|
65
|
+
for (const [, body] of rules) {
|
|
66
|
+
const nameMatch = body.match(/font-family\s*:\s*([^;]+);/i);
|
|
67
|
+
if (!nameMatch) continue;
|
|
68
|
+
const name = nameMatch[1].trim().replace(/^['"]|['"]$/g, '');
|
|
69
|
+
const weightMatch = body.match(/font-weight\s*:\s*([^;]+);/i);
|
|
70
|
+
const styleMatch = body.match(/font-style\s*:\s*([^;]+);/i);
|
|
71
|
+
|
|
72
|
+
const entry = byName.get(name) ?? { weights: new Set<number>(), italics: false };
|
|
73
|
+
if (weightMatch) {
|
|
74
|
+
const val = weightMatch[1].trim();
|
|
75
|
+
const rangeParts = val.split(/\s+/).map((s) => parseInt(s, 10)).filter((n) => !isNaN(n));
|
|
76
|
+
if (rangeParts.length === 2) {
|
|
77
|
+
const [lo, hi] = rangeParts;
|
|
78
|
+
for (let w = lo; w <= hi; w += 100) entry.weights.add(w);
|
|
79
|
+
} else if (rangeParts.length === 1) {
|
|
80
|
+
entry.weights.add(rangeParts[0]);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (styleMatch && /italic|oblique/i.test(styleMatch[1])) entry.italics = true;
|
|
84
|
+
byName.set(name, entry);
|
|
85
|
+
}
|
|
86
|
+
return [...byName.entries()].map(([name, e]) => ({
|
|
87
|
+
name,
|
|
88
|
+
...(e.weights.size > 0 ? { weights: [...e.weights].sort((a, b) => a - b) } : {}),
|
|
89
|
+
...(e.italics ? { italics: true } : {}),
|
|
90
|
+
}));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Fetch a CSS URL and extract its @font-face families. Used for Typekit kits
|
|
95
|
+
* and generic CSS URLs whose families aren't encoded in the URL itself.
|
|
96
|
+
* Returns null if the fetch fails (CORS or network).
|
|
97
|
+
*/
|
|
98
|
+
export async function fetchAndParseCss(url: string): Promise<ParsedFamily[] | null> {
|
|
99
|
+
try {
|
|
100
|
+
const res = await fetch(url, { method: 'GET' });
|
|
101
|
+
if (!res.ok) return null;
|
|
102
|
+
const text = await res.text();
|
|
103
|
+
const families = parseFontFaceText(text);
|
|
104
|
+
return families.length > 0 ? families : null;
|
|
105
|
+
} catch {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function guessKindFromUrl(url: string): FontSourceKind | null {
|
|
111
|
+
try {
|
|
112
|
+
const host = new URL(url).hostname;
|
|
113
|
+
if (host.includes('fonts.googleapis.com')) return 'google';
|
|
114
|
+
if (host.includes('use.typekit.net') || host.includes('typekit.com')) return 'typekit';
|
|
115
|
+
return 'css-url';
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export function familiesToFontFamilies(sourceId: string, parsed: ParsedFamily[]): FontFamily[] {
|
|
122
|
+
return parsed.map((p) => ({
|
|
123
|
+
id: `${sourceId}:${p.name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')}`,
|
|
124
|
+
name: p.name,
|
|
125
|
+
cssName: `"${p.name}"`,
|
|
126
|
+
...(p.weights ? { weights: p.weights } : {}),
|
|
127
|
+
...(p.italics ? { italics: p.italics } : {}),
|
|
128
|
+
}));
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Given a URL, return a best-effort parsed set of families. For Google URLs,
|
|
133
|
+
* families are derivable from the querystring directly. For other URLs we
|
|
134
|
+
* attempt to fetch-and-parse the CSS. `null` means "unknown — ask the user
|
|
135
|
+
* to name the families manually."
|
|
136
|
+
*/
|
|
137
|
+
export async function discoverFamiliesFromUrl(url: string): Promise<ParsedFamily[] | null> {
|
|
138
|
+
const kind = guessKindFromUrl(url);
|
|
139
|
+
if (!kind) return null;
|
|
140
|
+
if (kind === 'google') {
|
|
141
|
+
const direct = parseGoogleFontsUrl(url);
|
|
142
|
+
if (direct && direct.length > 0) return direct;
|
|
143
|
+
}
|
|
144
|
+
return fetchAndParseCss(url);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function buildSourceFromUrl(url: string, pickedFamilies: ParsedFamily[]): FontSource {
|
|
148
|
+
const kind = guessKindFromUrl(url) ?? 'css-url';
|
|
149
|
+
const id = `src_${kind}_${Math.random().toString(36).slice(2, 10)}`;
|
|
150
|
+
return {
|
|
151
|
+
id,
|
|
152
|
+
kind,
|
|
153
|
+
url,
|
|
154
|
+
label: kind === 'google' ? 'Google Fonts' : kind === 'typekit' ? 'Adobe Typekit' : 'CSS URL',
|
|
155
|
+
families: familiesToFontFamilies(id, pickedFamilies),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function buildSourceFromFontFaceText(css: string, pickedFamilies: ParsedFamily[]): FontSource {
|
|
160
|
+
const id = `src_fontface_${Math.random().toString(36).slice(2, 10)}`;
|
|
161
|
+
return {
|
|
162
|
+
id,
|
|
163
|
+
kind: 'font-face',
|
|
164
|
+
cssText: css,
|
|
165
|
+
label: 'Local @font-face',
|
|
166
|
+
families: familiesToFontFamilies(id, pickedFamilies),
|
|
167
|
+
};
|
|
168
|
+
}
|
package/src/lib/index.ts
CHANGED
|
@@ -1,50 +1,68 @@
|
|
|
1
1
|
export { default as LiveEditorOverlay } from './LiveEditorOverlay.svelte';
|
|
2
|
-
export type { NavLink } from './
|
|
2
|
+
export type { NavLink } from './navLinkTypes';
|
|
3
3
|
export { default as ColumnsOverlay } from './ColumnsOverlay.svelte';
|
|
4
4
|
|
|
5
|
-
export { columnsVisible, toggleColumns } from './columnsOverlay';
|
|
5
|
+
export { columnsVisible, toggleColumns, init as initColumnsOverlay } from './columnsOverlay';
|
|
6
6
|
export { configureEditor, storageKey } from './editorConfig';
|
|
7
|
-
export {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
activeFileName,
|
|
12
|
-
} from './editorConfigStore';
|
|
7
|
+
export { activeFileName } from './editorConfigStore';
|
|
8
|
+
export { init as initRouter, route, navigate } from './router';
|
|
9
|
+
export { init as initCssVarSync } from './cssVarSync';
|
|
10
|
+
export { init as initEditorStore } from './editorStore';
|
|
13
11
|
|
|
14
|
-
export {
|
|
15
|
-
setCssVar,
|
|
16
|
-
removeCssVar,
|
|
17
|
-
applyCssVariables,
|
|
18
|
-
clearAllCssVarOverrides,
|
|
19
|
-
scrapeCssVariables,
|
|
20
|
-
} from './cssVarSync';
|
|
12
|
+
export { setCssVar, removeCssVar } from './cssVarSync';
|
|
21
13
|
|
|
22
14
|
export {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
listThemes,
|
|
16
|
+
loadTheme,
|
|
17
|
+
saveTheme,
|
|
18
|
+
deleteTheme,
|
|
19
|
+
getActiveTheme,
|
|
28
20
|
setActiveFile,
|
|
29
21
|
getProductionInfo,
|
|
30
22
|
setProductionFile,
|
|
31
|
-
listBackups,
|
|
32
|
-
getBackupContent,
|
|
33
|
-
restoreBackup,
|
|
34
|
-
getCurrentCss,
|
|
35
23
|
sanitizeFileName,
|
|
36
|
-
} from './
|
|
37
|
-
export type { ProductionInfo
|
|
24
|
+
} from './themeService';
|
|
25
|
+
export type { ProductionInfo } from './themeService';
|
|
38
26
|
|
|
39
27
|
export type {
|
|
40
28
|
PaletteConfig,
|
|
41
|
-
|
|
42
|
-
|
|
29
|
+
Theme,
|
|
30
|
+
ThemeMeta,
|
|
43
31
|
GradientStyle,
|
|
44
32
|
GradientStop,
|
|
45
|
-
|
|
33
|
+
FontSource,
|
|
34
|
+
FontSourceKind,
|
|
35
|
+
FontFamily,
|
|
36
|
+
FontStack,
|
|
37
|
+
FontStackSlot,
|
|
38
|
+
FontStackVariable,
|
|
39
|
+
SystemCascadePreset,
|
|
40
|
+
GenericFamily,
|
|
41
|
+
Preset,
|
|
42
|
+
PresetMeta,
|
|
43
|
+
} from './themeTypes';
|
|
44
|
+
|
|
45
|
+
export {
|
|
46
|
+
listPresets,
|
|
47
|
+
loadPreset,
|
|
48
|
+
savePreset,
|
|
49
|
+
deletePreset,
|
|
50
|
+
getActivePreset,
|
|
51
|
+
setActivePreset,
|
|
52
|
+
applyPreset,
|
|
53
|
+
captureCurrentAsPreset,
|
|
54
|
+
} from './presetService';
|
|
55
|
+
export type { ApplyPresetResult } from './presetService';
|
|
56
|
+
|
|
57
|
+
export {
|
|
58
|
+
applyFontSources,
|
|
59
|
+
applyFontStacks,
|
|
60
|
+
resolveFontStackValues,
|
|
61
|
+
SYSTEM_CASCADES,
|
|
62
|
+
} from './fontLoader';
|
|
63
|
+
export { migrateThemeFonts, defaultFontSources, defaultFontStacks } from './fontMigration';
|
|
46
64
|
|
|
47
65
|
export { hexToOklch, oklchToHex, gamutClamp } from './oklch';
|
|
48
66
|
export type { Oklch } from './oklch';
|
|
49
67
|
|
|
50
|
-
export {
|
|
68
|
+
export { initializeTheme } from './themeInit';
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// @vitest-environment happy-dom
|
|
2
|
+
import { describe, expect, it, vi, beforeEach } from 'vitest';
|
|
3
|
+
import { get } from 'svelte/store';
|
|
4
|
+
import { configureEditor, storageKey } from './editorConfig';
|
|
5
|
+
import { route, navigate, init as initRouter } from './router';
|
|
6
|
+
|
|
7
|
+
describe('M8 — lazy storage prefix resolution', () => {
|
|
8
|
+
it('storageKey() reflects the prefix at call time, not at module-load time', () => {
|
|
9
|
+
// The default prefix is `lt-`. configureEditor mutates it; subsequent
|
|
10
|
+
// storageKey() calls must observe the new prefix.
|
|
11
|
+
expect(storageKey('editor-state')).toBe('lt-editor-state');
|
|
12
|
+
|
|
13
|
+
configureEditor({ storagePrefix: 'test-' });
|
|
14
|
+
expect(storageKey('editor-state')).toBe('test-editor-state');
|
|
15
|
+
|
|
16
|
+
// Reset for any later tests in the file.
|
|
17
|
+
configureEditor({ storagePrefix: 'lt-' });
|
|
18
|
+
expect(storageKey('editor-state')).toBe('lt-editor-state');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('editorStore persistNow uses the configured prefix at write time', async () => {
|
|
22
|
+
// Reconfigure BEFORE importing editorStore — but since these tests share
|
|
23
|
+
// module state with other suites, we can only verify that getPersistKey
|
|
24
|
+
// behaviour is lazy via the storageKey contract above. The functional
|
|
25
|
+
// round-trip is covered by manual library-import tests.
|
|
26
|
+
configureEditor({ storagePrefix: 'lazy-' });
|
|
27
|
+
expect(storageKey('editor-state')).toBe('lazy-editor-state');
|
|
28
|
+
configureEditor({ storagePrefix: 'lt-' });
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
describe('m9 — navigate() emits exactly one route store update', () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
// Pin a stable starting route. Router.init() is idempotent; calling it
|
|
35
|
+
// here is safe even if some earlier test already initialised it.
|
|
36
|
+
initRouter();
|
|
37
|
+
// Reset history pushState side effects to a clean state by overwriting
|
|
38
|
+
// the current route to '/'.
|
|
39
|
+
history.replaceState(null, '', '/');
|
|
40
|
+
route.set('/');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('produces a single store write per navigate() call', () => {
|
|
44
|
+
const sub = vi.fn();
|
|
45
|
+
const unsub = route.subscribe(sub);
|
|
46
|
+
sub.mockClear(); // drop the immediate-emit on subscribe
|
|
47
|
+
|
|
48
|
+
navigate('/foo');
|
|
49
|
+
expect(sub).toHaveBeenCalledTimes(1);
|
|
50
|
+
expect(get(route)).toBe('/foo');
|
|
51
|
+
|
|
52
|
+
unsub();
|
|
53
|
+
});
|
|
54
|
+
});
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { Migration } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Component-config migration (2026-04-24): unabbreviate component prefixes
|
|
5
|
+
* and apply the per-component suffix rewrites that landed alongside the
|
|
6
|
+
* prefix rename.
|
|
7
|
+
*
|
|
8
|
+
* Prefix layer: `--segment-*` → `--segmentedcontrol-*`, etc. — undoes the
|
|
9
|
+
* abbreviated namespaces that were used briefly before we standardised on
|
|
10
|
+
* full component names.
|
|
11
|
+
*
|
|
12
|
+
* Suffix layer: progressbar's `-track-bg` → `-track-surface`, and
|
|
13
|
+
* inlineeditactions' `-bg-hover` / `-bg` → `-hover-surface` / `-surface`.
|
|
14
|
+
*
|
|
15
|
+
* Whole-key rename: detailnav's only abbreviated token (`--detail-nav-bg`
|
|
16
|
+
* → `--detailnav-surface`) is handled inline.
|
|
17
|
+
*/
|
|
18
|
+
const COMPONENT_PREFIX_RENAMES: Record<string, string> = {
|
|
19
|
+
'--segment-': '--segmentedcontrol-',
|
|
20
|
+
'--collapsible-': '--collapsiblesection-',
|
|
21
|
+
'--progress-': '--progressbar-',
|
|
22
|
+
'--section-divider-': '--sectiondivider-',
|
|
23
|
+
'--radio-': '--radiobutton-',
|
|
24
|
+
'--inline-edit-': '--inlineeditactions-',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const COMPONENT_SUFFIX_RENAMES: Record<string, Array<[string, string]>> = {
|
|
28
|
+
progressbar: [['-track-bg', '-track-surface']],
|
|
29
|
+
inlineeditactions: [
|
|
30
|
+
['-bg-hover', '-hover-surface'],
|
|
31
|
+
['-bg', '-surface'],
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const componentMigration_2026_04_24_prefixAndSuffixRenames: Migration = {
|
|
36
|
+
id: '2026-04-24-component-prefix-and-suffix-renames',
|
|
37
|
+
fromVersion: 0,
|
|
38
|
+
toVersion: 1,
|
|
39
|
+
appliesTo: 'component-config',
|
|
40
|
+
apply(rawVars, meta) {
|
|
41
|
+
const component = meta.component ?? '';
|
|
42
|
+
const out: Record<string, string> = {};
|
|
43
|
+
const suffixRules = COMPONENT_SUFFIX_RENAMES[component] ?? [];
|
|
44
|
+
for (const [oldKey, value] of Object.entries(rawVars)) {
|
|
45
|
+
let key = oldKey;
|
|
46
|
+
// One-off whole-key rename (detailnav's only abbreviated token).
|
|
47
|
+
if (key === '--detail-nav-bg') key = '--detailnav-surface';
|
|
48
|
+
for (const [oldPrefix, newPrefix] of Object.entries(COMPONENT_PREFIX_RENAMES)) {
|
|
49
|
+
if (key.startsWith(oldPrefix)) {
|
|
50
|
+
key = newPrefix + key.slice(oldPrefix.length);
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
for (const [oldSuffix, newSuffix] of suffixRules) {
|
|
55
|
+
if (key.endsWith(oldSuffix)) {
|
|
56
|
+
key = key.slice(0, -oldSuffix.length) + newSuffix;
|
|
57
|
+
break;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (!(key in out)) out[key] = value;
|
|
61
|
+
}
|
|
62
|
+
return out;
|
|
63
|
+
},
|
|
64
|
+
};
|