@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
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/* Editor form controls — `--ui-*` tokens only. Theme-immune; see CONVENTIONS.md. */
|
|
2
|
+
|
|
3
|
+
/* ========================================
|
|
4
|
+
Form Field Layouts
|
|
5
|
+
======================================== */
|
|
6
|
+
|
|
7
|
+
/* Vertical Layout - Label Above Input/Select */
|
|
8
|
+
/* Usage: Add .ui-form-field-vertical to container div wrapping label + input/select */
|
|
9
|
+
.ui-form-field-vertical {
|
|
10
|
+
display: flex;
|
|
11
|
+
flex-direction: column;
|
|
12
|
+
align-items: stretch;
|
|
13
|
+
gap: var(--ui-space-4);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.ui-form-label {
|
|
17
|
+
margin-bottom: 0;
|
|
18
|
+
font-size: var(--ui-font-size-md);
|
|
19
|
+
color: var(--ui-text-primary);
|
|
20
|
+
font-weight: var(--ui-font-weight-normal);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/* Horizontal Layout - Label Beside Input/Select */
|
|
24
|
+
/* Usage: Add .ui-form-field-horizontal to container div wrapping label + input/select */
|
|
25
|
+
.ui-form-field-horizontal {
|
|
26
|
+
display: flex;
|
|
27
|
+
justify-content: space-between;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: var(--ui-space-8);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.ui-form-field-horizontal .ui-form-label {
|
|
33
|
+
white-space: nowrap;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/* Inline Layout - Label and Input/Select Close Together */
|
|
37
|
+
/* Usage: Add .ui-form-field-inline to container div wrapping label + input/select */
|
|
38
|
+
.ui-form-field-inline {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
gap: var(--ui-space-12);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.ui-form-field-inline .ui-form-label {
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
flex-shrink: 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.ui-form-field-inline .ui-form-select,
|
|
50
|
+
.ui-form-field-inline .ui-form-input {
|
|
51
|
+
flex-shrink: 0;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/* ========================================
|
|
55
|
+
Form Control Styling
|
|
56
|
+
======================================== */
|
|
57
|
+
|
|
58
|
+
/* Select/Dropdown Styling */
|
|
59
|
+
.ui-form-select {
|
|
60
|
+
/* No vertical padding - let min-height and line-height center text naturally */
|
|
61
|
+
padding: 0 var(--ui-space-16);
|
|
62
|
+
min-height: 2.375rem; /* ~38px to match button height */
|
|
63
|
+
background: var(--ui-surface-lowest) !important;
|
|
64
|
+
border: 1px solid var(--ui-border-default) !important;
|
|
65
|
+
border-radius: var(--ui-radius-md);
|
|
66
|
+
color: var(--ui-text-primary) !important;
|
|
67
|
+
font-family: var(--ui-font-sans);
|
|
68
|
+
font-size: var(--ui-font-size-md);
|
|
69
|
+
font-weight: var(--ui-font-weight-normal);
|
|
70
|
+
line-height: var(--ui-line-height-normal);
|
|
71
|
+
vertical-align: middle;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
transition: all var(--ui-transition-fast);
|
|
74
|
+
/* Prevent clipping */
|
|
75
|
+
overflow: visible !important;
|
|
76
|
+
box-sizing: border-box !important;
|
|
77
|
+
/* Reset browser defaults */
|
|
78
|
+
-webkit-appearance: none;
|
|
79
|
+
-moz-appearance: none;
|
|
80
|
+
appearance: none;
|
|
81
|
+
/* Custom dropdown arrow */
|
|
82
|
+
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 12 12'%3E%3Cpath fill='%23888' d='M6 8L1 3h10z'/%3E%3C/svg%3E") !important;
|
|
83
|
+
background-repeat: no-repeat !important;
|
|
84
|
+
background-position: right var(--ui-space-12) center !important;
|
|
85
|
+
padding-right: var(--ui-space-32) !important;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.ui-form-select:hover:not(:disabled) {
|
|
89
|
+
background-color: var(--ui-surface-low) !important;
|
|
90
|
+
border-color: var(--ui-border-medium) !important;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
.ui-form-select:focus {
|
|
94
|
+
outline: none;
|
|
95
|
+
border-color: var(--ui-border-strong) !important;
|
|
96
|
+
box-shadow: 0 0 0 2px hsla(0, 58%, 50%, 0.2);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.ui-form-select:focus-visible {
|
|
100
|
+
outline: 2px solid var(--ui-highlight);
|
|
101
|
+
outline-offset: 2px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.ui-form-select:active:not(:disabled) {
|
|
105
|
+
background-color: var(--ui-surface) !important;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.ui-form-select:disabled {
|
|
109
|
+
background-color: var(--ui-surface-lowest) !important;
|
|
110
|
+
border-color: var(--ui-border-faint) !important;
|
|
111
|
+
color: var(--ui-text-disabled) !important;
|
|
112
|
+
cursor: not-allowed;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/* Option Styling - Critical for Legibility */
|
|
116
|
+
/* Note: Most option styling is controlled by browser/OS and cannot be fully overridden */
|
|
117
|
+
/* These styles apply where browsers allow (limited support in Chrome/Firefox) */
|
|
118
|
+
.ui-form-select option {
|
|
119
|
+
background-color: var(--ui-surface-lowest) !important;
|
|
120
|
+
color: var(--ui-text-primary) !important;
|
|
121
|
+
padding: var(--ui-space-8) var(--ui-space-12);
|
|
122
|
+
min-height: 2rem;
|
|
123
|
+
font-size: var(--ui-font-size-md);
|
|
124
|
+
font-family: var(--ui-font-sans);
|
|
125
|
+
line-height: var(--ui-line-height-normal);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* Disabled options */
|
|
129
|
+
.ui-form-select option:disabled {
|
|
130
|
+
color: var(--ui-text-disabled) !important;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* Input Field Styling */
|
|
134
|
+
.ui-form-input {
|
|
135
|
+
padding: var(--ui-space-8);
|
|
136
|
+
background: var(--ui-surface-lowest);
|
|
137
|
+
border: 1px solid var(--ui-border-default);
|
|
138
|
+
border-radius: var(--ui-radius-md);
|
|
139
|
+
color: var(--ui-text-primary);
|
|
140
|
+
font-family: var(--ui-font-sans);
|
|
141
|
+
font-size: var(--ui-font-size-md);
|
|
142
|
+
transition: border-color var(--ui-transition-fast);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
.ui-form-input:hover:not(:disabled) {
|
|
146
|
+
border-color: var(--ui-border-strong);
|
|
147
|
+
background: var(--ui-surface-low);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
.ui-form-input:focus {
|
|
151
|
+
outline: none;
|
|
152
|
+
border-color: var(--ui-border-strong);
|
|
153
|
+
box-shadow: 0 0 0 3px hsla(240, 5%, 38%, 0.2);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.ui-form-input:disabled {
|
|
157
|
+
background: var(--ui-surface-lowest);
|
|
158
|
+
border-color: var(--ui-border-faint);
|
|
159
|
+
color: var(--ui-text-disabled);
|
|
160
|
+
cursor: not-allowed;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* Placeholder text styling */
|
|
164
|
+
.ui-form-input::placeholder {
|
|
165
|
+
color: var(--ui-text-muted);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/* Number input spinner buttons */
|
|
169
|
+
.ui-form-input[type="number"]::-webkit-inner-spin-button,
|
|
170
|
+
.ui-form-input[type="number"]::-webkit-outer-spin-button {
|
|
171
|
+
opacity: 1;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/* Checkbox and Radio Styling */
|
|
175
|
+
.ui-form-checkbox,
|
|
176
|
+
.ui-form-radio {
|
|
177
|
+
cursor: pointer;
|
|
178
|
+
width: var(--ui-space-16);
|
|
179
|
+
height: var(--ui-space-16);
|
|
180
|
+
accent-color: var(--ui-gray-600);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.ui-form-checkbox:disabled,
|
|
184
|
+
.ui-form-radio:disabled {
|
|
185
|
+
cursor: not-allowed;
|
|
186
|
+
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
+
import { stopPropagation } from 'svelte/legacy';
|
|
3
|
+
|
|
2
4
|
import {
|
|
3
5
|
type CurveAnchor, type CurveConfig,
|
|
4
6
|
CURVE_H, CURVE_PAD_Y, CURVE_Y_PAD,
|
|
@@ -7,15 +9,29 @@
|
|
|
7
9
|
serializeCurve, deserializeCurve,
|
|
8
10
|
} from './curveEngine';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
12
|
+
interface Props {
|
|
13
|
+
anchors: CurveAnchor[];
|
|
14
|
+
cfg: CurveConfig;
|
|
15
|
+
stepCount: number;
|
|
16
|
+
padX?: number;
|
|
17
|
+
offset?: number;
|
|
18
|
+
defaultAnchors?: CurveAnchor[] | null;
|
|
19
|
+
lockedAnchorIndex?: number | null;
|
|
20
|
+
onAnchorsChange?: (anchors: CurveAnchor[]) => void;
|
|
21
|
+
onOffsetChange?: (offset: number) => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
let {
|
|
25
|
+
anchors,
|
|
26
|
+
cfg,
|
|
27
|
+
stepCount,
|
|
28
|
+
padX = 0,
|
|
29
|
+
offset = 0,
|
|
30
|
+
defaultAnchors = null,
|
|
31
|
+
lockedAnchorIndex = null,
|
|
32
|
+
onAnchorsChange = () => {},
|
|
33
|
+
onOffsetChange = () => {}
|
|
34
|
+
}: Props = $props();
|
|
19
35
|
|
|
20
36
|
function resetToDefault() {
|
|
21
37
|
if (!defaultAnchors) return;
|
|
@@ -24,14 +40,14 @@
|
|
|
24
40
|
}
|
|
25
41
|
|
|
26
42
|
const CURVE_W_DEFAULT = 720;
|
|
27
|
-
let svgEl: SVGSVGElement | null = null;
|
|
28
|
-
let dims = CURVE_W_DEFAULT;
|
|
29
|
-
let shiftActive = false;
|
|
43
|
+
let svgEl: SVGSVGElement | null = $state(null);
|
|
44
|
+
let dims = $state(CURVE_W_DEFAULT);
|
|
45
|
+
let shiftActive = $state(false);
|
|
30
46
|
|
|
31
47
|
const clipId = `curve-clip-${Math.random().toString(36).slice(2, 8)}`;
|
|
32
48
|
|
|
33
|
-
|
|
34
|
-
|
|
49
|
+
let w = $derived(dims);
|
|
50
|
+
let offsetPx = $derived(-(offset / ((cfg.yMax - cfg.yMin) * (1 + 2 * CURVE_Y_PAD))) * (CURVE_H - 2 * CURVE_PAD_Y));
|
|
35
51
|
|
|
36
52
|
function stepToX(index: number): number {
|
|
37
53
|
return stepCount > 1 ? (index / (stepCount - 1)) * 100 : 50;
|
|
@@ -281,21 +297,21 @@
|
|
|
281
297
|
<!-- Curve content group — offset vertically, clipped -->
|
|
282
298
|
<g transform="translate(0,{offsetPx})" clip-path="url(#{clipId})">
|
|
283
299
|
{#if shiftActive}
|
|
284
|
-
<!-- svelte-ignore
|
|
300
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
285
301
|
<rect
|
|
286
302
|
x="0" y={-CURVE_H} width={w} height={CURVE_H * 3}
|
|
287
303
|
class="shift-overlay"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
304
|
+
onpointerdown={handleShiftPointerDown}
|
|
305
|
+
onpointermove={handleShiftPointerMove}
|
|
306
|
+
onpointerup={handleShiftPointerUp}
|
|
291
307
|
/>
|
|
292
308
|
{:else}
|
|
293
|
-
<!-- svelte-ignore
|
|
294
|
-
<!-- svelte-ignore
|
|
309
|
+
<!-- svelte-ignore a11y_click_events_have_key_events -->
|
|
310
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
295
311
|
<path
|
|
296
312
|
d={buildCurvePath(anchors, cfg, w, padX)}
|
|
297
313
|
class="curve-hit"
|
|
298
|
-
|
|
314
|
+
onclick={insertPointOnPath}
|
|
299
315
|
/>
|
|
300
316
|
{/if}
|
|
301
317
|
|
|
@@ -311,13 +327,13 @@
|
|
|
311
327
|
x2={curveXToSvg(pt.x + pt.inDx, w, padX)} y2={curveYToSvg(pt.y + pt.inDy, cfg)}
|
|
312
328
|
class="handle-line"
|
|
313
329
|
/>
|
|
314
|
-
<!-- svelte-ignore
|
|
330
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
315
331
|
<circle
|
|
316
332
|
cx={curveXToSvg(pt.x + pt.inDx, w, padX)} cy={curveYToSvg(pt.y + pt.inDy, cfg)}
|
|
317
333
|
r="3.5" class="handle-grip"
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
334
|
+
onpointerdown={(e) => handlePointerDown(e, { kind: 'handleIn', index: i })}
|
|
335
|
+
onpointermove={handlePointerMove}
|
|
336
|
+
onpointerup={handlePointerUp}
|
|
321
337
|
/>
|
|
322
338
|
{/if}
|
|
323
339
|
{#if i < anchors.length - 1 && !isCornerAnchor(pt)}
|
|
@@ -326,16 +342,16 @@
|
|
|
326
342
|
x2={curveXToSvg(pt.x + pt.outDx, w, padX)} y2={curveYToSvg(pt.y + pt.outDy, cfg)}
|
|
327
343
|
class="handle-line"
|
|
328
344
|
/>
|
|
329
|
-
<!-- svelte-ignore
|
|
345
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
330
346
|
<circle
|
|
331
347
|
cx={curveXToSvg(pt.x + pt.outDx, w, padX)} cy={curveYToSvg(pt.y + pt.outDy, cfg)}
|
|
332
348
|
r="3.5" class="handle-grip"
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
349
|
+
onpointerdown={(e) => handlePointerDown(e, { kind: 'handleOut', index: i })}
|
|
350
|
+
onpointermove={handlePointerMove}
|
|
351
|
+
onpointerup={handlePointerUp}
|
|
336
352
|
/>
|
|
337
353
|
{/if}
|
|
338
|
-
<!-- svelte-ignore
|
|
354
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
339
355
|
{#if i === lockedAnchorIndex}
|
|
340
356
|
<path
|
|
341
357
|
d="M{curveXToSvg(pt.x, w, padX)},{curveYToSvg(pt.y, cfg) - 6} l5,6 l-5,6 l-5,-6 Z"
|
|
@@ -346,19 +362,19 @@
|
|
|
346
362
|
x={curveXToSvg(pt.x, w, padX) - 4} y={curveYToSvg(pt.y, cfg) - 4}
|
|
347
363
|
width="8" height="8"
|
|
348
364
|
class="curve-handle corner"
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
365
|
+
onpointerdown={(e) => handlePointerDown(e, { kind: 'anchor', index: i })}
|
|
366
|
+
onpointermove={handlePointerMove}
|
|
367
|
+
onpointerup={handlePointerUp}
|
|
368
|
+
ondblclick={stopPropagation(() => toggleAnchorSmooth(i))}
|
|
353
369
|
/>
|
|
354
370
|
{:else}
|
|
355
371
|
<circle
|
|
356
372
|
cx={curveXToSvg(pt.x, w, padX)} cy={curveYToSvg(pt.y, cfg)}
|
|
357
373
|
r="5" class="curve-handle"
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
374
|
+
onpointerdown={(e) => handlePointerDown(e, { kind: 'anchor', index: i })}
|
|
375
|
+
onpointermove={handlePointerMove}
|
|
376
|
+
onpointerup={handlePointerUp}
|
|
377
|
+
ondblclick={stopPropagation(() => toggleAnchorSmooth(i))}
|
|
362
378
|
/>
|
|
363
379
|
{/if}
|
|
364
380
|
{/each}
|
|
@@ -373,7 +389,7 @@
|
|
|
373
389
|
class:active={shiftActive}
|
|
374
390
|
type="button"
|
|
375
391
|
title="Vertical offset"
|
|
376
|
-
|
|
392
|
+
onclick={() => shiftActive = !shiftActive}
|
|
377
393
|
>
|
|
378
394
|
<svg viewBox="0 0 12 20" class="curve-tool-icon">
|
|
379
395
|
<path d="M6,2 L10,7 L7,7 L7,13 L10,13 L6,18 L2,13 L5,13 L5,7 L2,7 Z" />
|
|
@@ -381,8 +397,8 @@
|
|
|
381
397
|
<span>Offset{offset !== 0 ? ` ${offset > 0 ? '+' : ''}${offset}` : ''}</span>
|
|
382
398
|
</button>
|
|
383
399
|
<span class="curve-hint">&x2325;-click to remove point</span>
|
|
384
|
-
<button class="curve-tool-btn" type="button" title="Copy curve"
|
|
385
|
-
<button class="curve-tool-btn" type="button" title="Paste curve"
|
|
400
|
+
<button class="curve-tool-btn" type="button" title="Copy curve" onclick={copyToClipboard}>Copy</button>
|
|
401
|
+
<button class="curve-tool-btn" type="button" title="Paste curve" onclick={pasteFromClipboard}>Paste</button>
|
|
386
402
|
</div>
|
|
387
403
|
<div class="curve-templates">
|
|
388
404
|
{#each curveTemplates as tpl}
|
|
@@ -390,7 +406,7 @@
|
|
|
390
406
|
class="curve-template-btn"
|
|
391
407
|
type="button"
|
|
392
408
|
title={tpl.name}
|
|
393
|
-
|
|
409
|
+
onclick={() => applyTemplate(tpl)}
|
|
394
410
|
>
|
|
395
411
|
<svg viewBox="0 0 20 12" class="curve-template-icon">
|
|
396
412
|
<path d={tpl.icon} />
|
|
@@ -402,7 +418,7 @@
|
|
|
402
418
|
class="curve-tool-btn"
|
|
403
419
|
type="button"
|
|
404
420
|
title="Reset to default"
|
|
405
|
-
|
|
421
|
+
onclick={resetToDefault}
|
|
406
422
|
>Reset</button>
|
|
407
423
|
{/if}
|
|
408
424
|
</div>
|
|
@@ -3,25 +3,46 @@
|
|
|
3
3
|
import InlineEditActions from '../components/InlineEditActions.svelte';
|
|
4
4
|
import Button from '../components/Button.svelte';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
color: string;
|
|
11
|
+
title?: string | null;
|
|
12
|
+
showRemoveOverride?: boolean;
|
|
13
|
+
onColorChange?: (hex: string) => void;
|
|
14
|
+
onConfirm?: () => void;
|
|
15
|
+
onCancel?: () => void;
|
|
16
|
+
onRemoveOverride?: () => void;
|
|
17
|
+
/**
|
|
14
18
|
* Optional pointerdown hook for slider drags — lets parents open a store
|
|
15
19
|
* transaction so the whole drag collapses to one undo step. If the parent
|
|
16
20
|
* doesn't route slider writes through the editor store, leave this unset.
|
|
17
21
|
*/
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
onSliderStart?: () => void;
|
|
23
|
+
// Hue-chroma mode props (for neutral/gray base editing)
|
|
24
|
+
mode?: 'hsl' | 'hue-chroma';
|
|
25
|
+
hue?: number;
|
|
26
|
+
chroma?: number;
|
|
27
|
+
onHueChromaChange?: (hue: number, chroma: number) => void;
|
|
28
|
+
actions?: import('svelte').Snippet;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let {
|
|
32
|
+
color,
|
|
33
|
+
title = null,
|
|
34
|
+
showRemoveOverride = false,
|
|
35
|
+
onColorChange = () => {},
|
|
36
|
+
onConfirm = () => {},
|
|
37
|
+
onCancel = () => {},
|
|
38
|
+
onRemoveOverride = () => {},
|
|
39
|
+
onSliderStart = () => {},
|
|
40
|
+
mode = 'hsl',
|
|
41
|
+
hue = 0,
|
|
42
|
+
chroma = 0.04,
|
|
43
|
+
onHueChromaChange = () => {},
|
|
44
|
+
actions
|
|
45
|
+
}: Props = $props();
|
|
25
46
|
|
|
26
47
|
const hasEyeDropper = typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
27
48
|
const PREVIEW_LIGHTNESS = 0.55;
|
|
@@ -59,7 +80,7 @@
|
|
|
59
80
|
|
|
60
81
|
// --- HSL mode reactives ---
|
|
61
82
|
|
|
62
|
-
|
|
83
|
+
let hsl = $derived(hexToHsl(color));
|
|
63
84
|
|
|
64
85
|
function hueGrad(s: number, l: number): string {
|
|
65
86
|
return `linear-gradient(to right, ${
|
|
@@ -83,11 +104,11 @@
|
|
|
83
104
|
|
|
84
105
|
// --- Hue-chroma mode reactives ---
|
|
85
106
|
|
|
86
|
-
|
|
107
|
+
let previewHex = $derived(mode === 'hue-chroma'
|
|
87
108
|
? (() => { const c = gamutClamp(PREVIEW_LIGHTNESS, chroma, hue); return oklchToHex(c.l, c.c, c.h); })()
|
|
88
|
-
: color;
|
|
109
|
+
: color);
|
|
89
110
|
|
|
90
|
-
|
|
111
|
+
let hueGradient = $derived((() => {
|
|
91
112
|
const _c = chroma;
|
|
92
113
|
const displayChroma = Math.max(_c, CHROMA_MAX);
|
|
93
114
|
const stops = Array.from({ length: 13 }, (_, i) => {
|
|
@@ -96,9 +117,9 @@
|
|
|
96
117
|
return oklchToHex(c.l, c.c, c.h);
|
|
97
118
|
});
|
|
98
119
|
return `linear-gradient(to right, ${stops.join(',')})`;
|
|
99
|
-
})();
|
|
120
|
+
})());
|
|
100
121
|
|
|
101
|
-
|
|
122
|
+
let chromaGradient = $derived((() => {
|
|
102
123
|
const _h = hue;
|
|
103
124
|
const stops = Array.from({ length: 8 }, (_, i) => {
|
|
104
125
|
const c = (i / 7) * CHROMA_MAX;
|
|
@@ -106,7 +127,7 @@
|
|
|
106
127
|
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
107
128
|
});
|
|
108
129
|
return `linear-gradient(to right, ${stops.join(',')})`;
|
|
109
|
-
})();
|
|
130
|
+
})());
|
|
110
131
|
|
|
111
132
|
// --- Shared ---
|
|
112
133
|
|
|
@@ -127,8 +148,8 @@
|
|
|
127
148
|
}
|
|
128
149
|
}
|
|
129
150
|
|
|
130
|
-
let hexEditing = false;
|
|
131
|
-
let hexDraft = '';
|
|
151
|
+
let hexEditing = $state(false);
|
|
152
|
+
let hexDraft = $state('');
|
|
132
153
|
|
|
133
154
|
function startHexEdit() {
|
|
134
155
|
hexDraft = previewHex;
|
|
@@ -165,7 +186,7 @@
|
|
|
165
186
|
class="eyedropper-btn"
|
|
166
187
|
type="button"
|
|
167
188
|
title="Pick color from screen"
|
|
168
|
-
|
|
189
|
+
onclick={pickScreenColor}
|
|
169
190
|
><i class="fas fa-eye-dropper"></i></button>
|
|
170
191
|
{/if}
|
|
171
192
|
{#if title}
|
|
@@ -176,20 +197,20 @@
|
|
|
176
197
|
class="hsl-hex-input"
|
|
177
198
|
type="text"
|
|
178
199
|
bind:value={hexDraft}
|
|
179
|
-
|
|
180
|
-
|
|
200
|
+
onkeydown={handleHexKeydown}
|
|
201
|
+
onblur={commitHex}
|
|
181
202
|
maxlength="7"
|
|
182
203
|
use:autoFocus
|
|
183
204
|
/>
|
|
184
205
|
{:else}
|
|
185
|
-
<button class="hsl-hex"
|
|
206
|
+
<button class="hsl-hex" onclick={startHexEdit} title="Click to edit hex">{previewHex}</button>
|
|
186
207
|
{/if}
|
|
187
208
|
{#if mode === 'hue-chroma'}
|
|
188
209
|
<code class="hsl-values">oklch({PREVIEW_LIGHTNESS}, {chroma.toFixed(3)}, {Math.round(hue)})</code>
|
|
189
210
|
{:else}
|
|
190
211
|
<code class="hsl-values">hsl({hsl[0]}, {hsl[1]}%, {hsl[2]}%)</code>
|
|
191
212
|
{/if}
|
|
192
|
-
|
|
213
|
+
{@render actions?.()}
|
|
193
214
|
<div class="hsl-panel-actions">
|
|
194
215
|
{#if showRemoveOverride}
|
|
195
216
|
<Button
|
|
@@ -211,9 +232,10 @@
|
|
|
211
232
|
{#if mode === 'hue-chroma'}
|
|
212
233
|
<div class="hsl-slider-row">
|
|
213
234
|
<span class="hsl-slider-label">H</span>
|
|
214
|
-
|
|
235
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
236
|
+
<div class="slider-track" style="background: {hueGradient}" onpointerdown={onSliderStart}>
|
|
215
237
|
<input type="range" min="0" max="360" value={hue}
|
|
216
|
-
|
|
238
|
+
oninput={(e) => onHueChromaChange(+e.currentTarget.value, chroma)} />
|
|
217
239
|
</div>
|
|
218
240
|
<input
|
|
219
241
|
class="hsl-slider-input"
|
|
@@ -221,14 +243,15 @@
|
|
|
221
243
|
min="0"
|
|
222
244
|
max="360"
|
|
223
245
|
value={hue}
|
|
224
|
-
|
|
246
|
+
onchange={(e) => onHueChromaChange(Math.min(360, Math.max(0, +e.currentTarget.value)), chroma)}
|
|
225
247
|
/><span class="hsl-slider-unit">°</span>
|
|
226
248
|
</div>
|
|
227
249
|
<div class="hsl-slider-row">
|
|
228
250
|
<span class="hsl-slider-label">C</span>
|
|
229
|
-
|
|
251
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
252
|
+
<div class="slider-track" style="background: {chromaGradient}" onpointerdown={onSliderStart}>
|
|
230
253
|
<input type="range" min="0" max={CHROMA_MAX} step="0.001" value={chroma}
|
|
231
|
-
|
|
254
|
+
oninput={(e) => onHueChromaChange(hue, +e.currentTarget.value)} />
|
|
232
255
|
</div>
|
|
233
256
|
<input
|
|
234
257
|
class="hsl-slider-input chroma-input"
|
|
@@ -237,15 +260,16 @@
|
|
|
237
260
|
max={CHROMA_MAX}
|
|
238
261
|
step="0.001"
|
|
239
262
|
value={chroma.toFixed(3)}
|
|
240
|
-
|
|
263
|
+
onchange={(e) => onHueChromaChange(hue, Math.min(CHROMA_MAX, Math.max(0, +e.currentTarget.value)))}
|
|
241
264
|
/>
|
|
242
265
|
</div>
|
|
243
266
|
{:else}
|
|
244
267
|
<div class="hsl-slider-row">
|
|
245
268
|
<span class="hsl-slider-label">H</span>
|
|
246
|
-
|
|
269
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
270
|
+
<div class="slider-track" style="background: {hueGrad(hsl[1], hsl[2])}" onpointerdown={onSliderStart}>
|
|
247
271
|
<input type="range" min="0" max="360" value={hsl[0]}
|
|
248
|
-
|
|
272
|
+
oninput={(e) => updateHsl(0, +e.currentTarget.value)} />
|
|
249
273
|
</div>
|
|
250
274
|
<input
|
|
251
275
|
class="hsl-slider-input"
|
|
@@ -253,14 +277,15 @@
|
|
|
253
277
|
min="0"
|
|
254
278
|
max="360"
|
|
255
279
|
value={hsl[0]}
|
|
256
|
-
|
|
280
|
+
onchange={(e) => updateHsl(0, Math.min(360, Math.max(0, +e.currentTarget.value)))}
|
|
257
281
|
/><span class="hsl-slider-unit">°</span>
|
|
258
282
|
</div>
|
|
259
283
|
<div class="hsl-slider-row">
|
|
260
284
|
<span class="hsl-slider-label">S</span>
|
|
261
|
-
|
|
285
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
286
|
+
<div class="slider-track" style="background: {satGrad(hsl[0], hsl[2])}" onpointerdown={onSliderStart}>
|
|
262
287
|
<input type="range" min="0" max="100" value={hsl[1]}
|
|
263
|
-
|
|
288
|
+
oninput={(e) => updateHsl(1, +e.currentTarget.value)} />
|
|
264
289
|
</div>
|
|
265
290
|
<input
|
|
266
291
|
class="hsl-slider-input"
|
|
@@ -268,14 +293,15 @@
|
|
|
268
293
|
min="0"
|
|
269
294
|
max="100"
|
|
270
295
|
value={hsl[1]}
|
|
271
|
-
|
|
296
|
+
onchange={(e) => updateHsl(1, Math.min(100, Math.max(0, +e.currentTarget.value)))}
|
|
272
297
|
/><span class="hsl-slider-unit">%</span>
|
|
273
298
|
</div>
|
|
274
299
|
<div class="hsl-slider-row">
|
|
275
300
|
<span class="hsl-slider-label">L</span>
|
|
276
|
-
|
|
301
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
302
|
+
<div class="slider-track" style="background: {lightGrad(hsl[0], hsl[1])}" onpointerdown={onSliderStart}>
|
|
277
303
|
<input type="range" min="0" max="100" value={hsl[2]}
|
|
278
|
-
|
|
304
|
+
oninput={(e) => updateHsl(2, +e.currentTarget.value)} />
|
|
279
305
|
</div>
|
|
280
306
|
<input
|
|
281
307
|
class="hsl-slider-input"
|
|
@@ -283,7 +309,7 @@
|
|
|
283
309
|
min="0"
|
|
284
310
|
max="100"
|
|
285
311
|
value={hsl[2]}
|
|
286
|
-
|
|
312
|
+
onchange={(e) => updateHsl(2, Math.min(100, Math.max(0, +e.currentTarget.value)))}
|
|
287
313
|
/><span class="hsl-slider-unit">%</span>
|
|
288
314
|
</div>
|
|
289
315
|
{/if}
|
|
@@ -477,6 +503,7 @@
|
|
|
477
503
|
border-radius: var(--ui-radius-sm);
|
|
478
504
|
padding: var(--ui-space-2) var(--ui-space-4);
|
|
479
505
|
-moz-appearance: textfield;
|
|
506
|
+
appearance: textfield;
|
|
480
507
|
}
|
|
481
508
|
|
|
482
509
|
.hsl-slider-input.chroma-input {
|