@motion-proto/live-tokens 0.3.9 → 0.6.0
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 +47 -4
- package/package.json +18 -12
- package/src/component-editor/BadgeEditor.svelte +24 -22
- package/src/component-editor/CalloutEditor.svelte +3 -3
- package/src/component-editor/CardEditor.svelte +25 -21
- package/src/component-editor/CollapsibleSectionEditor.svelte +27 -25
- package/src/component-editor/CornerBadgeEditor.svelte +37 -35
- package/src/component-editor/DialogEditor.svelte +26 -24
- package/src/component-editor/ImageEditor.svelte +11 -9
- package/src/component-editor/InlineEditActionsEditor.svelte +17 -15
- package/src/component-editor/NotificationEditor.svelte +32 -30
- package/src/component-editor/ProgressBarEditor.svelte +3 -3
- package/src/component-editor/RadioButtonEditor.svelte +31 -29
- package/src/component-editor/SectionDividerEditor.svelte +30 -28
- package/src/component-editor/SegmentedControlEditor.svelte +29 -25
- package/src/component-editor/StandardButtonsEditor.svelte +42 -38
- package/src/component-editor/TabBarEditor.svelte +20 -18
- package/src/component-editor/TableEditor.svelte +4 -4
- package/src/component-editor/TooltipEditor.svelte +11 -9
- package/src/component-editor/registry.ts +2 -2
- package/src/component-editor/scaffolding/AngleDial.svelte +20 -19
- package/src/component-editor/scaffolding/ComponentEditorBase.svelte +44 -20
- package/src/component-editor/scaffolding/ComponentFileManager.svelte +262 -38
- package/src/component-editor/scaffolding/ComponentFileMenu.svelte +41 -29
- package/src/component-editor/scaffolding/ComponentsTab.svelte +7 -3
- package/src/component-editor/scaffolding/CopyFromMenu.svelte +21 -12
- package/src/component-editor/scaffolding/DemoHeader.svelte +13 -4
- package/src/component-editor/scaffolding/DividerEditor.svelte +27 -14
- package/src/component-editor/scaffolding/FieldsetWrapper.svelte +10 -4
- package/src/component-editor/scaffolding/GradientCard.svelte +25 -20
- package/src/component-editor/scaffolding/LinkageChart.svelte +43 -34
- package/src/component-editor/scaffolding/LinkedBlock.svelte +24 -21
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +6 -1
- package/src/component-editor/scaffolding/SaveAsDialog.svelte +39 -35
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +21 -9
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +8 -3
- package/src/component-editor/scaffolding/StateBlock.svelte +30 -13
- package/src/component-editor/scaffolding/TokenLayout.svelte +46 -30
- package/src/component-editor/scaffolding/TypeEditor.svelte +52 -26
- package/src/component-editor/scaffolding/VariantGroup.svelte +81 -48
- package/src/component-editor/scaffolding/componentSectionType.ts +2 -2
- package/src/components/Badge.svelte +45 -26
- package/src/components/Button.svelte +44 -21
- package/src/components/Callout.svelte +17 -12
- package/src/components/Card.svelte +23 -11
- package/src/components/CollapsibleSection.svelte +56 -27
- package/src/components/CornerBadge.svelte +32 -18
- package/src/components/Dialog.svelte +55 -31
- package/src/components/Image.svelte +14 -5
- package/src/components/InlineEditActions.svelte +22 -10
- package/src/components/Notification.svelte +39 -19
- package/src/components/ProgressBar.svelte +27 -17
- package/src/components/RadioButton.svelte +27 -10
- package/src/components/SectionDivider.svelte +34 -26
- package/src/components/SegmentedControl.svelte +23 -9
- package/src/components/TabBar.svelte +23 -10
- package/src/components/Table.svelte +8 -3
- package/src/components/Tooltip.svelte +15 -5
- package/src/lib/ColumnsOverlay.svelte +3 -3
- package/src/lib/LiveEditorOverlay.svelte +57 -36
- package/src/pages/ComponentEditorPage.svelte +25 -14
- package/src/pages/Editor.svelte +8 -2
- package/src/pages/EditorShell.svelte +24 -20
- package/src/styles/site.css +138 -0
- package/src/styles/tokens.css +78 -76
- package/src/styles/ui-form-controls.css +186 -0
- package/src/ui/BezierCurveEditor.svelte +59 -43
- package/src/ui/ColorEditPanel.svelte +71 -44
- package/src/ui/EditorViewSwitcher.svelte +9 -5
- package/src/ui/FontStackEditor.svelte +17 -16
- package/src/ui/GradientEditor.svelte +42 -33
- package/src/ui/GradientStopPicker.svelte +18 -29
- package/src/ui/PaletteEditor.svelte +238 -212
- package/src/ui/PresetFileManager.svelte +20 -18
- package/src/ui/ProjectFontsSection.svelte +34 -34
- package/src/ui/SurfacesTab.svelte +3 -3
- package/src/ui/TextTab.svelte +2 -2
- package/src/ui/ThemeFileManager.svelte +38 -35
- package/src/ui/Toggle.svelte +11 -9
- package/src/ui/UICopyPopover.svelte +19 -15
- package/src/ui/UIDialog.svelte +48 -30
- package/src/ui/UIFontFamilySelector.svelte +104 -78
- package/src/ui/UIFontSizeSelector.svelte +38 -20
- package/src/ui/UIFontWeightSelector.svelte +33 -13
- package/src/ui/UILineHeightSelector.svelte +33 -13
- package/src/ui/UILinkToggle.svelte +7 -6
- package/src/ui/UIOptionItem.svelte +21 -7
- package/src/ui/UIOptionList.svelte +9 -3
- package/src/ui/UIPaddingSelector.svelte +108 -82
- package/src/ui/UIPaletteSelector.svelte +186 -161
- package/src/ui/UIRadio.svelte +23 -8
- package/src/ui/UIRadioGroup.svelte +9 -8
- package/src/ui/UIRelinkConfirmPopover.svelte +26 -16
- package/src/ui/UITokenSelector.svelte +112 -68
- package/src/ui/UIVariantSelector.svelte +79 -57
- package/src/ui/VariablesTab.svelte +15 -15
- package/src/ui/palette/GradientStopEditor.svelte +45 -26
- package/src/ui/palette/OverridesPanel.svelte +85 -49
- package/src/ui/palette/PaletteBase.svelte +60 -32
- package/src/ui/palette/ScaleCurveEditor.svelte +25 -10
- package/src/ui/sections/ColumnsSection.svelte +13 -13
- package/src/ui/sections/GradientsSection.svelte +12 -9
- package/src/ui/sections/OverlaysSection.svelte +50 -47
- package/src/ui/sections/ShadowsSection.svelte +110 -104
- package/src/ui/sections/TokenScaleTable.svelte +38 -22
- package/src/ui/sections/tokenScales.ts +2 -2
- package/src/styles/form-controls.css +0 -188
|
@@ -2,13 +2,17 @@
|
|
|
2
2
|
import { editorView } from '../lib/editorViewStore';
|
|
3
3
|
import { parentRoute } from '../lib/parentRouteStore';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
interface Props {
|
|
6
|
+
condensed?: boolean;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
let { condensed = false }: Props = $props();
|
|
6
10
|
|
|
7
11
|
// On /components the host page is already the components editor — the
|
|
8
12
|
// overlay's components view would just stack on top of it, so disable the
|
|
9
13
|
// switch. The switcher renders inside the editor iframe, so we read the
|
|
10
14
|
// *parent* route, not this iframe's own route.
|
|
11
|
-
|
|
15
|
+
let componentsDisabled = $derived($parentRoute === '/components');
|
|
12
16
|
|
|
13
17
|
function set(v: 'tokens' | 'components') {
|
|
14
18
|
editorView.set(v);
|
|
@@ -26,7 +30,7 @@
|
|
|
26
30
|
class="compact"
|
|
27
31
|
aria-label={$editorView === 'tokens' ? 'Switch to components' : 'Switch to tokens'}
|
|
28
32
|
title={$editorView === 'tokens' ? 'Tokens (click for components)' : 'Components (click for tokens)'}
|
|
29
|
-
|
|
33
|
+
onclick={toggle}
|
|
30
34
|
>
|
|
31
35
|
<i class="fas {$editorView === 'tokens' ? 'fa-palette' : 'fa-cubes'}"></i>
|
|
32
36
|
</button>
|
|
@@ -40,7 +44,7 @@
|
|
|
40
44
|
class="seg-btn"
|
|
41
45
|
class:active={$editorView === 'tokens'}
|
|
42
46
|
aria-selected={$editorView === 'tokens'}
|
|
43
|
-
|
|
47
|
+
onclick={() => set('tokens')}
|
|
44
48
|
>
|
|
45
49
|
<span class="radio" aria-hidden="true"></span>
|
|
46
50
|
<span>Tokens</span>
|
|
@@ -53,7 +57,7 @@
|
|
|
53
57
|
aria-selected={$editorView === 'components'}
|
|
54
58
|
disabled={componentsDisabled}
|
|
55
59
|
title={componentsDisabled ? 'Already viewing the Components page' : undefined}
|
|
56
|
-
|
|
60
|
+
onclick={() => set('components')}
|
|
57
61
|
>
|
|
58
62
|
<span class="radio" aria-hidden="true"></span>
|
|
59
63
|
<span>Components</span>
|
|
@@ -21,17 +21,17 @@
|
|
|
21
21
|
'--font-mono',
|
|
22
22
|
];
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
24
|
+
let fontSourcesList = $derived($editorState.fonts.sources);
|
|
25
|
+
let fontStacksList = $derived($editorState.fonts.stacks);
|
|
26
|
+
let allFamilies = $derived((fontSourcesList as FontSource[]).flatMap((s) => s.families.map((f) => ({ ...f, sourceLabel: s.label ?? s.kind }))));
|
|
27
|
+
let familyById = $derived(new Map<string, FontFamily>(allFamilies.map((f) => [f.id, f])));
|
|
28
28
|
|
|
29
29
|
function ensureAllStacksPresent(current: FontStack[]): FontStack[] {
|
|
30
30
|
const byVar = new Map(current.map((s) => [s.variable, s]));
|
|
31
31
|
return STACK_VARIABLES.map((v) => byVar.get(v) ?? { variable: v, slots: [{ kind: 'generic', value: 'sans-serif' } as FontStackSlot] });
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
let stacks = $derived(ensureAllStacksPresent(fontStacksList));
|
|
35
35
|
|
|
36
36
|
function slotKey(slot: FontStackSlot): string {
|
|
37
37
|
if (slot.kind === 'project') return `project:${slot.familyId}`;
|
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
});
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
let dragSource: { variable: FontStackVariable; index: number } | null = null;
|
|
112
|
-
let dragOver: { variable: FontStackVariable; index: number; position: 'before' | 'on' | 'after' } | null = null;
|
|
111
|
+
let dragSource: { variable: FontStackVariable; index: number } | null = $state(null);
|
|
112
|
+
let dragOver: { variable: FontStackVariable; index: number; position: 'before' | 'on' | 'after' } | null = $state(null);
|
|
113
113
|
|
|
114
114
|
function onDragStart(e: DragEvent, variable: FontStackVariable, index: number) {
|
|
115
115
|
if (!e.dataTransfer) return;
|
|
@@ -167,6 +167,7 @@
|
|
|
167
167
|
</div>
|
|
168
168
|
<div class="font-stack-list">
|
|
169
169
|
{#each stack.slots as slot, i (i + ':' + slotKey(slot))}
|
|
170
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
170
171
|
<div
|
|
171
172
|
class="slot-row"
|
|
172
173
|
class:drop-on={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'on'}
|
|
@@ -174,11 +175,11 @@
|
|
|
174
175
|
class:drop-after={dragOver?.variable === stack.variable && dragOver?.index === i && dragOver?.position === 'after'}
|
|
175
176
|
class:dragging={dragSource?.variable === stack.variable && dragSource?.index === i}
|
|
176
177
|
draggable="true"
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
178
|
+
ondragstart={(e) => onDragStart(e, stack.variable, i)}
|
|
179
|
+
ondragover={(e) => onDragOver(e, stack.variable, i)}
|
|
180
|
+
ondragleave={onDragLeave}
|
|
181
|
+
ondrop={(e) => onDrop(e, stack.variable, i)}
|
|
182
|
+
ondragend={onDragEnd}
|
|
182
183
|
>
|
|
183
184
|
<span class="drag-handle" aria-hidden="true">⋮⋮</span>
|
|
184
185
|
<span class="slot-position">{i + 1}.</span>
|
|
@@ -188,9 +189,9 @@
|
|
|
188
189
|
style="font-family: {slotCssValue(slot)};{stack.variable === '--font-display' ? ' font-size: var(--ui-font-size-2xl);' : ''}"
|
|
189
190
|
>The quick brown fox jumps over the lazy dog</span>
|
|
190
191
|
<select
|
|
191
|
-
class="form-select slot-select"
|
|
192
|
+
class="ui-form-select slot-select"
|
|
192
193
|
value={slotKey(slot)}
|
|
193
|
-
|
|
194
|
+
onchange={(e) => onSelectChange(e, stack.variable, i)}
|
|
194
195
|
>
|
|
195
196
|
{#if allFamilies.length > 0}
|
|
196
197
|
<optgroup label="Project fonts">
|
|
@@ -216,13 +217,13 @@
|
|
|
216
217
|
class="slot-remove"
|
|
217
218
|
aria-label="Remove slot"
|
|
218
219
|
title="Remove"
|
|
219
|
-
|
|
220
|
+
onclick={() => removeSlot(stack.variable, i)}
|
|
220
221
|
disabled={stack.slots.length <= 1}
|
|
221
222
|
>×</button>
|
|
222
223
|
</div>
|
|
223
224
|
{/each}
|
|
224
225
|
</div>
|
|
225
|
-
<button type="button" class="add-fallback"
|
|
226
|
+
<button type="button" class="add-fallback" onclick={() => addSlot(stack.variable)}>
|
|
226
227
|
+ add fallback
|
|
227
228
|
</button>
|
|
228
229
|
</div>
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { run } from 'svelte/legacy';
|
|
3
|
+
|
|
2
4
|
/**
|
|
3
5
|
* Visual gradient editor. Stops are draggable diamond handles below a live
|
|
4
6
|
* ribbon; only the selected stop exposes its position + color controls (the
|
|
5
7
|
* old list of every stop is replaced by this single-row pattern, mirroring
|
|
6
8
|
* GradientCard.svelte). Stops can be added/removed with a minimum of two.
|
|
7
9
|
*/
|
|
8
|
-
import { tick, onMount
|
|
10
|
+
import { tick, onMount } from 'svelte';
|
|
9
11
|
import {
|
|
10
12
|
editorState,
|
|
11
13
|
setGradient,
|
|
@@ -19,9 +21,13 @@
|
|
|
19
21
|
import GradientStopPicker from './GradientStopPicker.svelte';
|
|
20
22
|
import AngleDial from '../component-editor/scaffolding/AngleDial.svelte';
|
|
21
23
|
|
|
22
|
-
|
|
24
|
+
interface Props {
|
|
25
|
+
variable: string;
|
|
26
|
+
onsave?: () => void;
|
|
27
|
+
oncancel?: () => void;
|
|
28
|
+
}
|
|
23
29
|
|
|
24
|
-
|
|
30
|
+
let { variable, onsave, oncancel }: Props = $props();
|
|
25
31
|
|
|
26
32
|
/** Deep snapshot of the gradient at editor open, used to restore on Cancel. */
|
|
27
33
|
let snapshot: { type: GradientType; angle: number; stops: GradientTokenStop[] } | null = null;
|
|
@@ -32,25 +38,27 @@
|
|
|
32
38
|
}
|
|
33
39
|
});
|
|
34
40
|
|
|
35
|
-
function save() {
|
|
41
|
+
function save() { onsave?.(); }
|
|
36
42
|
function cancel() {
|
|
37
43
|
if (snapshot) setGradient(variable, snapshot);
|
|
38
|
-
|
|
44
|
+
oncancel?.();
|
|
39
45
|
}
|
|
40
46
|
|
|
41
|
-
|
|
42
|
-
|
|
47
|
+
let gradient = $derived($editorState.gradients.tokens.find((t) => t.variable === variable));
|
|
48
|
+
let stopCount = $derived(gradient?.stops.length ?? 0);
|
|
43
49
|
|
|
44
|
-
let selected = 0;
|
|
50
|
+
let selected = $state(0);
|
|
45
51
|
// Keep `selected` in range as stops are added/removed.
|
|
46
|
-
|
|
52
|
+
run(() => {
|
|
53
|
+
if (selected >= stopCount) selected = Math.max(0, stopCount - 1);
|
|
54
|
+
});
|
|
47
55
|
|
|
48
56
|
function setType(type: GradientType) {
|
|
49
57
|
setGradientType(variable, type);
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
function onAngleChange(
|
|
53
|
-
setGradientAngle(variable,
|
|
60
|
+
function onAngleChange(detail: { value: number }) {
|
|
61
|
+
setGradientAngle(variable, detail.value);
|
|
54
62
|
}
|
|
55
63
|
|
|
56
64
|
function setPosition(i: number, pct: number) {
|
|
@@ -63,8 +71,8 @@
|
|
|
63
71
|
if (Number.isFinite(v)) setPosition(selected, v);
|
|
64
72
|
}
|
|
65
73
|
|
|
66
|
-
function handleStopChange(i: number,
|
|
67
|
-
setGradientStop(variable, i, { color:
|
|
74
|
+
function handleStopChange(i: number, payload: { color: string; opacity: number }) {
|
|
75
|
+
setGradientStop(variable, i, { color: payload.color, opacity: payload.opacity });
|
|
68
76
|
}
|
|
69
77
|
|
|
70
78
|
/** Insert a stop at the given percentage, inheriting color/opacity from the
|
|
@@ -103,7 +111,7 @@
|
|
|
103
111
|
* a sensible color rather than a default. */
|
|
104
112
|
function onRibbonClick(e: MouseEvent) {
|
|
105
113
|
if (!gradient || e.button !== 0) return;
|
|
106
|
-
const rect = barEl
|
|
114
|
+
const rect = barEl!.getBoundingClientRect();
|
|
107
115
|
const pct = ((e.clientX - rect.left) / rect.width) * 100;
|
|
108
116
|
const nearest = gradient.stops.reduce(
|
|
109
117
|
(best, s) => (Math.abs(s.position - pct) < Math.abs(best.position - pct) ? s : best),
|
|
@@ -119,11 +127,11 @@
|
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
// ── Ribbon handle drag ─────────────────────────────────────────────────
|
|
122
|
-
let barEl: HTMLDivElement;
|
|
123
|
-
let dragIndex: number | null = null;
|
|
130
|
+
let barEl: HTMLDivElement | undefined = $state();
|
|
131
|
+
let dragIndex: number | null = $state(null);
|
|
124
132
|
|
|
125
133
|
function pctFromEvent(e: PointerEvent): number {
|
|
126
|
-
const rect = barEl
|
|
134
|
+
const rect = barEl!.getBoundingClientRect();
|
|
127
135
|
const x = e.clientX - rect.left;
|
|
128
136
|
return (x / rect.width) * 100;
|
|
129
137
|
}
|
|
@@ -145,21 +153,22 @@
|
|
|
145
153
|
}
|
|
146
154
|
|
|
147
155
|
// Stop colors rendered into the diamonds: token refs become var(...).
|
|
148
|
-
|
|
156
|
+
let stopSwatches = $derived((gradient?.stops ?? []).map((s) => {
|
|
149
157
|
const base = s.color.startsWith('--') ? `var(${s.color})` : s.color;
|
|
150
158
|
const op = s.opacity ?? 100;
|
|
151
159
|
return op >= 100 ? base : `color-mix(in srgb, ${base} ${Math.round(op)}%, transparent)`;
|
|
152
|
-
});
|
|
160
|
+
}));
|
|
153
161
|
</script>
|
|
154
162
|
|
|
155
163
|
{#if gradient}
|
|
156
164
|
<div class="gradient-editor">
|
|
157
165
|
<div class="ribbon-wrap">
|
|
166
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
158
167
|
<div
|
|
159
168
|
class="ribbon"
|
|
160
169
|
bind:this={barEl}
|
|
161
170
|
style="background: var({variable});"
|
|
162
|
-
|
|
171
|
+
onclick={onRibbonClick}
|
|
163
172
|
role="button"
|
|
164
173
|
tabindex="-1"
|
|
165
174
|
aria-label="Click to add a gradient stop"
|
|
@@ -172,10 +181,10 @@
|
|
|
172
181
|
class:selected={selected === i}
|
|
173
182
|
class:dragging={dragIndex === i}
|
|
174
183
|
style="left: {stop.position}%; --stop-color: {stopSwatches[i]};"
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
184
|
+
onpointerdown={(e) => onHandleDown(e, i)}
|
|
185
|
+
onpointermove={onHandleMove}
|
|
186
|
+
onpointerup={onHandleUp}
|
|
187
|
+
onpointercancel={onHandleUp}
|
|
179
188
|
title={`Stop ${i + 1} (${stop.position}%)`}
|
|
180
189
|
aria-label={`Gradient stop ${i + 1}`}
|
|
181
190
|
>
|
|
@@ -190,24 +199,24 @@
|
|
|
190
199
|
<button
|
|
191
200
|
type="button"
|
|
192
201
|
class:active={gradient.type === 'linear'}
|
|
193
|
-
|
|
202
|
+
onclick={() => setType('linear')}
|
|
194
203
|
>Linear</button>
|
|
195
204
|
<button
|
|
196
205
|
type="button"
|
|
197
206
|
class:active={gradient.type === 'radial'}
|
|
198
|
-
|
|
207
|
+
onclick={() => setType('radial')}
|
|
199
208
|
>Radial</button>
|
|
200
209
|
</div>
|
|
201
210
|
{#if gradient.type === 'linear'}
|
|
202
211
|
<div class="angle-slot">
|
|
203
|
-
<AngleDial value={gradient.angle}
|
|
212
|
+
<AngleDial value={gradient.angle} onchange={onAngleChange} />
|
|
204
213
|
</div>
|
|
205
214
|
{/if}
|
|
206
215
|
<div class="spacer"></div>
|
|
207
216
|
<button
|
|
208
217
|
type="button"
|
|
209
218
|
class="ghost-btn"
|
|
210
|
-
|
|
219
|
+
onclick={addStop}
|
|
211
220
|
title="Add stop"
|
|
212
221
|
>
|
|
213
222
|
<i class="fas fa-plus"></i> Add stop
|
|
@@ -215,7 +224,7 @@
|
|
|
215
224
|
<button
|
|
216
225
|
type="button"
|
|
217
226
|
class="ghost-btn"
|
|
218
|
-
|
|
227
|
+
onclick={removeSelected}
|
|
219
228
|
disabled={gradient.stops.length <= 2}
|
|
220
229
|
title={gradient.stops.length <= 2 ? 'Gradient needs at least two stops' : 'Remove selected stop'}
|
|
221
230
|
>
|
|
@@ -233,7 +242,7 @@
|
|
|
233
242
|
max="100"
|
|
234
243
|
step="0.1"
|
|
235
244
|
value={gradient.stops[selected].position}
|
|
236
|
-
|
|
245
|
+
onchange={onPositionInput}
|
|
237
246
|
/>
|
|
238
247
|
<span class="suffix">%</span>
|
|
239
248
|
</label>
|
|
@@ -242,15 +251,15 @@
|
|
|
242
251
|
stopId={`${variable}-${selected}`}
|
|
243
252
|
color={gradient.stops[selected].color}
|
|
244
253
|
opacity={gradient.stops[selected].opacity ?? 100}
|
|
245
|
-
|
|
254
|
+
onchange={(payload) => handleStopChange(selected, payload)}
|
|
246
255
|
/>
|
|
247
256
|
</div>
|
|
248
257
|
</div>
|
|
249
258
|
{/if}
|
|
250
259
|
|
|
251
260
|
<div class="footer-row">
|
|
252
|
-
<button type="button" class="ghost-btn"
|
|
253
|
-
<button type="button" class="primary-btn"
|
|
261
|
+
<button type="button" class="ghost-btn" onclick={cancel}>Cancel</button>
|
|
262
|
+
<button type="button" class="primary-btn" onclick={save}>Save</button>
|
|
254
263
|
</div>
|
|
255
264
|
</div>
|
|
256
265
|
{/if}
|
|
@@ -5,18 +5,20 @@
|
|
|
5
5
|
* to that scratch var get parsed back out and forwarded as a structured update
|
|
6
6
|
* to gradient state, so we don't have to refactor UIPaletteSelector itself.
|
|
7
7
|
*/
|
|
8
|
-
import { onDestroy, createEventDispatcher } from 'svelte';
|
|
9
8
|
import UIPaletteSelector from './UIPaletteSelector.svelte';
|
|
10
9
|
import { setCssVar, removeCssVar } from '../lib/cssVarSync';
|
|
11
10
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
interface Props {
|
|
12
|
+
stopId: string; // unique key (e.g. gradient-var + stop index)
|
|
13
|
+
color: string; // token name like '--color-brand-500'
|
|
14
|
+
opacity?: number; // 0–100
|
|
15
|
+
onchange?: (payload: { color: string; opacity: number }) => void;
|
|
16
|
+
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
let { stopId, color, opacity = 100, onchange }: Props = $props();
|
|
17
19
|
|
|
18
20
|
/** Scratch var the embedded picker reads/writes; isolated per stop. */
|
|
19
|
-
|
|
21
|
+
let scratchVar = $derived(`--__grad-stop-${stopId}`);
|
|
20
22
|
|
|
21
23
|
function buildScratchValue(c: string, o: number): string {
|
|
22
24
|
const base = c.startsWith('--') ? `var(${c})` : c;
|
|
@@ -39,36 +41,23 @@
|
|
|
39
41
|
return null;
|
|
40
42
|
}
|
|
41
43
|
|
|
42
|
-
// Seed
|
|
43
|
-
//
|
|
44
|
-
|
|
44
|
+
// Seed (and re-seed on external updates like undo/redo) the scratch var so
|
|
45
|
+
// UIPaletteSelector reads the current stop value. The effect's cleanup runs
|
|
46
|
+
// on dependency change AND unmount, so it removes the right key even if
|
|
47
|
+
// stopId changes mid-life.
|
|
48
|
+
$effect(() => {
|
|
45
49
|
setCssVar(scratchVar, buildScratchValue(color, opacity));
|
|
46
|
-
|
|
50
|
+
const key = scratchVar;
|
|
51
|
+
return () => removeCssVar(key);
|
|
52
|
+
});
|
|
47
53
|
|
|
48
54
|
function handleChange() {
|
|
49
55
|
const raw = document.documentElement.style.getPropertyValue(scratchVar);
|
|
50
56
|
const parsed = parseScratch(raw);
|
|
51
57
|
if (!parsed) return;
|
|
52
58
|
if (parsed.color === color && parsed.opacity === opacity) return;
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
onDestroy(() => {
|
|
57
|
-
removeCssVar(scratchVar);
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
// When external state updates the stop (undo/redo, sibling-stop edits),
|
|
61
|
-
// refresh the scratch so the picker reflects current values.
|
|
62
|
-
let lastSynced = `${color}|${opacity}`;
|
|
63
|
-
$: {
|
|
64
|
-
const sig = `${color}|${opacity}`;
|
|
65
|
-
if (sig !== lastSynced) {
|
|
66
|
-
lastSynced = sig;
|
|
67
|
-
if (typeof document !== 'undefined') {
|
|
68
|
-
setCssVar(scratchVar, buildScratchValue(color, opacity));
|
|
69
|
-
}
|
|
70
|
-
}
|
|
59
|
+
onchange?.(parsed);
|
|
71
60
|
}
|
|
72
61
|
</script>
|
|
73
62
|
|
|
74
|
-
<UIPaletteSelector variable={scratchVar}
|
|
63
|
+
<UIPaletteSelector variable={scratchVar} onchange={handleChange} />
|