@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,56 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from 'svelte';
|
|
3
|
+
import UIRadioGroup from '../../ui/UIRadioGroup.svelte';
|
|
4
|
+
import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
|
|
5
|
+
import { setCssVar } from '../../lib/cssVarSync';
|
|
6
|
+
|
|
7
|
+
type Mode = 'image' | 'color';
|
|
8
|
+
export let mode: Mode = 'image';
|
|
9
|
+
/** Editor-scoped CSS var the picker writes to (must end with `-surface` to allow gradients). */
|
|
10
|
+
export let colorVariable: string;
|
|
11
|
+
|
|
12
|
+
const options: ReadonlyArray<{ value: Mode; label: string }> = [
|
|
13
|
+
{ value: 'image', label: 'Image' },
|
|
14
|
+
{ value: 'color', label: 'Color' },
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
// Editor-only backdrop vars aren't persisted to disk; seed surface-canvas as
|
|
18
|
+
// the default selection on first mount so the picker reads as surface-canvas
|
|
19
|
+
// and the color-mode preview matches.
|
|
20
|
+
onMount(() => {
|
|
21
|
+
if (!document.documentElement.style.getPropertyValue(colorVariable)) {
|
|
22
|
+
setCssVar(colorVariable, 'var(--surface-canvas)');
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
</script>
|
|
26
|
+
|
|
27
|
+
<label class="backdrop-config">
|
|
28
|
+
<span>Sample background</span>
|
|
29
|
+
<div class="backdrop-row">
|
|
30
|
+
<UIRadioGroup
|
|
31
|
+
bind:value={mode}
|
|
32
|
+
name="shadow-backdrop-mode-{Math.random().toString(36).slice(2, 8)}"
|
|
33
|
+
{options}
|
|
34
|
+
/>
|
|
35
|
+
<div class="picker-slot">
|
|
36
|
+
<UIPaletteSelector variable={colorVariable} disabled={mode !== 'color'} />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</label>
|
|
40
|
+
|
|
41
|
+
<style>
|
|
42
|
+
.backdrop-row {
|
|
43
|
+
display: inline-flex;
|
|
44
|
+
align-items: center;
|
|
45
|
+
gap: var(--ui-space-16);
|
|
46
|
+
padding: var(--ui-space-4) var(--ui-space-8);
|
|
47
|
+
border: 1px solid var(--ui-border-faint);
|
|
48
|
+
border-radius: var(--ui-radius-sm);
|
|
49
|
+
}
|
|
50
|
+
.picker-slot {
|
|
51
|
+
min-width: 8rem;
|
|
52
|
+
}
|
|
53
|
+
.picker-slot :global(.ui-token-selector) {
|
|
54
|
+
width: 100%;
|
|
55
|
+
}
|
|
56
|
+
</style>
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Shared inner block rendered inside a single state in VariantGroup.
|
|
4
|
+
*
|
|
5
|
+
* Both the tabs branch and the list branch of VariantGroup render the same
|
|
6
|
+
* `<TypeEditor>` (when a state has type groups) followed by `<TokenLayout>`,
|
|
7
|
+
* differing only in the surrounding chrome (preview placement, toggles,
|
|
8
|
+
* tab strip). This component owns the duplicated inner block so a per-state
|
|
9
|
+
* control change happens in exactly one place.
|
|
10
|
+
*/
|
|
11
|
+
import TokenLayout from './TokenLayout.svelte';
|
|
12
|
+
import TypeEditor from './TypeEditor.svelte';
|
|
13
|
+
import type { Token, TypeGroupConfig } from './types';
|
|
14
|
+
|
|
15
|
+
/** Tokens for this state, fed to `<TokenLayout>`. */
|
|
16
|
+
export let tokens: Token[];
|
|
17
|
+
/** Type groups for this state; rendered as a row of `<TypeEditor>` blocks. */
|
|
18
|
+
export let typeGroups: TypeGroupConfig[] = [];
|
|
19
|
+
/** Forwarded to TypeEditor and TokenLayout so writes persist through the editor store. */
|
|
20
|
+
export let component: string | undefined = undefined;
|
|
21
|
+
/** Per-variable rank passed through to TokenLayout for linked-block alignment. */
|
|
22
|
+
export let linkedOrder: Map<string, number> | undefined = undefined;
|
|
23
|
+
/** Render the token grid with N visual columns. >1 spreads a long property
|
|
24
|
+
list horizontally; only meaningful for state-blocks without typeGroups
|
|
25
|
+
(the two-col flex layout already partitions screen real estate when
|
|
26
|
+
typeGroups are present). */
|
|
27
|
+
export let columns: number = 1;
|
|
28
|
+
|
|
29
|
+
$: hasTypeGroups = typeGroups.length > 0;
|
|
30
|
+
</script>
|
|
31
|
+
|
|
32
|
+
<div class="state-controls" class:two-col={hasTypeGroups}>
|
|
33
|
+
{#if hasTypeGroups}
|
|
34
|
+
<div class="state-type-groups">
|
|
35
|
+
{#each typeGroups as tg}
|
|
36
|
+
<TypeEditor
|
|
37
|
+
legend={tg.legend ?? 'type'}
|
|
38
|
+
colorVariable={tg.colorVariable}
|
|
39
|
+
colorLabel={tg.colorLabel ?? 'text color'}
|
|
40
|
+
familyVariable={tg.familyVariable}
|
|
41
|
+
familyLabel={tg.familyLabel ?? 'font family'}
|
|
42
|
+
sizeVariable={tg.sizeVariable}
|
|
43
|
+
sizeLabel={tg.sizeLabel ?? 'font size'}
|
|
44
|
+
weightVariable={tg.weightVariable}
|
|
45
|
+
weightLabel={tg.weightLabel ?? 'font weight'}
|
|
46
|
+
lineHeightVariable={tg.lineHeightVariable}
|
|
47
|
+
lineHeightLabel={tg.lineHeightLabel ?? 'line height'}
|
|
48
|
+
outlineWidthVariable={tg.outlineWidthVariable}
|
|
49
|
+
outlineWidthLabel={tg.outlineWidthLabel ?? 'outline thickness'}
|
|
50
|
+
outlineColorVariable={tg.outlineColorVariable}
|
|
51
|
+
outlineColorLabel={tg.outlineColorLabel ?? 'outline color'}
|
|
52
|
+
{component}
|
|
53
|
+
on:change
|
|
54
|
+
/>
|
|
55
|
+
{/each}
|
|
56
|
+
</div>
|
|
57
|
+
{/if}
|
|
58
|
+
<TokenLayout
|
|
59
|
+
title=""
|
|
60
|
+
{tokens}
|
|
61
|
+
{component}
|
|
62
|
+
{linkedOrder}
|
|
63
|
+
{columns}
|
|
64
|
+
on:change
|
|
65
|
+
/>
|
|
66
|
+
</div>
|
|
67
|
+
|
|
68
|
+
<style>
|
|
69
|
+
.state-controls {
|
|
70
|
+
display: grid;
|
|
71
|
+
grid-template-columns: 1fr;
|
|
72
|
+
gap: var(--ui-space-12);
|
|
73
|
+
align-items: start;
|
|
74
|
+
margin-top: var(--ui-space-4);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.state-controls.two-col {
|
|
78
|
+
display: flex;
|
|
79
|
+
flex-wrap: wrap;
|
|
80
|
+
gap: var(--ui-space-16) var(--ui-space-16);
|
|
81
|
+
align-items: flex-start;
|
|
82
|
+
justify-content: flex-start;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.state-type-groups {
|
|
86
|
+
display: flex;
|
|
87
|
+
flex-direction: row;
|
|
88
|
+
flex-wrap: wrap;
|
|
89
|
+
gap: var(--ui-space-16);
|
|
90
|
+
align-items: flex-start;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/* Inside a state's two-col layout the fieldset frame is redundant with the
|
|
94
|
+
surrounding state card. Flatten the border/padding but keep the legend so
|
|
95
|
+
each block ("title", "body text", …) is identifiable. */
|
|
96
|
+
.state-controls.two-col .state-type-groups :global(.fieldset-wrapper) {
|
|
97
|
+
border: none;
|
|
98
|
+
padding: 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.state-controls.two-col .state-type-groups :global(.fieldset-wrapper.active) {
|
|
102
|
+
outline: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.state-controls.two-col .state-type-groups :global(.fieldset-legend) {
|
|
106
|
+
padding: 0 var(--ui-space-4) var(--ui-space-4);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/* The general-properties column has no legend of its own; pad it down by
|
|
110
|
+
one legend-line so its first row aligns with the first row of the
|
|
111
|
+
adjacent type-group. */
|
|
112
|
+
.state-controls.two-col > :global(.token-group) {
|
|
113
|
+
padding-top: calc(var(--ui-font-size-xs) + var(--ui-space-4));
|
|
114
|
+
}
|
|
115
|
+
</style>
|
|
@@ -0,0 +1,511 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { createEventDispatcher, type ComponentType } from 'svelte';
|
|
3
|
+
import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
|
|
4
|
+
import UIVariantSelector from '../../ui/UIVariantSelector.svelte';
|
|
5
|
+
import UIFontFamilySelector from '../../ui/UIFontFamilySelector.svelte';
|
|
6
|
+
import UIFontWeightSelector from '../../ui/UIFontWeightSelector.svelte';
|
|
7
|
+
import UIFontSizeSelector from '../../ui/UIFontSizeSelector.svelte';
|
|
8
|
+
import UILineHeightSelector from '../../ui/UILineHeightSelector.svelte';
|
|
9
|
+
import UIPaddingSelector from '../../ui/UIPaddingSelector.svelte';
|
|
10
|
+
import { BLUR, BORDER_WIDTH, DOT_SIZE, RADIUS, SHADOW, DIVIDER_HEIGHT } from '../../ui/variantScales';
|
|
11
|
+
import {
|
|
12
|
+
editorState,
|
|
13
|
+
getComponentPropertySiblings,
|
|
14
|
+
setComponentAliasLinked,
|
|
15
|
+
clearComponentAliasLinked,
|
|
16
|
+
} from '../../lib/editorStore';
|
|
17
|
+
import { getEditorContext } from './editorContext';
|
|
18
|
+
import type { Token } from './types';
|
|
19
|
+
|
|
20
|
+
/** Selector kind. `padding-split` is `padding` whose per-side variables exist;
|
|
21
|
+
it renders the four-sided field group instead of the single-value row. */
|
|
22
|
+
type Kind =
|
|
23
|
+
| 'surface'
|
|
24
|
+
| 'border'
|
|
25
|
+
| 'border-width'
|
|
26
|
+
| 'radius'
|
|
27
|
+
| 'divider-width'
|
|
28
|
+
| 'divider-height'
|
|
29
|
+
| 'dot-size'
|
|
30
|
+
| 'blur'
|
|
31
|
+
| 'shadow'
|
|
32
|
+
| 'font-family'
|
|
33
|
+
| 'font-weight'
|
|
34
|
+
| 'font-size'
|
|
35
|
+
| 'line-height'
|
|
36
|
+
| 'padding'
|
|
37
|
+
| 'padding-split'
|
|
38
|
+
| 'gap'
|
|
39
|
+
| 'extras';
|
|
40
|
+
|
|
41
|
+
type Entry = { kind: Kind; token: Token };
|
|
42
|
+
|
|
43
|
+
export let title: string = '';
|
|
44
|
+
export let tokens: Token[];
|
|
45
|
+
/** Forwarded to each selector; when set, writes persist through the editor store. */
|
|
46
|
+
export let component: string | undefined = undefined;
|
|
47
|
+
/** Optional context labels per variable (shown below the selector). */
|
|
48
|
+
export let contexts: Record<string, string[]> = {};
|
|
49
|
+
/** Per-variable rank that overrides kind rank when sorting; lets linked tokens align with the top linked row. */
|
|
50
|
+
export let linkedOrder: Map<string, number> | undefined = undefined;
|
|
51
|
+
/** Set true on the linked-block instance so dimmed variant rows can scroll/flash to the matching anchor. */
|
|
52
|
+
export let isLinkedBlock: boolean = false;
|
|
53
|
+
/** Number of visual columns. >1 switches to a column-major grid (grid-auto-flow: column)
|
|
54
|
+
so the consumer can spread a long property list across the available width. In
|
|
55
|
+
multi-col mode, the linked-first sort + zone divider are dropped — kind-grouped flow
|
|
56
|
+
reads more naturally when columns themselves carry the visual grouping. */
|
|
57
|
+
export let columns: number = 1;
|
|
58
|
+
|
|
59
|
+
/** Suffix/prefix patterns mapped to kinds — single source of truth used by `categorize`.
|
|
60
|
+
Order matters: `text` must run before `border`/`surface` because `--text-*` would
|
|
61
|
+
otherwise match `surface` checks if any pattern overlapped. */
|
|
62
|
+
const KIND_PATTERNS: Array<{ kind: Kind; matches: (v: string) => boolean }> = [
|
|
63
|
+
{ kind: 'font-family', matches: (v) => v.endsWith('-font-family') },
|
|
64
|
+
{ kind: 'font-weight', matches: (v) => v.endsWith('-font-weight') },
|
|
65
|
+
{ kind: 'font-size', matches: (v) => v.endsWith('-font-size') || v.endsWith('-icon-size') },
|
|
66
|
+
{ kind: 'line-height', matches: (v) => v.endsWith('-line-height') },
|
|
67
|
+
{ kind: 'extras', matches: (v) => v.endsWith('-text') || v.startsWith('--text-') },
|
|
68
|
+
{ kind: 'radius', matches: (v) => v.endsWith('-radius') || v.startsWith('--radius-') },
|
|
69
|
+
{ kind: 'divider-width', matches: (v) => v.endsWith('-divider-width') || v.endsWith('-divider-thickness') },
|
|
70
|
+
{ kind: 'divider-height', matches: (v) => v.endsWith('-divider-height') || v.endsWith('-track-height') },
|
|
71
|
+
{ kind: 'dot-size', matches: (v) => v.endsWith('-dot-size') },
|
|
72
|
+
{ kind: 'blur', matches: (v) => v.endsWith('-blur') || v.startsWith('--blur-') },
|
|
73
|
+
{ kind: 'shadow', matches: (v) => v.endsWith('-shadow') || v.startsWith('--shadow-') },
|
|
74
|
+
{ kind: 'padding', matches: (v) => v.endsWith('-padding') || v.endsWith('-margin') },
|
|
75
|
+
{ kind: 'gap', matches: (v) => v.endsWith('-gap') },
|
|
76
|
+
{ kind: 'border-width', matches: (v) => v.endsWith('-border-width') || v.startsWith('--border-width-') },
|
|
77
|
+
{ kind: 'border', matches: (v) => v.endsWith('-border') || v.startsWith('--border-') },
|
|
78
|
+
{ kind: 'surface', matches: (v) => v.endsWith('-surface') || v.startsWith('--surface-') },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
/** Fixed internal order for tokens within a layout. `padding-split` co-orders with `padding`. */
|
|
82
|
+
const baseKindOrder: Kind[] = [
|
|
83
|
+
'font-family',
|
|
84
|
+
'font-weight',
|
|
85
|
+
'font-size',
|
|
86
|
+
'line-height',
|
|
87
|
+
'divider-width',
|
|
88
|
+
'divider-height',
|
|
89
|
+
'dot-size',
|
|
90
|
+
'radius',
|
|
91
|
+
'padding',
|
|
92
|
+
'padding-split',
|
|
93
|
+
'gap',
|
|
94
|
+
'blur',
|
|
95
|
+
'shadow',
|
|
96
|
+
'extras',
|
|
97
|
+
'surface',
|
|
98
|
+
'border-width',
|
|
99
|
+
'border',
|
|
100
|
+
];
|
|
101
|
+
const orderRank: Record<Kind, number> = Object.fromEntries(
|
|
102
|
+
baseKindOrder.map((k, i) => [k, i]),
|
|
103
|
+
) as Record<Kind, number>;
|
|
104
|
+
|
|
105
|
+
function rawKind(v: string): Kind {
|
|
106
|
+
for (const { kind, matches } of KIND_PATTERNS) {
|
|
107
|
+
if (matches(v)) return kind;
|
|
108
|
+
}
|
|
109
|
+
return 'extras';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** A padding token is "split" when its per-side variables exist for this component. */
|
|
113
|
+
function paddingIsSplit(varName: string, comp: string | undefined, state: typeof $editorState): boolean {
|
|
114
|
+
const sides = ['top', 'right', 'bottom', 'left'];
|
|
115
|
+
if (comp) {
|
|
116
|
+
const slice = state.components[comp];
|
|
117
|
+
if (!slice) return false;
|
|
118
|
+
return sides.some((s) => `${varName}-${s}` in slice.aliases);
|
|
119
|
+
}
|
|
120
|
+
return sides.some((s) => !!document.documentElement.style.getPropertyValue(`${varName}-${s}`).trim());
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function categorize(v: string, comp: string | undefined, state: typeof $editorState): Kind {
|
|
124
|
+
const k = rawKind(v);
|
|
125
|
+
if (k === 'padding' && paddingIsSplit(v, comp, state)) return 'padding-split';
|
|
126
|
+
return k;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** For sibling/grouping checks we want the canonical kind, not the split-vs-single distinction. */
|
|
130
|
+
function groupingKind(v: string): Kind {
|
|
131
|
+
return rawKind(v);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** Selector registry: one entry per kind. `extra` props (e.g. UIPaddingSelector's
|
|
135
|
+
`mode`/`splittable`/`rowLabel`) are forwarded alongside the linked props. */
|
|
136
|
+
type SelectorEntry = {
|
|
137
|
+
component: ComponentType;
|
|
138
|
+
extra?: (token: Token) => Record<string, unknown>;
|
|
139
|
+
/** When true, the row is rendered as a self-contained block (spans all grid columns,
|
|
140
|
+
no .token-row wrapper, no contexts strip). Currently only `padding-split`. */
|
|
141
|
+
standalone?: boolean;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const SELECTOR_REGISTRY: Record<Kind, SelectorEntry> = {
|
|
145
|
+
'font-family': { component: UIFontFamilySelector },
|
|
146
|
+
'font-weight': { component: UIFontWeightSelector },
|
|
147
|
+
'font-size': { component: UIFontSizeSelector },
|
|
148
|
+
'line-height': { component: UILineHeightSelector },
|
|
149
|
+
'border-width': { component: UIVariantSelector, extra: () => ({ ...BORDER_WIDTH }) },
|
|
150
|
+
'divider-width': { component: UIVariantSelector, extra: () => ({ ...BORDER_WIDTH }) },
|
|
151
|
+
'divider-height': { component: UIVariantSelector, extra: () => ({ ...DIVIDER_HEIGHT }) },
|
|
152
|
+
'dot-size': { component: UIVariantSelector, extra: () => ({ ...DOT_SIZE }) },
|
|
153
|
+
'radius': { component: UIVariantSelector, extra: () => ({ ...RADIUS }) },
|
|
154
|
+
'padding': { component: UIPaddingSelector, extra: () => ({ mode: 'single' }) },
|
|
155
|
+
/* padding-split is NOT standalone: TokenLayout renders the .token-label
|
|
156
|
+
(e.g. "padding") in col 1 and the wrapper provides the [label][trigger][value]
|
|
157
|
+
subgrid. UIPaddingSelector's sides template fills cols 2-3 of row 1 with
|
|
158
|
+
the link/merge header and cols 1-3 of row 2 with the side rows, so the
|
|
159
|
+
four side dropdowns align with the same trigger and value columns as
|
|
160
|
+
every other property in the panel. */
|
|
161
|
+
'padding-split': {
|
|
162
|
+
component: UIPaddingSelector,
|
|
163
|
+
extra: () => ({ mode: 'sides' }),
|
|
164
|
+
},
|
|
165
|
+
'gap': { component: UIPaddingSelector, extra: () => ({ mode: 'single', splittable: false }) },
|
|
166
|
+
'blur': { component: UIVariantSelector, extra: () => ({ ...BLUR }) },
|
|
167
|
+
'shadow': { component: UIVariantSelector, extra: () => ({ ...SHADOW }) },
|
|
168
|
+
'surface': { component: UIPaletteSelector },
|
|
169
|
+
'border': { component: UIPaletteSelector },
|
|
170
|
+
'extras': { component: UIPaletteSelector },
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
/** Multi-col rank: same as `orderRank` but with `extras` (text-color-like) hoisted
|
|
174
|
+
between `line-height` and `border-width` so typography reads as one logical
|
|
175
|
+
block in column flow. Single-col mode keeps `orderRank` (linked-first sort
|
|
176
|
+
already segregates extras to the bottom). */
|
|
177
|
+
const multiColRank: Record<Kind, number> = (() => {
|
|
178
|
+
const reordered: Kind[] = [
|
|
179
|
+
'font-family',
|
|
180
|
+
'font-weight',
|
|
181
|
+
'font-size',
|
|
182
|
+
'line-height',
|
|
183
|
+
'extras',
|
|
184
|
+
'divider-width',
|
|
185
|
+
'divider-height',
|
|
186
|
+
'dot-size',
|
|
187
|
+
'radius',
|
|
188
|
+
'padding',
|
|
189
|
+
'padding-split',
|
|
190
|
+
'gap',
|
|
191
|
+
'blur',
|
|
192
|
+
'shadow',
|
|
193
|
+
'surface',
|
|
194
|
+
'border-width',
|
|
195
|
+
'border',
|
|
196
|
+
];
|
|
197
|
+
return Object.fromEntries(reordered.map((k, i) => [k, i])) as Record<Kind, number>;
|
|
198
|
+
})();
|
|
199
|
+
|
|
200
|
+
function buildEntries(list: Token[], order: Map<string, number> | undefined, linked: Set<Kind>, comp: string | undefined, state: typeof $editorState, multiCol: boolean): Entry[] {
|
|
201
|
+
const indexed = list.map((token, i) => ({ e: { kind: categorize(token.variable, comp, state), token }, i }));
|
|
202
|
+
const rank = multiCol ? multiColRank : orderRank;
|
|
203
|
+
indexed.sort((a, b) => {
|
|
204
|
+
if (!multiCol) {
|
|
205
|
+
const aLinked = linked.has(a.e.kind) ? 0 : 1;
|
|
206
|
+
const bLinked = linked.has(b.e.kind) ? 0 : 1;
|
|
207
|
+
if (aLinked !== bLinked) return aLinked - bLinked;
|
|
208
|
+
}
|
|
209
|
+
const rankDiff = rank[a.e.kind] - rank[b.e.kind];
|
|
210
|
+
if (rankDiff !== 0) return rankDiff;
|
|
211
|
+
const aKey = order?.get(a.e.token.variable);
|
|
212
|
+
const bKey = order?.get(b.e.token.variable);
|
|
213
|
+
if (aKey !== undefined && bKey !== undefined) return aKey - bKey;
|
|
214
|
+
if (aKey !== undefined) return -1;
|
|
215
|
+
if (bKey !== undefined) return 1;
|
|
216
|
+
return a.i - b.i;
|
|
217
|
+
});
|
|
218
|
+
return indexed.map((x) => x.e);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/** Kinds that currently have at least one variable with ≥2 siblings in the component.
|
|
222
|
+
Returns canonical kinds (so a `padding` linked-set covers `padding-split` rows too). */
|
|
223
|
+
function computeLinkedKinds(comp: string | undefined, state: typeof $editorState): Set<Kind> {
|
|
224
|
+
const set = new Set<Kind>();
|
|
225
|
+
if (!comp) return set;
|
|
226
|
+
const slice = state.components[comp];
|
|
227
|
+
if (!slice) return set;
|
|
228
|
+
for (const varName of Object.keys(slice.aliases)) {
|
|
229
|
+
if (getComponentPropertySiblings(comp, varName).length >= 2) {
|
|
230
|
+
const k = groupingKind(varName);
|
|
231
|
+
set.add(k);
|
|
232
|
+
if (k === 'padding') set.add('padding-split');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return set;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const dispatch = createEventDispatcher();
|
|
239
|
+
|
|
240
|
+
/** When a row collapses several groupKey leads into one display, mirror the lead's
|
|
241
|
+
new alias onto each peer (and its siblings) so the merged display stays in sync. */
|
|
242
|
+
function handleRowChange(token: Token) {
|
|
243
|
+
if (token.mergeVariables?.length && component) {
|
|
244
|
+
const slice = $editorState.components[component];
|
|
245
|
+
const leadAlias = slice?.aliases[token.variable];
|
|
246
|
+
for (const peer of token.mergeVariables) {
|
|
247
|
+
if (leadAlias) setComponentAliasLinked(component, peer, leadAlias);
|
|
248
|
+
else clearComponentAliasLinked(component, peer);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
dispatch('change');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/** Bidirectional hover cue with the Linked-properties block. The upper grid
|
|
255
|
+
(isLinkedBlock=false) drives and reads the shared store; the inner grid
|
|
256
|
+
inside each linked card (isLinkedBlock=true) sits out — its parent card
|
|
257
|
+
already provides the visual cue, and re-emitting from the inner row would
|
|
258
|
+
flicker hover state when the cursor crossed sub-elements. */
|
|
259
|
+
const editorCtx = getEditorContext();
|
|
260
|
+
const hoveredLinkedVariable = editorCtx?.hoveredLinkedVariable;
|
|
261
|
+
function setHover(variable: string | null) {
|
|
262
|
+
if (isLinkedBlock) return;
|
|
263
|
+
hoveredLinkedVariable?.set(variable);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
$: hoveredVar = !isLinkedBlock && hoveredLinkedVariable ? $hoveredLinkedVariable : null;
|
|
267
|
+
$: isMultiCol = columns > 1;
|
|
268
|
+
$: linkedKinds = computeLinkedKinds(component, $editorState);
|
|
269
|
+
$: entries = buildEntries(tokens.filter((t) => !t.hidden), linkedOrder, linkedKinds, component, $editorState, isMultiCol);
|
|
270
|
+
/** Index of the first independent (non-linked) entry; -1 when there are no linked entries or no boundary.
|
|
271
|
+
Suppressed in multi-col mode (no divider — column structure is the grouping). */
|
|
272
|
+
$: firstIndependentIdx = (() => {
|
|
273
|
+
if (isMultiCol) return -1;
|
|
274
|
+
const idx = entries.findIndex((e) => !linkedKinds.has(e.kind));
|
|
275
|
+
if (idx <= 0) return -1;
|
|
276
|
+
return idx;
|
|
277
|
+
})();
|
|
278
|
+
/** Rows per column for column-major flow; ceil so the last column may be short. */
|
|
279
|
+
$: rowsPerCol = isMultiCol ? Math.max(1, Math.ceil(entries.length / columns)) : entries.length;
|
|
280
|
+
|
|
281
|
+
</script>
|
|
282
|
+
|
|
283
|
+
<div class="token-group">
|
|
284
|
+
{#if title}
|
|
285
|
+
<span class="token-group-title">{title}</span>
|
|
286
|
+
{/if}
|
|
287
|
+
<div
|
|
288
|
+
class="token-grid"
|
|
289
|
+
class:multi-col={isMultiCol}
|
|
290
|
+
style:--columns={columns}
|
|
291
|
+
style:--rows-per-col={rowsPerCol}
|
|
292
|
+
>
|
|
293
|
+
{#each entries as entry, i}
|
|
294
|
+
{@const token = entry.token}
|
|
295
|
+
{@const dis = token.disabled ?? false}
|
|
296
|
+
{@const ctxs = contexts[token.variable]}
|
|
297
|
+
{@const lockedSelections = dis}
|
|
298
|
+
{@const sel = SELECTOR_REGISTRY[entry.kind]}
|
|
299
|
+
{@const sharedProps = {
|
|
300
|
+
variable: token.variable,
|
|
301
|
+
component,
|
|
302
|
+
canBeLinked: token.canBeLinked ?? false,
|
|
303
|
+
selectionsLocked: lockedSelections,
|
|
304
|
+
}}
|
|
305
|
+
{@const extra = sel.extra ? sel.extra(token) : {}}
|
|
306
|
+
{@const isNonFirstSet = isMultiCol && Math.floor(i / rowsPerCol) > 0}
|
|
307
|
+
{#if i === firstIndependentIdx}
|
|
308
|
+
<div class="zone-divider" aria-hidden="true"></div>
|
|
309
|
+
{/if}
|
|
310
|
+
<!--
|
|
311
|
+
Same wrapper for standalone and row-chrome modes so the inner
|
|
312
|
+
<svelte:component> stays at one template position across a kind
|
|
313
|
+
change (e.g. padding ↔ padding-split). If we branched on
|
|
314
|
+
sel.standalone with the component inside each branch, Svelte
|
|
315
|
+
would treat them as different mount points and remount the
|
|
316
|
+
selector — which kills any |local transition the selector runs
|
|
317
|
+
on internal prop changes (mode in UIPaddingSelector). Class
|
|
318
|
+
toggling alone keeps the instance alive; the label and contexts
|
|
319
|
+
come and go around it.
|
|
320
|
+
-->
|
|
321
|
+
{@const isLinkedRow = linkedKinds.has(entry.kind)}
|
|
322
|
+
{@const isHovered = !isLinkedBlock && isLinkedRow && hoveredVar === token.variable}
|
|
323
|
+
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
|
324
|
+
<div
|
|
325
|
+
class="token-entry"
|
|
326
|
+
class:token-row={!sel.standalone}
|
|
327
|
+
class:has-contexts={!sel.standalone && !!ctxs?.length}
|
|
328
|
+
class:linked-hovered={isHovered}
|
|
329
|
+
class:non-first-set={isNonFirstSet}
|
|
330
|
+
on:mouseenter={isLinkedBlock || !isLinkedRow ? undefined : () => setHover(token.variable)}
|
|
331
|
+
on:mouseleave={isLinkedBlock || !isLinkedRow ? undefined : () => setHover(null)}
|
|
332
|
+
>
|
|
333
|
+
{#if !sel.standalone}
|
|
334
|
+
<span class="token-label">{token.label}</span>
|
|
335
|
+
{/if}
|
|
336
|
+
<svelte:component
|
|
337
|
+
this={sel.component}
|
|
338
|
+
{...sharedProps}
|
|
339
|
+
{...extra}
|
|
340
|
+
on:change={() => handleRowChange(token)}
|
|
341
|
+
/>
|
|
342
|
+
{#if !sel.standalone && ctxs?.length}
|
|
343
|
+
<div class="token-contexts">
|
|
344
|
+
{#each ctxs as ctx}
|
|
345
|
+
<span class="token-context">{ctx}</span>
|
|
346
|
+
{/each}
|
|
347
|
+
</div>
|
|
348
|
+
{/if}
|
|
349
|
+
</div>
|
|
350
|
+
{/each}
|
|
351
|
+
</div>
|
|
352
|
+
</div>
|
|
353
|
+
|
|
354
|
+
<style>
|
|
355
|
+
.token-group {
|
|
356
|
+
display: flex;
|
|
357
|
+
flex-direction: column;
|
|
358
|
+
gap: var(--ui-space-6);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.token-group-title {
|
|
362
|
+
font-size: var(--ui-font-size-xs);
|
|
363
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
364
|
+
color: var(--ui-text-tertiary);
|
|
365
|
+
font-family: var(--ui-font-mono);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.token-grid {
|
|
369
|
+
--token-selector-w: 8rem;
|
|
370
|
+
--columns: 1;
|
|
371
|
+
display: grid;
|
|
372
|
+
grid-template-columns: repeat(var(--columns), max-content var(--token-selector-w) 1fr);
|
|
373
|
+
column-gap: var(--ui-space-10);
|
|
374
|
+
row-gap: var(--ui-space-6);
|
|
375
|
+
align-items: center;
|
|
376
|
+
padding: var(--ui-space-4) var(--ui-space-12);
|
|
377
|
+
min-width: 0;
|
|
378
|
+
}
|
|
379
|
+
/* Multi-col mode: column-major flow with explicit row count so items fill
|
|
380
|
+
column 1 top-to-bottom before spilling into column 2, etc. The value track
|
|
381
|
+
drops to `auto` so column-sets pack at natural width instead of letting
|
|
382
|
+
`1fr` value cells eat the panel and shove sets to opposite edges. The
|
|
383
|
+
inter-set gap is applied as `padding-left` on the lead column of every
|
|
384
|
+
non-first set — see `.non-first-set > .token-label`. Selector width and
|
|
385
|
+
gap are intentionally tighter than single-col so the layout stays viable
|
|
386
|
+
inside the docked overlay's iframe (~624px container at default width). */
|
|
387
|
+
.token-grid.multi-col {
|
|
388
|
+
--token-selector-w: 7rem;
|
|
389
|
+
grid-template-columns: repeat(var(--columns), max-content var(--token-selector-w) auto);
|
|
390
|
+
grid-template-rows: repeat(var(--rows-per-col), auto);
|
|
391
|
+
grid-auto-flow: column;
|
|
392
|
+
column-gap: var(--ui-space-8);
|
|
393
|
+
justify-content: start;
|
|
394
|
+
}
|
|
395
|
+
.token-grid.multi-col .token-entry.token-row.non-first-set > .token-label {
|
|
396
|
+
padding-left: var(--ui-space-20);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
@container (max-width: 480px) {
|
|
400
|
+
.token-grid { --token-selector-w: 6rem; }
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/* Narrow multi-col: shrink selector + inter-set gap further before giving
|
|
404
|
+
up the second column. Targets the overlay's typical docked width range. */
|
|
405
|
+
@container (max-width: 640px) {
|
|
406
|
+
.token-grid.multi-col {
|
|
407
|
+
--token-selector-w: 6rem;
|
|
408
|
+
column-gap: var(--ui-space-6);
|
|
409
|
+
}
|
|
410
|
+
.token-grid.multi-col .token-entry.token-row.non-first-set > .token-label {
|
|
411
|
+
padding-left: var(--ui-space-12);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/* Drop to one column only when even the tightened layout can't fit two
|
|
416
|
+
sets. Single col uses row flow (default), so unsetting the column-flow +
|
|
417
|
+
row template restores the original layout. The value track returns to
|
|
418
|
+
`1fr` so the lone column fills the panel like single-col mode, and the
|
|
419
|
+
inter-set padding is suppressed so wrapped "set 2" rows don't sit
|
|
420
|
+
indented. */
|
|
421
|
+
@container (max-width: 520px) {
|
|
422
|
+
.token-grid.multi-col {
|
|
423
|
+
--columns: 1;
|
|
424
|
+
grid-template-columns: max-content var(--token-selector-w) 1fr;
|
|
425
|
+
grid-template-rows: none;
|
|
426
|
+
grid-auto-flow: row;
|
|
427
|
+
column-gap: var(--ui-space-10);
|
|
428
|
+
}
|
|
429
|
+
.token-grid.multi-col .token-entry.token-row.non-first-set > .token-label {
|
|
430
|
+
padding-left: 0;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
@container (max-width: 380px) {
|
|
435
|
+
.token-grid {
|
|
436
|
+
grid-template-columns: max-content 1fr;
|
|
437
|
+
column-gap: var(--ui-space-6);
|
|
438
|
+
}
|
|
439
|
+
.token-grid :global(.ui-token-selector) { grid-column: 2; }
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
.zone-divider {
|
|
443
|
+
grid-column: 1 / -1;
|
|
444
|
+
height: 1.75rem;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/* The wrapper exists in both standalone and row-chrome modes to keep
|
|
448
|
+
the inner <svelte:component> at one template position across kind
|
|
449
|
+
changes (so its internal |local transitions can fire on prop
|
|
450
|
+
updates instead of being short-circuited by a remount).
|
|
451
|
+
- Standalone (no .token-row): `display: contents` makes the
|
|
452
|
+
wrapper transparent to the parent grid, letting the standalone
|
|
453
|
+
child (e.g. UIPaddingSelector's split fieldset) place itself
|
|
454
|
+
directly against `.token-grid`'s columns just like it did when
|
|
455
|
+
it was rendered without a wrapper.
|
|
456
|
+
- Row chrome (.token-row): grid + subgrid + alignment, the
|
|
457
|
+
original .token-row layout for [label][trigger][value]. */
|
|
458
|
+
.token-entry {
|
|
459
|
+
display: contents;
|
|
460
|
+
}
|
|
461
|
+
.token-entry.token-row {
|
|
462
|
+
display: grid;
|
|
463
|
+
grid-template-columns: subgrid;
|
|
464
|
+
/* Span one [label][selector][value] column-set. Equivalent to `1 / -1`
|
|
465
|
+
in single-col mode (3 sub-cols total) and lands the row in one
|
|
466
|
+
column-set in multi-col mode (3*N sub-cols total). */
|
|
467
|
+
grid-column: span 3;
|
|
468
|
+
align-items: center;
|
|
469
|
+
row-gap: var(--ui-space-2);
|
|
470
|
+
border-radius: var(--ui-radius-sm);
|
|
471
|
+
transition: background var(--ui-transition-fast), box-shadow var(--ui-transition-fast);
|
|
472
|
+
min-width: 0;
|
|
473
|
+
}
|
|
474
|
+
/* Bidirectional hover cue with the Linked-properties block. Background
|
|
475
|
+
lift extends the row beyond its three columns so the cue reads as a
|
|
476
|
+
full-width band; box-shadow adds the same horizontal padding the grid's
|
|
477
|
+
own padding provides without pushing the row's inner layout. */
|
|
478
|
+
.token-entry.token-row.linked-hovered {
|
|
479
|
+
background: var(--ui-hover-lowest);
|
|
480
|
+
box-shadow: 0 0 0 var(--ui-space-6) var(--ui-hover-lowest);
|
|
481
|
+
}
|
|
482
|
+
/* Suppress the trigger's own surface fill while the row is in the linked-
|
|
483
|
+
hover state so the row's hover band reads as one continuous strip
|
|
484
|
+
instead of being broken by the trigger's brighter chip. `:not(:hover)`
|
|
485
|
+
keeps the trigger's direct hover style intact when the cursor lands on
|
|
486
|
+
it specifically. */
|
|
487
|
+
.token-entry.token-row.linked-hovered :global(.ui-ts-trigger:not(:hover)) {
|
|
488
|
+
background: transparent;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
.token-label {
|
|
492
|
+
grid-column: 1;
|
|
493
|
+
font-size: var(--ui-font-size-sm);
|
|
494
|
+
color: var(--ui-text-secondary);
|
|
495
|
+
text-align: left;
|
|
496
|
+
line-height: 1;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
.token-contexts {
|
|
500
|
+
grid-column: 2 / -1;
|
|
501
|
+
display: flex;
|
|
502
|
+
flex-direction: column;
|
|
503
|
+
gap: 1px;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
.token-context {
|
|
507
|
+
font-size: var(--ui-font-size-xs);
|
|
508
|
+
color: var(--ui-text-tertiary);
|
|
509
|
+
white-space: nowrap;
|
|
510
|
+
}
|
|
511
|
+
</style>
|