@motion-proto/live-tokens 0.6.2 → 0.7.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 +14 -13
- package/dist-plugin/index.cjs +147 -136
- package/dist-plugin/index.d.cts +1 -1
- package/dist-plugin/index.d.ts +1 -1
- package/dist-plugin/index.js +145 -135
- package/package.json +25 -40
- package/src/{component-editor → editor/component-editor}/BadgeEditor.svelte +8 -82
- package/src/{component-editor → editor/component-editor}/CalloutEditor.svelte +4 -4
- package/src/{component-editor → editor/component-editor}/CardEditor.svelte +28 -76
- package/src/{component-editor → editor/component-editor}/CollapsibleSectionEditor.svelte +3 -3
- package/src/{component-editor → editor/component-editor}/CornerBadgeEditor.svelte +31 -93
- package/src/{component-editor → editor/component-editor}/DialogEditor.svelte +60 -57
- package/src/editor/component-editor/ImageEditor.svelte +30 -0
- package/src/{component-editor → editor/component-editor}/InlineEditActionsEditor.svelte +6 -4
- package/src/editor/component-editor/MenuSelectEditor.svelte +160 -0
- package/src/{component-editor → editor/component-editor}/NotificationEditor.svelte +64 -37
- package/src/{component-editor → editor/component-editor}/ProgressBarEditor.svelte +5 -4
- package/src/{component-editor → editor/component-editor}/RadioButtonEditor.svelte +3 -3
- package/src/{component-editor → editor/component-editor}/SectionDividerEditor.svelte +57 -84
- package/src/{component-editor → editor/component-editor}/SegmentedControlEditor.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/StandardButtonsEditor.svelte +16 -20
- package/src/{component-editor → editor/component-editor}/TabBarEditor.svelte +9 -14
- package/src/{component-editor → editor/component-editor}/TableEditor.svelte +9 -18
- package/src/{component-editor → editor/component-editor}/TooltipEditor.svelte +11 -47
- package/src/{component-editor → editor/component-editor}/registry.ts +28 -18
- package/src/{component-editor → editor/component-editor}/scaffolding/AngleDial.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentEditorBase.svelte +3 -51
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileManager.svelte +144 -416
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentFileMenu.svelte +18 -170
- package/src/{component-editor → editor/component-editor}/scaffolding/ComponentsTab.svelte +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/CopyFromMenu.svelte +44 -4
- package/src/{component-editor → editor/component-editor}/scaffolding/DividerEditor.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/FieldsetWrapper.svelte +1 -1
- package/src/{component-editor → editor/component-editor}/scaffolding/GradientCard.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkageChart.svelte +6 -6
- package/src/{component-editor → editor/component-editor}/scaffolding/LinkedBlock.svelte +6 -11
- package/src/editor/component-editor/scaffolding/NonStylableConfig.svelte +38 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/SaveAsDialog.svelte +66 -12
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +72 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdropControls.svelte +132 -0
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +257 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/TokenLayout.svelte +9 -7
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +644 -0
- package/src/{component-editor → editor/component-editor}/scaffolding/editorContext.ts +19 -9
- package/src/{component-editor → editor/component-editor}/scaffolding/linkedBlock.ts +2 -2
- package/src/{component-editor → editor/component-editor}/scaffolding/types.ts +14 -0
- package/src/{lib → editor/core/components}/componentConfigService.ts +2 -2
- package/src/{lib → editor/core/components}/componentPersist.ts +5 -5
- package/src/editor/core/flashStatus.ts +30 -0
- package/src/{lib → editor/core/fonts}/fontLoader.ts +2 -2
- package/src/{lib → editor/core/fonts}/fontMigration.ts +4 -4
- package/src/{lib → editor/core/fonts}/fontParse.ts +1 -1
- package/src/editor/core/manifests/manifestService.ts +116 -0
- package/src/{lib → editor/core/palettes}/paletteDerivation.ts +2 -2
- package/src/{lib → editor/core/palettes}/tokenRegistry.ts +5 -5
- package/src/editor/core/productionPulse.ts +37 -0
- package/src/{lib → editor/core/routing}/router.ts +1 -1
- package/src/{lib/files/versionedFileResource.ts → editor/core/storage/files/versionedFileResourceClient.ts} +8 -1
- package/src/{lib → editor/core/store}/editorCore.ts +24 -8
- package/src/{lib → editor/core/store}/editorPersistence.ts +3 -3
- package/src/{lib → editor/core/store}/editorRenderer.ts +2 -2
- package/src/{lib → editor/core/store}/editorStore.ts +17 -17
- package/src/{lib → editor/core/store}/editorTypes.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/columns.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/components.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/fonts.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/gradients.ts +2 -2
- package/src/{lib → editor/core/themes}/slices/overlays.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/palettes.ts +1 -1
- package/src/{lib → editor/core/themes}/slices/shadows.ts +3 -3
- package/src/{lib → editor/core/themes}/themeInit.ts +6 -6
- package/src/{lib → editor/core/themes}/themeService.ts +6 -6
- package/src/{lib → editor/core/themes}/themeTypes.ts +11 -7
- package/src/editor/index.ts +69 -0
- package/src/{lib → editor/overlay}/LiveEditorOverlay.svelte +79 -125
- package/src/{lib → editor/overlay}/columnsOverlay.ts +2 -2
- package/src/{pages → editor/pages}/ComponentEditorPage.svelte +12 -12
- package/src/{pages → editor/pages}/Editor.svelte +4 -4
- package/src/{pages → editor/pages}/EditorShell.svelte +18 -36
- package/src/{styles → editor/styles}/ui-editor.css +41 -21
- package/src/{styles → editor/styles}/ui-form-controls.css +8 -8
- package/src/{ui → editor/ui}/BezierCurveEditor.svelte +8 -8
- package/src/{ui → editor/ui}/ColorEditPanel.svelte +13 -13
- package/src/{ui → editor/ui}/EditorViewSwitcher.svelte +8 -6
- package/src/editor/ui/FileLoadList.svelte +350 -0
- package/src/editor/ui/FilePill.svelte +80 -0
- package/src/{ui → editor/ui}/FontStackEditor.svelte +7 -7
- package/src/{ui → editor/ui}/GradientEditor.svelte +11 -11
- package/src/{ui → editor/ui}/GradientStopPicker.svelte +1 -1
- package/src/editor/ui/ManifestFileManager.svelte +371 -0
- package/src/{ui → editor/ui}/PaletteEditor.svelte +132 -598
- package/src/{ui → editor/ui}/ProjectFontsSection.svelte +102 -144
- package/src/{ui → editor/ui}/SurfacesTab.svelte +3 -3
- package/src/{ui → editor/ui}/TextTab.svelte +3 -3
- package/src/{ui → editor/ui}/ThemeFileManager.svelte +286 -519
- package/src/{ui → editor/ui}/UICopyPopover.svelte +4 -4
- package/src/{ui → editor/ui}/UIFontFamilySelector.svelte +6 -6
- package/src/{ui → editor/ui}/UIFontSizeSelector.svelte +1 -1
- package/src/editor/ui/UIInfoPopover.svelte +244 -0
- package/src/{ui → editor/ui}/UILineHeightSelector.svelte +5 -5
- package/src/{ui → editor/ui}/UILinkToggle.svelte +2 -2
- package/src/{ui → editor/ui}/UIPaddingSelector.svelte +6 -6
- package/src/{ui → editor/ui}/UIPaletteSelector.svelte +26 -26
- package/src/editor/ui/UIPillButton.svelte +138 -0
- package/src/{ui → editor/ui}/UIRadio.svelte +2 -2
- package/src/{ui → editor/ui}/UIRelinkConfirmPopover.svelte +4 -4
- package/src/editor/ui/UISquareButton.svelte +172 -0
- package/src/{ui → editor/ui}/UITokenSelector.svelte +10 -10
- package/src/{ui → editor/ui}/UIVariantSelector.svelte +1 -1
- package/src/{ui → editor/ui}/VariablesTab.svelte +31 -8
- package/src/{ui → editor/ui}/palette/GradientStopEditor.svelte +13 -13
- package/src/{ui → editor/ui}/palette/OverridesPanel.svelte +13 -13
- package/src/{ui → editor/ui}/palette/PaletteBase.svelte +8 -5
- package/src/{ui → editor/ui}/palette/paletteEditorState.ts +1 -1
- package/src/editor/ui/palette/paletteMath.ts +275 -0
- package/src/{ui → editor/ui}/sections/ColumnsSection.svelte +137 -17
- package/src/{ui → editor/ui}/sections/GradientsSection.svelte +7 -7
- package/src/{ui → editor/ui}/sections/OverlaysSection.svelte +17 -17
- package/src/{ui → editor/ui}/sections/ShadowsSection.svelte +22 -22
- package/src/{ui → editor/ui}/sections/TokenScaleTable.svelte +3 -3
- package/src/{components → system/components}/Badge.svelte +0 -36
- package/src/{components → system/components}/Card.svelte +8 -62
- package/src/{components → system/components}/CornerBadge.svelte +8 -24
- package/src/{components → system/components}/Dialog.svelte +1 -1
- package/src/system/components/FloatingTokenTags.css +256 -0
- package/src/system/components/FloatingTokenTags.svelte +592 -0
- package/src/{components → system/components}/InlineEditActions.svelte +6 -4
- package/src/system/components/MenuSelect.svelte +229 -0
- package/src/{components → system/components}/ProgressBar.svelte +29 -11
- package/src/{components → system/components}/SegmentedControl.svelte +49 -43
- package/src/{components → system/components}/TabBar.svelte +81 -65
- package/src/{components → system/components}/Table.svelte +17 -3
- package/src/{components → system/components}/Tooltip.svelte +6 -4
- package/src/system/styles/CONVENTIONS.md +178 -0
- package/src/{styles → system/styles}/fonts.css +6 -3
- package/src/{styles → system/styles}/tokens.css +149 -29
- package/src/component-editor/ImageEditor.svelte +0 -74
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +0 -62
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +0 -37
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +0 -61
- package/src/component-editor/scaffolding/StateBlock.svelte +0 -132
- package/src/component-editor/scaffolding/VariantGroup.svelte +0 -310
- package/src/data/google-fonts.json +0 -75
- package/src/lib/index.ts +0 -68
- package/src/lib/presetService.ts +0 -214
- package/src/lib/productionPulse.ts +0 -32
- package/src/ui/PresetFileManager.svelte +0 -1116
- package/src/ui/UnsavedComponentsDialog.svelte +0 -315
- /package/src/{styles → app}/site.css +0 -0
- /package/src/{component-editor → editor/component-editor}/index.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/DemoHeader.svelte +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/TypeEditor.svelte +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/buildTypeGroupTokens.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSectionType.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/componentSources.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/defaultSections.ts +0 -0
- /package/src/{component-editor → editor/component-editor}/scaffolding/siblings.ts +0 -0
- /package/src/{lib → editor/core/components}/componentConfigKeys.ts +0 -0
- /package/src/{lib → editor/core}/cssVarSync.ts +0 -0
- /package/src/{lib → editor/core/palettes}/oklch.ts +0 -0
- /package/src/{lib → editor/core/routing}/navLinkTypes.ts +0 -0
- /package/src/{lib → editor/core/routing}/parentRouteStore.ts +0 -0
- /package/src/{lib → editor/core/storage}/storage.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfig.ts +0 -0
- /package/src/{lib → editor/core/store}/editorConfigStore.ts +0 -0
- /package/src/{lib → editor/core/store}/editorKeybindings.ts +0 -0
- /package/src/{lib → editor/core/store}/editorViewStore.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-component-prefix-and-suffix-renames.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-24-legacy-keys-and-bg-to-canvas.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-04-27-segmentedcontrol-disabled-flatten.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-frame-and-cleanup.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-08-collapsiblesection-variant-namespace.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-10-sectiondivider-gradient-stops.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/2026-05-13-primary-to-brand.ts +0 -0
- /package/src/{lib → editor/core/themes}/migrations/index.ts +0 -0
- /package/src/{lib → editor/core/themes}/parsers/globalRootBlock.ts +0 -0
- /package/src/{lib → editor/core/themes}/slices/domainVars.ts +0 -0
- /package/src/{lib → editor/overlay}/ColumnsOverlay.svelte +0 -0
- /package/src/{lib → editor/overlay}/overlayState.ts +0 -0
- /package/src/{pages → editor/pages}/ComponentEditorPage.svelte.d.ts +0 -0
- /package/src/{pages → editor/pages}/Editor.svelte.d.ts +0 -0
- /package/src/{ui → editor/ui}/Toggle.svelte +0 -0
- /package/src/{ui → editor/ui}/UIDialog.svelte +0 -0
- /package/src/{ui → editor/ui}/UIFontWeightSelector.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionItem.svelte +0 -0
- /package/src/{ui → editor/ui}/UIOptionList.svelte +0 -0
- /package/src/{ui → editor/ui}/UIRadioGroup.svelte +0 -0
- /package/src/{lib → editor/ui}/copyPopover.ts +0 -0
- /package/src/{ui → editor/ui}/curveEngine.ts +0 -0
- /package/src/{ui → editor/ui}/index.ts +0 -0
- /package/src/{ui → editor/ui}/keepInViewport.ts +0 -0
- /package/src/{ui → editor/ui}/palette/ScaleCurveEditor.svelte +0 -0
- /package/src/{lib → editor/ui}/scrollSection.ts +0 -0
- /package/src/{ui → editor/ui}/sections/tokenScales.ts +0 -0
- /package/src/{ui → editor/ui}/variantScales.ts +0 -0
- /package/src/{assets → system/assets}/newspaper.webp +0 -0
- /package/src/{assets → system/assets}/offering.webp +0 -0
- /package/src/{components → system/components}/Button.svelte +0 -0
- /package/src/{components → system/components}/Callout.svelte +0 -0
- /package/src/{components → system/components}/CollapsibleSection.svelte +0 -0
- /package/src/{components → system/components}/Image.svelte +0 -0
- /package/src/{components → system/components}/Notification.svelte +0 -0
- /package/src/{components → system/components}/RadioButton.svelte +0 -0
- /package/src/{components → system/components}/SectionDivider.svelte +0 -0
- /package/src/{components → system/components}/types.ts +0 -0
- /package/src/{styles → system/styles}/_padding.scss +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-italic-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Fraunces/Fraunces-roman-latin.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin-ext.woff2 +0 -0
- /package/src/{styles → system/styles}/fonts/Manrope/Manrope-latin.woff2 +0 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { writable } from 'svelte/store';
|
|
3
|
+
import type { Snippet } from 'svelte';
|
|
4
|
+
import TokenLayout from './TokenLayout.svelte';
|
|
5
|
+
import StateBlock from './StateBlock.svelte';
|
|
6
|
+
import CopyFromMenu from './CopyFromMenu.svelte';
|
|
7
|
+
import ShadowBackdrop from './ShadowBackdrop.svelte';
|
|
8
|
+
import ShadowBackdropControls from './ShadowBackdropControls.svelte';
|
|
9
|
+
import { mutate } from '../../core/store/editorStore';
|
|
10
|
+
import { getDeclaredValue } from '../../core/palettes/tokenRegistry';
|
|
11
|
+
import type { CssVarRef } from '../../core/store/editorTypes';
|
|
12
|
+
import { getEditorContext } from './editorContext';
|
|
13
|
+
import type { Token, TypeGroupConfig } from './types';
|
|
14
|
+
import type { Sibling } from './siblings';
|
|
15
|
+
|
|
16
|
+
interface Props {
|
|
17
|
+
name: string;
|
|
18
|
+
title: string;
|
|
19
|
+
tokens?: Token[];
|
|
20
|
+
states?: Record<string, Token[]> | null;
|
|
21
|
+
typeGroups?: Record<string, TypeGroupConfig[]>;
|
|
22
|
+
/** When set, overrides are read from and cleared through the editor store. */
|
|
23
|
+
component?: string | undefined;
|
|
24
|
+
siblings?: Sibling[];
|
|
25
|
+
columns?: number;
|
|
26
|
+
/** Defaults to "Element" because most strips mix structural parts with states. */
|
|
27
|
+
selectorLabel?: string;
|
|
28
|
+
onchange?: () => void;
|
|
29
|
+
children?: Snippet<[{ activeState: string }]>;
|
|
30
|
+
stateActions?: Snippet<[string]>;
|
|
31
|
+
previewActions?: Snippet;
|
|
32
|
+
compositeControls?: Snippet<[string]>;
|
|
33
|
+
/** Each child should be a `.property-row` to match the token grid above. */
|
|
34
|
+
extraPropertyRows?: Snippet<[string]>;
|
|
35
|
+
/** Extra sections appended below the Background controls inside the canvas
|
|
36
|
+
toolbar. Use `.canvas-toolbar-eyebrow` for section headings to match
|
|
37
|
+
Background. Lets per-instance display knobs (anchor, alignment, etc.)
|
|
38
|
+
live with the canvas rather than in a separate config block above. */
|
|
39
|
+
canvasToolbarExtras?: Snippet;
|
|
40
|
+
/** Skip the default centered, padded stage when the editor brings its own backdrop. */
|
|
41
|
+
unboxedPreview?: boolean;
|
|
42
|
+
backdropPadding?: string;
|
|
43
|
+
backdropModes?: ReadonlyArray<'default' | 'image' | 'color'>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let {
|
|
47
|
+
name,
|
|
48
|
+
title,
|
|
49
|
+
tokens = [],
|
|
50
|
+
states = null,
|
|
51
|
+
typeGroups = {},
|
|
52
|
+
component = undefined,
|
|
53
|
+
siblings = [],
|
|
54
|
+
columns = 1,
|
|
55
|
+
selectorLabel = 'Element',
|
|
56
|
+
onchange,
|
|
57
|
+
children,
|
|
58
|
+
stateActions,
|
|
59
|
+
previewActions,
|
|
60
|
+
compositeControls,
|
|
61
|
+
extraPropertyRows,
|
|
62
|
+
canvasToolbarExtras,
|
|
63
|
+
unboxedPreview = false,
|
|
64
|
+
backdropPadding,
|
|
65
|
+
backdropModes,
|
|
66
|
+
}: Props = $props();
|
|
67
|
+
|
|
68
|
+
let bgMode: 'default' | 'image' | 'color' = $state('default');
|
|
69
|
+
let bgVar = $derived(`--backdrop-${component ?? name}-surface`);
|
|
70
|
+
|
|
71
|
+
const editorCtx = getEditorContext();
|
|
72
|
+
const linkedOrderStore = editorCtx?.linkedOrder ?? writable<Map<string, number> | null>(null);
|
|
73
|
+
const focusedVariantStore = editorCtx?.focusedVariant ?? writable<string | null>(null);
|
|
74
|
+
const focusedStateStore = editorCtx?.focusedState ?? writable<string | null>(null);
|
|
75
|
+
const variantsStore = editorCtx?.variants ?? writable<{ value: string; label: string }[]>([]);
|
|
76
|
+
const preserveColorFamilyStore = editorCtx?.preserveColorFamily ?? writable(false);
|
|
77
|
+
let linkedOrder = $derived($linkedOrderStore ?? undefined);
|
|
78
|
+
|
|
79
|
+
let activeTab: string = $state('');
|
|
80
|
+
|
|
81
|
+
const TYPE_PROPS = ['colorVariable', 'familyVariable', 'sizeVariable', 'weightVariable', 'lineHeightVariable', 'outlineWidthVariable', 'outlineColorVariable'] as const;
|
|
82
|
+
// Carry per-side derived vars so split padding fully transfers; no-op when absent.
|
|
83
|
+
const PADDING_SIDES = ['top', 'right', 'bottom', 'left'] as const;
|
|
84
|
+
|
|
85
|
+
/** A token whose label contains "color" describes a color property. The
|
|
86
|
+
copy-from "Preserve color families" toggle skips these so the destination
|
|
87
|
+
keeps its existing palette family (e.g. button-primary stays on `brand`)
|
|
88
|
+
while still picking up shape/typography from the source variant. */
|
|
89
|
+
function isColorToken(t: Token): boolean {
|
|
90
|
+
return t.label.toLowerCase().includes('color');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Parse the right-hand side of a CSS declaration (e.g. `var(--surface-accent-lowest)`
|
|
94
|
+
or `#ff0000`) into a CssVarRef. Returns null for empty/unparseable input.
|
|
95
|
+
Used by copy-from when the source has no explicit override — we resolve
|
|
96
|
+
its declared default so the destination visually matches the source
|
|
97
|
+
instead of falling back to its own family's default. */
|
|
98
|
+
function declaredToRef(declared: string | null): CssVarRef | null {
|
|
99
|
+
if (!declared) return null;
|
|
100
|
+
const m = declared.match(/^\s*var\((--[a-z0-9-]+)\)\s*$/i);
|
|
101
|
+
if (m) return { kind: 'token', name: m[1] };
|
|
102
|
+
return { kind: 'literal', value: declared };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Extract a transparency percentage from a `color-mix(in srgb, var(--X) N%, transparent)`
|
|
106
|
+
wrapper, optionally itself wrapped in `var(...)`. Returns null if no
|
|
107
|
+
such wrapper is present. */
|
|
108
|
+
const ALPHA_RE = /color-mix\s*\(\s*in\s+srgb\s*,\s*var\(--[a-z0-9-]+\)\s+([\d.]+%)\s*,\s*transparent\s*\)/i;
|
|
109
|
+
function extractAlpha(value: string): string | null {
|
|
110
|
+
const m = value.match(ALPHA_RE);
|
|
111
|
+
return m ? m[1] : null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Extract the inner var() base reference, whether the value is a bare
|
|
115
|
+
`var(--X)`, or wrapped by a transparency color-mix, or both. */
|
|
116
|
+
function extractBaseToken(value: string): string | null {
|
|
117
|
+
const mix = value.match(/color-mix\s*\(\s*in\s+srgb\s*,\s*var\((--[a-z0-9-]+)\)/i);
|
|
118
|
+
if (mix) return mix[1];
|
|
119
|
+
const bare = value.match(/var\((--[a-z0-9-]+)\)/i);
|
|
120
|
+
return bare ? bare[1] : null;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** TypeGroup props whose values are color tokens; preserve-color-families
|
|
124
|
+
skips these in the typeGroups copy loop. */
|
|
125
|
+
const COLOR_TYPE_PROPS = new Set(['colorVariable', 'outlineColorVariable']);
|
|
126
|
+
|
|
127
|
+
function pickCopySource(toState: string, fromVariant: string, fromState: string) {
|
|
128
|
+
const preserveColorFamily = $preserveColorFamilyStore;
|
|
129
|
+
if (!component || !states) return;
|
|
130
|
+
const isSelfVariant = fromVariant === name;
|
|
131
|
+
const sibling = isSelfVariant ? null : siblings.find((s) => s.name === fromVariant);
|
|
132
|
+
if (!isSelfVariant && !sibling) return;
|
|
133
|
+
const srcTokens = (isSelfVariant ? states[fromState] : sibling!.states[fromState]) ?? [];
|
|
134
|
+
const dstTokens = states[toState] ?? [];
|
|
135
|
+
const srcTypeGroups = (isSelfVariant ? typeGroups[fromState] : sibling!.typeGroups?.[fromState]) ?? [];
|
|
136
|
+
const dstTypeGroups = typeGroups[toState] ?? [];
|
|
137
|
+
|
|
138
|
+
mutate(`copy ${fromVariant}/${fromState} → ${name}/${toState}`, (s) => {
|
|
139
|
+
const slice = s.components[component!] ?? (s.components[component!] = { activeFile: 'default', aliases: {}, config: {} });
|
|
140
|
+
const dstVarsTouched: string[] = [];
|
|
141
|
+
/** Resolve a variable's effective value as a CSS string: the override if
|
|
142
|
+
set, otherwise its declared default. Returns null if neither exists. */
|
|
143
|
+
const effectiveValue = (varName: string): string | null => {
|
|
144
|
+
const ref = slice.aliases[varName];
|
|
145
|
+
if (ref) return ref.kind === 'token' ? `var(${ref.name})` : ref.value;
|
|
146
|
+
return getDeclaredValue(varName);
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const apply = (srcVar: string, dstVar: string) => {
|
|
150
|
+
if (srcVar === dstVar) return;
|
|
151
|
+
if (srcVar in slice.aliases) {
|
|
152
|
+
slice.aliases[dstVar] = slice.aliases[srcVar];
|
|
153
|
+
} else {
|
|
154
|
+
// Src is at its declared default. Materialize that default as dst's
|
|
155
|
+
// override so dst visually picks up src's value — otherwise dst
|
|
156
|
+
// falls back to its OWN family default and the copy is a visual no-op.
|
|
157
|
+
const ref = declaredToRef(getDeclaredValue(srcVar));
|
|
158
|
+
if (ref) slice.aliases[dstVar] = ref;
|
|
159
|
+
else delete slice.aliases[dstVar];
|
|
160
|
+
}
|
|
161
|
+
dstVarsTouched.push(dstVar);
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
/** Preserve-color-families variant of apply: copy src's transparency
|
|
165
|
+
wrapper (if any) over dst's existing base color so dst keeps its
|
|
166
|
+
palette family but picks up src's alpha. Source with no alpha = no-op
|
|
167
|
+
(leaves dst's color untouched). */
|
|
168
|
+
const applyColorPreserve = (srcVar: string, dstVar: string) => {
|
|
169
|
+
const srcVal = effectiveValue(srcVar);
|
|
170
|
+
if (!srcVal) return;
|
|
171
|
+
const alpha = extractAlpha(srcVal);
|
|
172
|
+
if (!alpha) return;
|
|
173
|
+
const dstVal = effectiveValue(dstVar);
|
|
174
|
+
const dstBase = dstVal ? extractBaseToken(dstVal) : null;
|
|
175
|
+
if (!dstBase) return;
|
|
176
|
+
slice.aliases[dstVar] = {
|
|
177
|
+
kind: 'literal',
|
|
178
|
+
value: `color-mix(in srgb, var(${dstBase}) ${alpha}, transparent)`,
|
|
179
|
+
};
|
|
180
|
+
dstVarsTouched.push(dstVar);
|
|
181
|
+
};
|
|
182
|
+
const minLen = Math.min(srcTokens.length, dstTokens.length);
|
|
183
|
+
for (let i = 0; i < minLen; i++) {
|
|
184
|
+
const srcVar = srcTokens[i].variable;
|
|
185
|
+
const dstVar = dstTokens[i].variable;
|
|
186
|
+
if (preserveColorFamily && isColorToken(srcTokens[i])) {
|
|
187
|
+
applyColorPreserve(srcVar, dstVar);
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
apply(srcVar, dstVar);
|
|
191
|
+
if (srcTokens[i].splittable !== false) {
|
|
192
|
+
for (const side of PADDING_SIDES) apply(`${srcVar}-${side}`, `${dstVar}-${side}`);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
const minTypeGroups = Math.min(srcTypeGroups.length, dstTypeGroups.length);
|
|
196
|
+
for (let g = 0; g < minTypeGroups; g++) {
|
|
197
|
+
const srcType = srcTypeGroups[g];
|
|
198
|
+
const dstType = dstTypeGroups[g];
|
|
199
|
+
for (const prop of TYPE_PROPS) {
|
|
200
|
+
const srcVar = srcType[prop];
|
|
201
|
+
const dstVar = dstType[prop];
|
|
202
|
+
if (!srcVar || !dstVar) continue;
|
|
203
|
+
if (preserveColorFamily && COLOR_TYPE_PROPS.has(prop)) {
|
|
204
|
+
applyColorPreserve(srcVar, dstVar);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
apply(srcVar, dstVar);
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
// Copy intent "make this state match" clears stale unlinked markers on touched vars.
|
|
211
|
+
if (slice.unlinked && slice.unlinked.length > 0) {
|
|
212
|
+
const touched = new Set(dstVarsTouched);
|
|
213
|
+
const remaining = slice.unlinked.filter((v) => !touched.has(v));
|
|
214
|
+
if (remaining.length === 0) delete slice.unlinked;
|
|
215
|
+
else slice.unlinked = remaining;
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
let copySources = $derived.by(() => {
|
|
221
|
+
const fromSiblings = siblings.map((s) => ({
|
|
222
|
+
name: s.name,
|
|
223
|
+
label: s.label,
|
|
224
|
+
states: Object.keys(s.states),
|
|
225
|
+
}));
|
|
226
|
+
const ownStates = states ? Object.keys(states) : [];
|
|
227
|
+
if (ownStates.length >= 2) {
|
|
228
|
+
return [{ name, label: title, states: ownStates }, ...fromSiblings];
|
|
229
|
+
}
|
|
230
|
+
return fromSiblings;
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
let stateNames = $derived(states ? Object.keys(states) : []);
|
|
234
|
+
let tabsStripVisible = $derived(stateNames.length >= 2);
|
|
235
|
+
$effect(() => {
|
|
236
|
+
if (stateNames.length > 0 && !stateNames.includes(activeTab)) {
|
|
237
|
+
activeTab = stateNames[0];
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Adopt cross-group state hint from chart row clicks.
|
|
241
|
+
$effect(() => {
|
|
242
|
+
if ($focusedStateStore && stateNames.includes($focusedStateStore) && activeTab !== $focusedStateStore) {
|
|
243
|
+
activeTab = $focusedStateStore;
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
let inFocusMode = $derived(siblings.length > 0);
|
|
248
|
+
let amIFocused = $derived($focusedVariantStore === name);
|
|
249
|
+
let shouldRender = $derived(!inFocusMode || amIFocused);
|
|
250
|
+
let showVariantTabs = $derived(inFocusMode && $variantsStore.length >= 2);
|
|
251
|
+
// Mirror state-tab clicks to the shared store so linked-block + chart track them.
|
|
252
|
+
$effect(() => {
|
|
253
|
+
if (amIFocused && activeTab && stateNames.includes(activeTab) && $focusedStateStore !== activeTab) {
|
|
254
|
+
focusedStateStore.set(activeTab);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
</script>
|
|
259
|
+
|
|
260
|
+
{#if shouldRender}
|
|
261
|
+
<div class="editor-section-card demo-section variant-group">
|
|
262
|
+
{#if states}
|
|
263
|
+
<div class="tabs-preview">
|
|
264
|
+
<div class="preview-header">
|
|
265
|
+
<span class="editor-section-title">Preview</span>
|
|
266
|
+
{#if showVariantTabs}
|
|
267
|
+
<div class="variant-tabs" role="tablist">
|
|
268
|
+
{#each $variantsStore as opt (opt.value)}
|
|
269
|
+
<button
|
|
270
|
+
type="button"
|
|
271
|
+
class="variant-tab-btn"
|
|
272
|
+
class:active={opt.value === $focusedVariantStore}
|
|
273
|
+
role="tab"
|
|
274
|
+
aria-selected={opt.value === $focusedVariantStore}
|
|
275
|
+
onclick={() => focusedVariantStore.set(opt.value)}
|
|
276
|
+
>{opt.label}</button>
|
|
277
|
+
{/each}
|
|
278
|
+
</div>
|
|
279
|
+
{/if}
|
|
280
|
+
{#if previewActions}
|
|
281
|
+
<div class="preview-actions">
|
|
282
|
+
{@render previewActions?.()}
|
|
283
|
+
</div>
|
|
284
|
+
{/if}
|
|
285
|
+
</div>
|
|
286
|
+
{#if unboxedPreview}
|
|
287
|
+
{@render children?.({ activeState: activeTab })}
|
|
288
|
+
{:else}
|
|
289
|
+
<ShadowBackdrop mode={bgMode} colorVariable={bgVar} padding={backdropPadding}>
|
|
290
|
+
{#snippet controls()}
|
|
291
|
+
<div class="canvas-toolbar">
|
|
292
|
+
<span class="canvas-toolbar-eyebrow">Background</span>
|
|
293
|
+
<ShadowBackdropControls bind:mode={bgMode} colorVariable={bgVar} modes={backdropModes ?? ['default', 'image', 'color']} />
|
|
294
|
+
{@render canvasToolbarExtras?.()}
|
|
295
|
+
</div>
|
|
296
|
+
{/snippet}
|
|
297
|
+
{@render children?.({ activeState: activeTab })}
|
|
298
|
+
</ShadowBackdrop>
|
|
299
|
+
{/if}
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
{#if tabsStripVisible}
|
|
303
|
+
<div class="tabs-states-block">
|
|
304
|
+
<span class="editor-subsection-title">{selectorLabel}</span>
|
|
305
|
+
<div class="tabs-selectors">
|
|
306
|
+
<div class="state-tabs" role="tablist">
|
|
307
|
+
{#each stateNames as s}
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
class="state-tab-btn"
|
|
311
|
+
class:active={activeTab === s}
|
|
312
|
+
role="tab"
|
|
313
|
+
aria-selected={activeTab === s}
|
|
314
|
+
onclick={() => { activeTab = s; focusedStateStore.set(s); }}
|
|
315
|
+
>{s}</button>
|
|
316
|
+
{/each}
|
|
317
|
+
</div>
|
|
318
|
+
{#if activeTab}
|
|
319
|
+
{@render stateActions?.(activeTab)}
|
|
320
|
+
{/if}
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
{/if}
|
|
324
|
+
|
|
325
|
+
{#if activeTab && states[activeTab]}
|
|
326
|
+
{@const stateName = activeTab}
|
|
327
|
+
{@render compositeControls?.(stateName)}
|
|
328
|
+
<div class="properties-header">
|
|
329
|
+
<span class="editor-subsection-title properties-title">Properties</span>
|
|
330
|
+
{#if !tabsStripVisible && stateActions}
|
|
331
|
+
{@render stateActions(activeTab)}
|
|
332
|
+
{/if}
|
|
333
|
+
{#if copySources.length > 0}
|
|
334
|
+
<CopyFromMenu
|
|
335
|
+
toState={activeTab}
|
|
336
|
+
variantName={name}
|
|
337
|
+
{copySources}
|
|
338
|
+
onselect={(d) => pickCopySource(activeTab, d.fromVariant, d.fromState)}
|
|
339
|
+
/>
|
|
340
|
+
{/if}
|
|
341
|
+
</div>
|
|
342
|
+
<StateBlock
|
|
343
|
+
tokens={states[stateName]}
|
|
344
|
+
typeGroups={typeGroups[stateName] ?? []}
|
|
345
|
+
{component}
|
|
346
|
+
{linkedOrder}
|
|
347
|
+
{columns}
|
|
348
|
+
{onchange}
|
|
349
|
+
/>
|
|
350
|
+
{#if extraPropertyRows}
|
|
351
|
+
<div class="extra-property-rows">
|
|
352
|
+
{@render extraPropertyRows(stateName)}
|
|
353
|
+
</div>
|
|
354
|
+
{/if}
|
|
355
|
+
{/if}
|
|
356
|
+
{:else}
|
|
357
|
+
{#if unboxedPreview}
|
|
358
|
+
{@render children?.({ activeState: '' })}
|
|
359
|
+
{:else}
|
|
360
|
+
<ShadowBackdrop mode={bgMode} colorVariable={bgVar} padding={backdropPadding}>
|
|
361
|
+
{@render children?.({ activeState: '' })}
|
|
362
|
+
</ShadowBackdrop>
|
|
363
|
+
{/if}
|
|
364
|
+
<TokenLayout
|
|
365
|
+
title={name}
|
|
366
|
+
tokens={tokens}
|
|
367
|
+
{component}
|
|
368
|
+
{linkedOrder}
|
|
369
|
+
{onchange}
|
|
370
|
+
/>
|
|
371
|
+
{/if}
|
|
372
|
+
|
|
373
|
+
</div>
|
|
374
|
+
{/if}
|
|
375
|
+
|
|
376
|
+
<style>
|
|
377
|
+
/* Card chrome lives on .editor-section-card in ui-editor.css. */
|
|
378
|
+
.variant-group {
|
|
379
|
+
gap: var(--ui-space-12);
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.tabs-preview {
|
|
383
|
+
display: flex;
|
|
384
|
+
flex-direction: column;
|
|
385
|
+
gap: var(--ui-space-20);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
.preview-header {
|
|
389
|
+
display: flex;
|
|
390
|
+
align-items: center;
|
|
391
|
+
gap: var(--ui-space-24);
|
|
392
|
+
flex-wrap: wrap;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
.preview-actions {
|
|
396
|
+
margin-left: auto;
|
|
397
|
+
display: inline-flex;
|
|
398
|
+
align-items: center;
|
|
399
|
+
gap: var(--ui-space-8);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/* Sits in the backdrop's right-rail column (ShadowBackdrop owns the two-column
|
|
403
|
+
split). Own border + raised surface so it reads as a distinct panel inside
|
|
404
|
+
the backdrop, regardless of which backdrop mode is active. */
|
|
405
|
+
.canvas-toolbar {
|
|
406
|
+
width: 11rem;
|
|
407
|
+
height: 100%;
|
|
408
|
+
display: flex;
|
|
409
|
+
flex-direction: column;
|
|
410
|
+
gap: var(--ui-space-12);
|
|
411
|
+
padding: var(--ui-space-10) var(--ui-space-12);
|
|
412
|
+
background: var(--ui-surface-low);
|
|
413
|
+
border: 1px solid var(--ui-border-low);
|
|
414
|
+
border-radius: var(--ui-radius-md);
|
|
415
|
+
color: var(--ui-text-primary);
|
|
416
|
+
box-sizing: border-box;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
.canvas-toolbar :global(.canvas-toolbar-eyebrow) {
|
|
420
|
+
font-size: var(--ui-font-size-xs);
|
|
421
|
+
font-weight: var(--ui-font-weight-medium);
|
|
422
|
+
color: var(--ui-text-tertiary);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/* Divider lives *between* sections, not under each eyebrow. First eyebrow
|
|
426
|
+
has no preceding sibling and stays flush. */
|
|
427
|
+
.canvas-toolbar > :global(* + .canvas-toolbar-eyebrow) {
|
|
428
|
+
padding-top: var(--ui-space-12);
|
|
429
|
+
border-top: 1px solid var(--ui-border-low);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/* Explicit separator for label-less sections in canvasToolbarExtras. */
|
|
433
|
+
.canvas-toolbar :global(.canvas-toolbar-divider) {
|
|
434
|
+
margin: 0;
|
|
435
|
+
border: 0;
|
|
436
|
+
border-top: 1px solid var(--ui-border-low);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/* Native <select> styled to match the property-row trigger chrome
|
|
440
|
+
(UITokenSelector) so toolbar selects don't read as a separate visual
|
|
441
|
+
vocabulary from the rest of the editor. */
|
|
442
|
+
.canvas-toolbar :global(.canvas-toolbar-select) {
|
|
443
|
+
width: 100%;
|
|
444
|
+
appearance: none;
|
|
445
|
+
-webkit-appearance: none;
|
|
446
|
+
padding: 0 var(--ui-space-24) 0 var(--ui-space-8);
|
|
447
|
+
min-height: 1.75rem;
|
|
448
|
+
background-color: var(--ui-surface-low);
|
|
449
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10' viewBox='0 0 12 12'%3E%3Cpath fill='none' stroke='%23999' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round' d='M3 5l3 3 3-3'/%3E%3C/svg%3E");
|
|
450
|
+
background-repeat: no-repeat;
|
|
451
|
+
background-position: right var(--ui-space-8) center;
|
|
452
|
+
border: 1px solid var(--ui-border);
|
|
453
|
+
border-radius: var(--ui-radius-md);
|
|
454
|
+
color: var(--ui-text-primary);
|
|
455
|
+
font-family: var(--ui-font-sans);
|
|
456
|
+
font-size: var(--ui-font-size-sm);
|
|
457
|
+
cursor: pointer;
|
|
458
|
+
transition: background-color var(--ui-transition-fast), border-color var(--ui-transition-fast);
|
|
459
|
+
}
|
|
460
|
+
.canvas-toolbar :global(.canvas-toolbar-select:hover) {
|
|
461
|
+
background-color: var(--ui-surface-high);
|
|
462
|
+
border-color: var(--ui-border-higher);
|
|
463
|
+
}
|
|
464
|
+
.canvas-toolbar :global(.canvas-toolbar-select:focus-visible) {
|
|
465
|
+
outline: 2px solid var(--ui-highlight);
|
|
466
|
+
outline-offset: 2px;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/* Native <input> styled to match the toolbar's select chrome. */
|
|
470
|
+
.canvas-toolbar :global(.canvas-toolbar-input) {
|
|
471
|
+
width: 100%;
|
|
472
|
+
padding: 0 var(--ui-space-8);
|
|
473
|
+
min-height: 1.75rem;
|
|
474
|
+
background: var(--ui-surface-low);
|
|
475
|
+
border: 1px solid var(--ui-border);
|
|
476
|
+
border-radius: var(--ui-radius-md);
|
|
477
|
+
color: var(--ui-text-primary);
|
|
478
|
+
font-family: var(--ui-font-sans);
|
|
479
|
+
font-size: var(--ui-font-size-sm);
|
|
480
|
+
transition: background-color var(--ui-transition-fast), border-color var(--ui-transition-fast);
|
|
481
|
+
}
|
|
482
|
+
.canvas-toolbar :global(.canvas-toolbar-input:hover) {
|
|
483
|
+
border-color: var(--ui-border-higher);
|
|
484
|
+
}
|
|
485
|
+
.canvas-toolbar :global(.canvas-toolbar-input:focus-visible) {
|
|
486
|
+
outline: 2px solid var(--ui-highlight);
|
|
487
|
+
outline-offset: 2px;
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/* Pill each option row so Default/Image match the swatch trigger's weight.
|
|
491
|
+
One step below the toolbar's --ui-surface-low so option chrome stays visible
|
|
492
|
+
against the panel. */
|
|
493
|
+
.canvas-toolbar :global(.backdrop-option) {
|
|
494
|
+
padding: 0 var(--ui-space-8);
|
|
495
|
+
background: var(--ui-surface-lowest);
|
|
496
|
+
border: 1px solid var(--ui-border-low);
|
|
497
|
+
border-radius: var(--ui-radius-md);
|
|
498
|
+
color: var(--ui-text-secondary);
|
|
499
|
+
}
|
|
500
|
+
.canvas-toolbar :global(.backdrop-option.checked) {
|
|
501
|
+
background: var(--ui-surface-high);
|
|
502
|
+
border-color: var(--ui-border);
|
|
503
|
+
color: var(--ui-text-primary);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
/* Drop the swatch trigger's frame to avoid a double-border inside the option pill. */
|
|
507
|
+
.canvas-toolbar :global(.backdrop-option .ui-ts-trigger) {
|
|
508
|
+
background: transparent;
|
|
509
|
+
border-color: transparent;
|
|
510
|
+
padding-left: 0;
|
|
511
|
+
padding-right: 0;
|
|
512
|
+
}
|
|
513
|
+
.canvas-toolbar :global(.backdrop-option .ui-ts-trigger:hover) {
|
|
514
|
+
background: var(--ui-hover);
|
|
515
|
+
border-color: transparent;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
.canvas-toolbar :global(.ui-ts-meta-text) {
|
|
519
|
+
color: var(--ui-text-tertiary);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/* Anchor dropdown to the trigger's right edge; the toolbar sits flush against
|
|
523
|
+
the editor's right gutter, so a left-anchored dropdown would clip. */
|
|
524
|
+
.canvas-toolbar :global(.ui-ts-dropdown) {
|
|
525
|
+
left: auto;
|
|
526
|
+
right: 0;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
.variant-tabs {
|
|
530
|
+
display: inline-flex;
|
|
531
|
+
flex-wrap: wrap;
|
|
532
|
+
gap: var(--ui-space-4);
|
|
533
|
+
padding: var(--ui-space-4);
|
|
534
|
+
background: var(--ui-surface-lowest);
|
|
535
|
+
border: 1px solid var(--ui-border-low);
|
|
536
|
+
border-radius: var(--ui-radius-md);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.variant-tab-btn {
|
|
540
|
+
padding: var(--ui-space-6) var(--ui-space-12);
|
|
541
|
+
background: none;
|
|
542
|
+
border: none;
|
|
543
|
+
border-radius: var(--ui-radius-sm);
|
|
544
|
+
color: var(--ui-text-secondary);
|
|
545
|
+
font-size: var(--ui-font-size-md);
|
|
546
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
547
|
+
text-transform: capitalize;
|
|
548
|
+
cursor: pointer;
|
|
549
|
+
transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
.variant-tab-btn:hover:not(.active) {
|
|
553
|
+
color: var(--ui-text-primary);
|
|
554
|
+
background: var(--ui-hover);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
.variant-tab-btn.active {
|
|
558
|
+
color: var(--ui-text-primary);
|
|
559
|
+
background: var(--ui-surface-high);
|
|
560
|
+
box-shadow: 0 0 0 1px var(--ui-border);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
.tabs-states-block {
|
|
564
|
+
display: flex;
|
|
565
|
+
flex-direction: column;
|
|
566
|
+
gap: var(--ui-space-8);
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
.tabs-selectors {
|
|
570
|
+
display: flex;
|
|
571
|
+
align-items: center;
|
|
572
|
+
gap: var(--ui-space-12);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
.state-tabs {
|
|
576
|
+
display: inline-flex;
|
|
577
|
+
flex-wrap: wrap;
|
|
578
|
+
gap: var(--ui-space-4);
|
|
579
|
+
padding: var(--ui-space-4);
|
|
580
|
+
background: var(--ui-surface-lowest);
|
|
581
|
+
border: 1px solid var(--ui-border-low);
|
|
582
|
+
border-radius: var(--ui-radius-md);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
.state-tab-btn {
|
|
586
|
+
padding: var(--ui-space-6) var(--ui-space-12);
|
|
587
|
+
background: none;
|
|
588
|
+
border: none;
|
|
589
|
+
border-radius: var(--ui-radius-sm);
|
|
590
|
+
color: var(--ui-text-secondary);
|
|
591
|
+
font-size: var(--ui-font-size-sm);
|
|
592
|
+
font-weight: var(--ui-font-weight-medium);
|
|
593
|
+
text-transform: capitalize;
|
|
594
|
+
cursor: pointer;
|
|
595
|
+
transition: color var(--ui-transition-fast), background var(--ui-transition-fast);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.state-tab-btn:hover:not(.active) {
|
|
599
|
+
color: var(--ui-text-primary);
|
|
600
|
+
background: var(--ui-hover);
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
.state-tab-btn.active {
|
|
604
|
+
color: var(--ui-text-primary);
|
|
605
|
+
background: var(--ui-surface-high);
|
|
606
|
+
box-shadow: 0 0 0 1px var(--ui-border);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
/* Direct child of .variant-group; needs its own margin since flex gap doesn't apply across siblings. */
|
|
610
|
+
.properties-header {
|
|
611
|
+
display: flex;
|
|
612
|
+
align-items: center;
|
|
613
|
+
gap: var(--ui-space-16);
|
|
614
|
+
margin-top: var(--ui-space-8);
|
|
615
|
+
margin-bottom: var(--ui-space-8);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/* Tighten the title's line-height so its line-box matches its visual glyph
|
|
619
|
+
height — otherwise the inherited body line-height makes the heading sit
|
|
620
|
+
visually above the smaller CopyFromMenu trigger even with align-items:center. */
|
|
621
|
+
.properties-header .properties-title {
|
|
622
|
+
line-height: 1;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
.extra-property-rows {
|
|
626
|
+
display: flex;
|
|
627
|
+
flex-direction: column;
|
|
628
|
+
gap: var(--ui-space-6);
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
.extra-property-rows :global(.property-row) {
|
|
632
|
+
display: grid;
|
|
633
|
+
grid-template-columns: minmax(8rem, max-content) 1fr;
|
|
634
|
+
column-gap: var(--ui-space-16);
|
|
635
|
+
align-items: center;
|
|
636
|
+
min-height: 1.75rem;
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
.extra-property-rows :global(.property-row > .property-label) {
|
|
640
|
+
font-size: var(--ui-font-size-sm);
|
|
641
|
+
color: var(--ui-text-secondary);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
</style>
|
|
@@ -3,37 +3,47 @@ import { writable, type Writable, type Readable } from 'svelte/store';
|
|
|
3
3
|
|
|
4
4
|
const KEY = Symbol('editor-context');
|
|
5
5
|
|
|
6
|
+
export type VariantOption = { value: string; label: string };
|
|
7
|
+
|
|
6
8
|
export type EditorContext = {
|
|
7
|
-
/** Per-variable rank
|
|
9
|
+
/** Per-variable rank for TokenLayout row alignment; null when no linked block. */
|
|
8
10
|
linkedOrder: Readable<Map<string, number> | null>;
|
|
9
|
-
/**
|
|
11
|
+
/** Focused preview variant when siblings exist. */
|
|
10
12
|
focusedVariant: Writable<string | null>;
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
/** Variants for this editor; empty when single-variant. Focused VariantGroup renders the tab strip itself. */
|
|
14
|
+
variants: Readable<VariantOption[]>;
|
|
15
|
+
/** Cross-group state-tab hint; VariantGroups whose stateNames contain it adopt as activeTab. */
|
|
14
16
|
focusedState: Writable<string | null>;
|
|
15
|
-
/**
|
|
16
|
-
Linked-properties block. Bidirectional cue: a hover in one surface lights up
|
|
17
|
-
the matching row in the other so the user can see the linkage at a glance. */
|
|
17
|
+
/** Hovered variable in Properties grid or Linked block; bidirectional highlight. */
|
|
18
18
|
hoveredLinkedVariable: Writable<string | null>;
|
|
19
|
+
/** Copy-from "Preserve color families" toggle, scoped to this editor. Lives
|
|
20
|
+
on context (not local to CopyFromMenu) so it survives VariantGroup remounts
|
|
21
|
+
when the user changes which variant is focused. */
|
|
22
|
+
preserveColorFamily: Writable<boolean>;
|
|
19
23
|
};
|
|
20
24
|
|
|
21
|
-
/**
|
|
25
|
+
/** Mutable handle for ComponentEditorBase. */
|
|
22
26
|
export type EditorContextInternal = EditorContext & {
|
|
23
27
|
_linkedOrder: Writable<Map<string, number> | null>;
|
|
28
|
+
_variants: Writable<VariantOption[]>;
|
|
24
29
|
};
|
|
25
30
|
|
|
26
31
|
export function createEditorContext(): EditorContextInternal {
|
|
27
32
|
const _linkedOrder = writable<Map<string, number> | null>(null);
|
|
33
|
+
const _variants = writable<VariantOption[]>([]);
|
|
28
34
|
const focusedVariant = writable<string | null>(null);
|
|
29
35
|
const focusedState = writable<string | null>(null);
|
|
30
36
|
const hoveredLinkedVariable = writable<string | null>(null);
|
|
37
|
+
const preserveColorFamily = writable<boolean>(false);
|
|
31
38
|
const ctx: EditorContextInternal = {
|
|
32
39
|
linkedOrder: _linkedOrder,
|
|
33
40
|
focusedVariant,
|
|
41
|
+
variants: _variants,
|
|
34
42
|
focusedState,
|
|
35
43
|
hoveredLinkedVariable,
|
|
44
|
+
preserveColorFamily,
|
|
36
45
|
_linkedOrder,
|
|
46
|
+
_variants,
|
|
37
47
|
};
|
|
38
48
|
setContext(KEY, ctx);
|
|
39
49
|
return ctx;
|