@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,81 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import { CSS_VAR_CHANGE_EVENT } from '../../lib/cssVarSync';
|
|
4
|
+
import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
|
|
5
|
+
import UIVariantSelector from '../../ui/UIVariantSelector.svelte';
|
|
6
|
+
import { BORDER_WIDTH, DIVIDER_HEIGHT } from '../../ui/variantScales';
|
|
7
|
+
import FieldsetWrapper from './FieldsetWrapper.svelte';
|
|
8
|
+
|
|
9
|
+
export let colorVariable: string | undefined = undefined;
|
|
10
|
+
export let colorLabel: string | undefined = undefined;
|
|
11
|
+
export let widthVariable: string | undefined = undefined;
|
|
12
|
+
export let widthLabel: string | undefined = undefined;
|
|
13
|
+
export let heightVariable: string | undefined = undefined;
|
|
14
|
+
export let heightLabel: string | undefined = undefined;
|
|
15
|
+
/** When set, writes persist through the editor store under this component. */
|
|
16
|
+
export let component: string | undefined = undefined;
|
|
17
|
+
|
|
18
|
+
let heightResolved = '';
|
|
19
|
+
|
|
20
|
+
function readHeight() {
|
|
21
|
+
if (!heightVariable) {
|
|
22
|
+
heightResolved = '';
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
heightResolved = getComputedStyle(document.documentElement)
|
|
26
|
+
.getPropertyValue(heightVariable)
|
|
27
|
+
.trim();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function handleVarChange(e: Event) {
|
|
31
|
+
const detail = (e as CustomEvent<{ name: string }>).detail;
|
|
32
|
+
if (detail?.name === heightVariable) readHeight();
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
onMount(() => {
|
|
36
|
+
readHeight();
|
|
37
|
+
document.addEventListener(CSS_VAR_CHANGE_EVENT, handleVarChange);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
onDestroy(() => {
|
|
41
|
+
document.removeEventListener(CSS_VAR_CHANGE_EVENT, handleVarChange);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
$: heightIsZero = /^0+(?:\.0+)?(px|rem|em|%)?$/.test(heightResolved);
|
|
45
|
+
$: siblingDisabled = heightIsZero;
|
|
46
|
+
</script>
|
|
47
|
+
|
|
48
|
+
<FieldsetWrapper legend="divider">
|
|
49
|
+
{#if colorVariable}
|
|
50
|
+
<div class="entry">
|
|
51
|
+
<UIPaletteSelector variable={colorVariable} {component} disabled={siblingDisabled} on:change={readHeight} />
|
|
52
|
+
<span class="label">{colorLabel ?? ''}</span>
|
|
53
|
+
</div>
|
|
54
|
+
{/if}
|
|
55
|
+
{#if widthVariable}
|
|
56
|
+
<div class="entry">
|
|
57
|
+
<UIVariantSelector variable={widthVariable} {component} disabled={siblingDisabled} {...BORDER_WIDTH} on:change={readHeight} />
|
|
58
|
+
<span class="label">{widthLabel ?? ''}</span>
|
|
59
|
+
</div>
|
|
60
|
+
{/if}
|
|
61
|
+
{#if heightVariable}
|
|
62
|
+
<div class="entry">
|
|
63
|
+
<UIVariantSelector variable={heightVariable} {component} {...DIVIDER_HEIGHT} on:change={readHeight} />
|
|
64
|
+
<span class="label">{heightLabel ?? ''}</span>
|
|
65
|
+
</div>
|
|
66
|
+
{/if}
|
|
67
|
+
</FieldsetWrapper>
|
|
68
|
+
|
|
69
|
+
<style>
|
|
70
|
+
.entry {
|
|
71
|
+
display: flex;
|
|
72
|
+
flex-direction: column;
|
|
73
|
+
gap: var(--ui-space-2);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.label {
|
|
77
|
+
font-size: var(--ui-font-size-sm);
|
|
78
|
+
color: var(--ui-text-secondary);
|
|
79
|
+
padding-left: var(--ui-space-2);
|
|
80
|
+
}
|
|
81
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
export let legend: string = '';
|
|
3
|
+
/** When true, the fieldset is rendered with a strong outline to mark it as the one currently driving the rendered preview. */
|
|
4
|
+
export let active: boolean = false;
|
|
5
|
+
</script>
|
|
6
|
+
|
|
7
|
+
<fieldset class="fieldset-wrapper" class:active>
|
|
8
|
+
{#if legend}
|
|
9
|
+
<legend class="fieldset-legend">{legend}</legend>
|
|
10
|
+
{/if}
|
|
11
|
+
<div class="fieldset-controls">
|
|
12
|
+
<slot />
|
|
13
|
+
</div>
|
|
14
|
+
</fieldset>
|
|
15
|
+
|
|
16
|
+
<style>
|
|
17
|
+
.fieldset-wrapper {
|
|
18
|
+
border: 1px solid var(--ui-border-faint);
|
|
19
|
+
border-radius: var(--ui-radius-lg);
|
|
20
|
+
padding: 0 var(--ui-space-8) var(--ui-space-8);
|
|
21
|
+
margin: 0;
|
|
22
|
+
width: fit-content;
|
|
23
|
+
min-inline-size: 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.fieldset-wrapper.active {
|
|
27
|
+
outline: 2px solid var(--ui-text-primary);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.fieldset-legend {
|
|
31
|
+
font-size: var(--ui-font-size-xs);
|
|
32
|
+
color: var(--ui-text-tertiary);
|
|
33
|
+
padding: 0 var(--ui-space-4);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
.fieldset-wrapper.active .fieldset-legend {
|
|
37
|
+
color: var(--ui-text-primary);
|
|
38
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.fieldset-controls {
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-wrap: wrap;
|
|
44
|
+
gap: var(--ui-space-8);
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Composite editor for a 3-stop linear gradient defined as flat per-variant
|
|
4
|
+
* tokens (angle + 3× color + 3× position). Renders:
|
|
5
|
+
* - a live gradient ribbon with diamond handles below each stop's position
|
|
6
|
+
* - a UIPaletteSelector for the currently-selected stop's color
|
|
7
|
+
* - an AngleDial bound to the angle token
|
|
8
|
+
*
|
|
9
|
+
* The component writes literals (`Ndeg`, `N%`) back through `setComponentAlias`
|
|
10
|
+
* when the user drags; the picker writes its own token/literal ref via
|
|
11
|
+
* UIPaletteSelector. Reads resolve through `tokenRegistry$` so values
|
|
12
|
+
* authored as `var(--gradient-angle-diagonal)` show their resolved degrees.
|
|
13
|
+
*/
|
|
14
|
+
import { editorState, setComponentAlias } from '../../lib/editorStore';
|
|
15
|
+
import { tokenRegistry$ } from '../../lib/tokenRegistry';
|
|
16
|
+
import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
|
|
17
|
+
import AngleDial from './AngleDial.svelte';
|
|
18
|
+
|
|
19
|
+
export let component: string;
|
|
20
|
+
/** Prefix shared by all 7 token names — e.g. `--sectiondivider-canvas`. */
|
|
21
|
+
export let prefix: string;
|
|
22
|
+
|
|
23
|
+
type StopIndex = 1 | 2 | 3;
|
|
24
|
+
const STOPS: readonly StopIndex[] = [1, 2, 3] as const;
|
|
25
|
+
|
|
26
|
+
$: angleVar = `${prefix}-gradient-angle`;
|
|
27
|
+
function stopColorVar(i: StopIndex): string {
|
|
28
|
+
return `${prefix}-gradient-stop-${i}-color`;
|
|
29
|
+
}
|
|
30
|
+
function stopPositionVar(i: StopIndex): string {
|
|
31
|
+
return `${prefix}-gradient-stop-${i}-position`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
type Registry = typeof $tokenRegistry$;
|
|
35
|
+
|
|
36
|
+
/** Walk the alias chain and read the terminal declared value as a literal.
|
|
37
|
+
* The registry is passed in explicitly so the reactive expressions below
|
|
38
|
+
* can see `$tokenRegistry$` as a dependency (Svelte's static analysis can't
|
|
39
|
+
* peek into function bodies). */
|
|
40
|
+
function resolveLiteralWith(reg: Registry, varName: string): string | null {
|
|
41
|
+
const chain = reg.resolveAliasChain(varName);
|
|
42
|
+
const terminal = chain[chain.length - 1];
|
|
43
|
+
const decl = reg.getDeclaredValue(terminal);
|
|
44
|
+
if (decl === null) return null;
|
|
45
|
+
const m = decl.match(/^var\((--[a-z0-9-]+)\)$/i);
|
|
46
|
+
return m ? reg.getDeclaredValue(m[1]) ?? null : decl.trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function parseNumberFromCss(raw: string | null, unit: 'deg' | '%'): number | null {
|
|
50
|
+
if (!raw) return null;
|
|
51
|
+
const m = raw.trim().match(new RegExp(`^(-?\\d+(?:\\.\\d+)?)\\s*${unit}$`));
|
|
52
|
+
return m ? parseFloat(m[1]) : null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
$: angleDeg = parseNumberFromCss(resolveLiteralWith($tokenRegistry$, angleVar), 'deg') ?? 135;
|
|
56
|
+
$: positions = [
|
|
57
|
+
parseNumberFromCss(resolveLiteralWith($tokenRegistry$, stopPositionVar(1)), '%') ?? 0,
|
|
58
|
+
parseNumberFromCss(resolveLiteralWith($tokenRegistry$, stopPositionVar(2)), '%') ?? 50,
|
|
59
|
+
parseNumberFromCss(resolveLiteralWith($tokenRegistry$, stopPositionVar(3)), '%') ?? 100,
|
|
60
|
+
] as [number, number, number];
|
|
61
|
+
|
|
62
|
+
$: stopColors = ([1, 2, 3] as StopIndex[]).map((i) => {
|
|
63
|
+
const ref = $editorState.components[component]?.aliases[stopColorVar(i)];
|
|
64
|
+
if (!ref) return '#888';
|
|
65
|
+
return ref.kind === 'token' ? `var(${ref.name})` : ref.value;
|
|
66
|
+
}) as [string, string, string];
|
|
67
|
+
|
|
68
|
+
// Build the live gradient string from current positions + colors so the
|
|
69
|
+
// ribbon reflects edits even mid-drag (before the component re-renders via
|
|
70
|
+
// its own CSS var consumption).
|
|
71
|
+
$: ribbonBg = `linear-gradient(90deg, ${stopColors
|
|
72
|
+
.map((c, i) => `${c} ${positions[i]}%`)
|
|
73
|
+
.join(', ')})`;
|
|
74
|
+
|
|
75
|
+
let selected: StopIndex = 1;
|
|
76
|
+
|
|
77
|
+
function setAngle(deg: number) {
|
|
78
|
+
setComponentAlias(component, angleVar, { kind: 'literal', value: `${Math.round(deg)}deg` });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function setPosition(i: StopIndex, pct: number) {
|
|
82
|
+
const clamped = Math.max(0, Math.min(100, Math.round(pct * 10) / 10));
|
|
83
|
+
setComponentAlias(component, stopPositionVar(i), { kind: 'literal', value: `${clamped}%` });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ── Position handle drag ────────────────────────────────────────────────
|
|
87
|
+
let barEl: HTMLDivElement;
|
|
88
|
+
let dragIndex: StopIndex | null = null;
|
|
89
|
+
|
|
90
|
+
function pctFromEvent(e: PointerEvent): number {
|
|
91
|
+
const rect = barEl.getBoundingClientRect();
|
|
92
|
+
const x = e.clientX - rect.left;
|
|
93
|
+
return (x / rect.width) * 100;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function onHandleDown(e: PointerEvent, i: StopIndex) {
|
|
97
|
+
selected = i;
|
|
98
|
+
dragIndex = i;
|
|
99
|
+
(e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);
|
|
100
|
+
setPosition(i, pctFromEvent(e));
|
|
101
|
+
}
|
|
102
|
+
function onHandleMove(e: PointerEvent) {
|
|
103
|
+
if (dragIndex === null) return;
|
|
104
|
+
setPosition(dragIndex, pctFromEvent(e));
|
|
105
|
+
}
|
|
106
|
+
function onHandleUp(e: PointerEvent) {
|
|
107
|
+
if (dragIndex === null) return;
|
|
108
|
+
(e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId);
|
|
109
|
+
dragIndex = null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function onPositionInput(i: StopIndex, e: Event) {
|
|
113
|
+
const v = parseFloat((e.target as HTMLInputElement).value);
|
|
114
|
+
if (Number.isFinite(v)) setPosition(i, v);
|
|
115
|
+
}
|
|
116
|
+
</script>
|
|
117
|
+
|
|
118
|
+
<div class="gradient-card">
|
|
119
|
+
<div class="ribbon-wrap">
|
|
120
|
+
<div class="ribbon" bind:this={barEl} style="background: {ribbonBg};"></div>
|
|
121
|
+
<div class="handles">
|
|
122
|
+
{#each STOPS as i}
|
|
123
|
+
<button
|
|
124
|
+
type="button"
|
|
125
|
+
class="handle"
|
|
126
|
+
class:selected={selected === i}
|
|
127
|
+
class:dragging={dragIndex === i}
|
|
128
|
+
style="left: {positions[i - 1]}%; --stop-color: {stopColors[i - 1]};"
|
|
129
|
+
on:pointerdown={(e) => onHandleDown(e, i)}
|
|
130
|
+
on:pointermove={onHandleMove}
|
|
131
|
+
on:pointerup={onHandleUp}
|
|
132
|
+
on:pointercancel={onHandleUp}
|
|
133
|
+
title="Stop {i} ({positions[i - 1]}%)"
|
|
134
|
+
aria-label="Gradient stop {i}"
|
|
135
|
+
>
|
|
136
|
+
<span class="handle-diamond"></span>
|
|
137
|
+
</button>
|
|
138
|
+
{/each}
|
|
139
|
+
</div>
|
|
140
|
+
</div>
|
|
141
|
+
|
|
142
|
+
<div class="stop-edit-row">
|
|
143
|
+
<span class="row-label">Stop {selected}</span>
|
|
144
|
+
<label class="pos-input">
|
|
145
|
+
<input
|
|
146
|
+
type="number"
|
|
147
|
+
min="0"
|
|
148
|
+
max="100"
|
|
149
|
+
step="0.1"
|
|
150
|
+
value={positions[selected - 1]}
|
|
151
|
+
on:change={(e) => onPositionInput(selected, e)}
|
|
152
|
+
/>
|
|
153
|
+
<span class="suffix">%</span>
|
|
154
|
+
</label>
|
|
155
|
+
<div class="picker-slot">
|
|
156
|
+
<UIPaletteSelector variable={stopColorVar(selected)} {component} />
|
|
157
|
+
</div>
|
|
158
|
+
<div class="angle-slot">
|
|
159
|
+
<AngleDial value={angleDeg} on:change={(e) => setAngle(e.detail.value)} />
|
|
160
|
+
</div>
|
|
161
|
+
</div>
|
|
162
|
+
</div>
|
|
163
|
+
|
|
164
|
+
<style>
|
|
165
|
+
.gradient-card {
|
|
166
|
+
display: flex;
|
|
167
|
+
flex-direction: column;
|
|
168
|
+
gap: var(--ui-space-12);
|
|
169
|
+
padding: var(--ui-space-12);
|
|
170
|
+
background: var(--ui-surface-lowest);
|
|
171
|
+
border: 1px solid var(--ui-border-faint);
|
|
172
|
+
border-radius: var(--ui-radius-md);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.ribbon-wrap {
|
|
176
|
+
display: flex;
|
|
177
|
+
flex-direction: column;
|
|
178
|
+
gap: var(--ui-space-8);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.ribbon {
|
|
182
|
+
position: relative;
|
|
183
|
+
height: 2.25rem;
|
|
184
|
+
border-radius: var(--ui-radius-sm);
|
|
185
|
+
border: 1px solid var(--ui-border-default);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
.handles {
|
|
189
|
+
position: relative;
|
|
190
|
+
height: 1.25rem;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/* The button is a generous (1.25rem) transparent hit target so the diamond
|
|
194
|
+
can stay visually small while remaining easy to grab/drag. The visible
|
|
195
|
+
marker lives in `.handle-diamond` inside. */
|
|
196
|
+
.handle {
|
|
197
|
+
position: absolute;
|
|
198
|
+
top: 0;
|
|
199
|
+
width: 1.25rem;
|
|
200
|
+
height: 1.25rem;
|
|
201
|
+
margin-left: -0.625rem;
|
|
202
|
+
padding: 0;
|
|
203
|
+
background: transparent;
|
|
204
|
+
border: none;
|
|
205
|
+
cursor: ew-resize;
|
|
206
|
+
touch-action: none;
|
|
207
|
+
display: flex;
|
|
208
|
+
align-items: center;
|
|
209
|
+
justify-content: center;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
.handle-diamond {
|
|
213
|
+
width: 0.7rem;
|
|
214
|
+
height: 0.7rem;
|
|
215
|
+
background: var(--stop-color, var(--ui-surface-high));
|
|
216
|
+
border: 1px solid var(--ui-border-default);
|
|
217
|
+
transform: rotate(45deg);
|
|
218
|
+
border-radius: 1px;
|
|
219
|
+
transition: border-color var(--ui-transition-fast), box-shadow var(--ui-transition-fast);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.handle:hover .handle-diamond {
|
|
223
|
+
border-color: var(--ui-text-secondary);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
.handle.selected .handle-diamond {
|
|
227
|
+
border-color: var(--ui-text-primary);
|
|
228
|
+
box-shadow: 0 0 0 1px var(--ui-text-primary);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
.handle.dragging {
|
|
232
|
+
z-index: 2;
|
|
233
|
+
}
|
|
234
|
+
.handle.dragging .handle-diamond {
|
|
235
|
+
border-color: var(--ui-text-primary);
|
|
236
|
+
box-shadow: 0 0 0 2px var(--ui-text-primary);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* Selected-stop row: label, position readout, color picker, then angle dial
|
|
240
|
+
inline on the right. The selector width matches TokenLayout's
|
|
241
|
+
`--token-selector-w` so this row aligns with the property grid below. */
|
|
242
|
+
.stop-edit-row {
|
|
243
|
+
display: flex;
|
|
244
|
+
flex-wrap: wrap;
|
|
245
|
+
align-items: center;
|
|
246
|
+
gap: var(--ui-space-12);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
.row-label {
|
|
250
|
+
font-size: var(--ui-font-size-xs);
|
|
251
|
+
color: var(--ui-text-secondary);
|
|
252
|
+
white-space: nowrap;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
.picker-slot {
|
|
256
|
+
width: 8rem;
|
|
257
|
+
min-width: 0;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
.pos-input {
|
|
261
|
+
display: inline-flex;
|
|
262
|
+
align-items: center;
|
|
263
|
+
gap: var(--ui-space-4);
|
|
264
|
+
font-size: var(--ui-font-size-xs);
|
|
265
|
+
color: var(--ui-text-secondary);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
.pos-input input {
|
|
269
|
+
width: 4rem;
|
|
270
|
+
padding: var(--ui-space-2) var(--ui-space-6);
|
|
271
|
+
background: var(--ui-surface-low);
|
|
272
|
+
border: 1px solid var(--ui-border-faint);
|
|
273
|
+
border-radius: var(--ui-radius-sm);
|
|
274
|
+
color: var(--ui-text-primary);
|
|
275
|
+
font-family: var(--ui-font-mono);
|
|
276
|
+
font-size: var(--ui-font-size-sm);
|
|
277
|
+
text-align: right;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
.pos-input input::-webkit-outer-spin-button,
|
|
281
|
+
.pos-input input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
|
|
282
|
+
|
|
283
|
+
.suffix {
|
|
284
|
+
font-size: var(--ui-font-size-xs);
|
|
285
|
+
color: var(--ui-text-tertiary);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
.angle-slot {
|
|
289
|
+
margin-left: auto;
|
|
290
|
+
}
|
|
291
|
+
</style>
|