@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.
Files changed (107) hide show
  1. package/README.md +47 -4
  2. package/package.json +18 -12
  3. package/src/component-editor/BadgeEditor.svelte +24 -22
  4. package/src/component-editor/CalloutEditor.svelte +3 -3
  5. package/src/component-editor/CardEditor.svelte +25 -21
  6. package/src/component-editor/CollapsibleSectionEditor.svelte +27 -25
  7. package/src/component-editor/CornerBadgeEditor.svelte +37 -35
  8. package/src/component-editor/DialogEditor.svelte +26 -24
  9. package/src/component-editor/ImageEditor.svelte +11 -9
  10. package/src/component-editor/InlineEditActionsEditor.svelte +17 -15
  11. package/src/component-editor/NotificationEditor.svelte +32 -30
  12. package/src/component-editor/ProgressBarEditor.svelte +3 -3
  13. package/src/component-editor/RadioButtonEditor.svelte +31 -29
  14. package/src/component-editor/SectionDividerEditor.svelte +30 -28
  15. package/src/component-editor/SegmentedControlEditor.svelte +29 -25
  16. package/src/component-editor/StandardButtonsEditor.svelte +42 -38
  17. package/src/component-editor/TabBarEditor.svelte +20 -18
  18. package/src/component-editor/TableEditor.svelte +4 -4
  19. package/src/component-editor/TooltipEditor.svelte +11 -9
  20. package/src/component-editor/registry.ts +2 -2
  21. package/src/component-editor/scaffolding/AngleDial.svelte +20 -19
  22. package/src/component-editor/scaffolding/ComponentEditorBase.svelte +44 -20
  23. package/src/component-editor/scaffolding/ComponentFileManager.svelte +262 -38
  24. package/src/component-editor/scaffolding/ComponentFileMenu.svelte +41 -29
  25. package/src/component-editor/scaffolding/ComponentsTab.svelte +7 -3
  26. package/src/component-editor/scaffolding/CopyFromMenu.svelte +21 -12
  27. package/src/component-editor/scaffolding/DemoHeader.svelte +13 -4
  28. package/src/component-editor/scaffolding/DividerEditor.svelte +27 -14
  29. package/src/component-editor/scaffolding/FieldsetWrapper.svelte +10 -4
  30. package/src/component-editor/scaffolding/GradientCard.svelte +25 -20
  31. package/src/component-editor/scaffolding/LinkageChart.svelte +43 -34
  32. package/src/component-editor/scaffolding/LinkedBlock.svelte +24 -21
  33. package/src/component-editor/scaffolding/NonStylableConfig.svelte +6 -1
  34. package/src/component-editor/scaffolding/SaveAsDialog.svelte +39 -35
  35. package/src/component-editor/scaffolding/ShadowBackdrop.svelte +21 -9
  36. package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +8 -3
  37. package/src/component-editor/scaffolding/StateBlock.svelte +30 -13
  38. package/src/component-editor/scaffolding/TokenLayout.svelte +46 -30
  39. package/src/component-editor/scaffolding/TypeEditor.svelte +52 -26
  40. package/src/component-editor/scaffolding/VariantGroup.svelte +81 -48
  41. package/src/component-editor/scaffolding/componentSectionType.ts +2 -2
  42. package/src/components/Badge.svelte +45 -26
  43. package/src/components/Button.svelte +44 -21
  44. package/src/components/Callout.svelte +17 -12
  45. package/src/components/Card.svelte +23 -11
  46. package/src/components/CollapsibleSection.svelte +56 -27
  47. package/src/components/CornerBadge.svelte +32 -18
  48. package/src/components/Dialog.svelte +55 -31
  49. package/src/components/Image.svelte +14 -5
  50. package/src/components/InlineEditActions.svelte +22 -10
  51. package/src/components/Notification.svelte +39 -19
  52. package/src/components/ProgressBar.svelte +27 -17
  53. package/src/components/RadioButton.svelte +27 -10
  54. package/src/components/SectionDivider.svelte +34 -26
  55. package/src/components/SegmentedControl.svelte +23 -9
  56. package/src/components/TabBar.svelte +23 -10
  57. package/src/components/Table.svelte +8 -3
  58. package/src/components/Tooltip.svelte +15 -5
  59. package/src/lib/ColumnsOverlay.svelte +3 -3
  60. package/src/lib/LiveEditorOverlay.svelte +57 -36
  61. package/src/pages/ComponentEditorPage.svelte +25 -14
  62. package/src/pages/Editor.svelte +8 -2
  63. package/src/pages/EditorShell.svelte +24 -20
  64. package/src/styles/site.css +138 -0
  65. package/src/styles/tokens.css +78 -76
  66. package/src/styles/ui-form-controls.css +186 -0
  67. package/src/ui/BezierCurveEditor.svelte +59 -43
  68. package/src/ui/ColorEditPanel.svelte +71 -44
  69. package/src/ui/EditorViewSwitcher.svelte +9 -5
  70. package/src/ui/FontStackEditor.svelte +17 -16
  71. package/src/ui/GradientEditor.svelte +42 -33
  72. package/src/ui/GradientStopPicker.svelte +18 -29
  73. package/src/ui/PaletteEditor.svelte +238 -212
  74. package/src/ui/PresetFileManager.svelte +20 -18
  75. package/src/ui/ProjectFontsSection.svelte +34 -34
  76. package/src/ui/SurfacesTab.svelte +3 -3
  77. package/src/ui/TextTab.svelte +2 -2
  78. package/src/ui/ThemeFileManager.svelte +38 -35
  79. package/src/ui/Toggle.svelte +11 -9
  80. package/src/ui/UICopyPopover.svelte +19 -15
  81. package/src/ui/UIDialog.svelte +48 -30
  82. package/src/ui/UIFontFamilySelector.svelte +104 -78
  83. package/src/ui/UIFontSizeSelector.svelte +38 -20
  84. package/src/ui/UIFontWeightSelector.svelte +33 -13
  85. package/src/ui/UILineHeightSelector.svelte +33 -13
  86. package/src/ui/UILinkToggle.svelte +7 -6
  87. package/src/ui/UIOptionItem.svelte +21 -7
  88. package/src/ui/UIOptionList.svelte +9 -3
  89. package/src/ui/UIPaddingSelector.svelte +108 -82
  90. package/src/ui/UIPaletteSelector.svelte +186 -161
  91. package/src/ui/UIRadio.svelte +23 -8
  92. package/src/ui/UIRadioGroup.svelte +9 -8
  93. package/src/ui/UIRelinkConfirmPopover.svelte +26 -16
  94. package/src/ui/UITokenSelector.svelte +112 -68
  95. package/src/ui/UIVariantSelector.svelte +79 -57
  96. package/src/ui/VariablesTab.svelte +15 -15
  97. package/src/ui/palette/GradientStopEditor.svelte +45 -26
  98. package/src/ui/palette/OverridesPanel.svelte +85 -49
  99. package/src/ui/palette/PaletteBase.svelte +60 -32
  100. package/src/ui/palette/ScaleCurveEditor.svelte +25 -10
  101. package/src/ui/sections/ColumnsSection.svelte +13 -13
  102. package/src/ui/sections/GradientsSection.svelte +12 -9
  103. package/src/ui/sections/OverlaysSection.svelte +50 -47
  104. package/src/ui/sections/ShadowsSection.svelte +110 -104
  105. package/src/ui/sections/TokenScaleTable.svelte +38 -22
  106. package/src/ui/sections/tokenScales.ts +2 -2
  107. 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
- export let anchors: CurveAnchor[];
11
- export let cfg: CurveConfig;
12
- export let stepCount: number;
13
- export let padX: number = 0;
14
- export let offset: number = 0;
15
- export let defaultAnchors: CurveAnchor[] | null = null;
16
- export let lockedAnchorIndex: number | null = null;
17
- export let onAnchorsChange: (anchors: CurveAnchor[]) => void = () => {};
18
- export let onOffsetChange: (offset: number) => void = () => {};
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
- $: w = dims;
34
- $: offsetPx = -(offset / ((cfg.yMax - cfg.yMin) * (1 + 2 * CURVE_Y_PAD))) * (CURVE_H - 2 * CURVE_PAD_Y);
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 a11y-no-static-element-interactions -->
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
- on:pointerdown={handleShiftPointerDown}
289
- on:pointermove={handleShiftPointerMove}
290
- on:pointerup={handleShiftPointerUp}
304
+ onpointerdown={handleShiftPointerDown}
305
+ onpointermove={handleShiftPointerMove}
306
+ onpointerup={handleShiftPointerUp}
291
307
  />
292
308
  {:else}
293
- <!-- svelte-ignore a11y-click-events-have-key-events -->
294
- <!-- svelte-ignore a11y-no-static-element-interactions -->
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
- on:click={insertPointOnPath}
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 a11y-no-static-element-interactions -->
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
- on:pointerdown={(e) => handlePointerDown(e, { kind: 'handleIn', index: i })}
319
- on:pointermove={handlePointerMove}
320
- on:pointerup={handlePointerUp}
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 a11y-no-static-element-interactions -->
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
- on:pointerdown={(e) => handlePointerDown(e, { kind: 'handleOut', index: i })}
334
- on:pointermove={handlePointerMove}
335
- on:pointerup={handlePointerUp}
349
+ onpointerdown={(e) => handlePointerDown(e, { kind: 'handleOut', index: i })}
350
+ onpointermove={handlePointerMove}
351
+ onpointerup={handlePointerUp}
336
352
  />
337
353
  {/if}
338
- <!-- svelte-ignore a11y-no-static-element-interactions -->
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
- on:pointerdown={(e) => handlePointerDown(e, { kind: 'anchor', index: i })}
350
- on:pointermove={handlePointerMove}
351
- on:pointerup={handlePointerUp}
352
- on:dblclick|stopPropagation={() => toggleAnchorSmooth(i)}
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
- on:pointerdown={(e) => handlePointerDown(e, { kind: 'anchor', index: i })}
359
- on:pointermove={handlePointerMove}
360
- on:pointerup={handlePointerUp}
361
- on:dblclick|stopPropagation={() => toggleAnchorSmooth(i)}
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
- on:click={() => shiftActive = !shiftActive}
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" on:click={copyToClipboard}>Copy</button>
385
- <button class="curve-tool-btn" type="button" title="Paste curve" on:click={pasteFromClipboard}>Paste</button>
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
- on:click={() => applyTemplate(tpl)}
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
- on:click={resetToDefault}
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
- export let color: string;
7
- export let title: string | null = null;
8
- export let showRemoveOverride: boolean = false;
9
- export let onColorChange: (hex: string) => void = () => {};
10
- export let onConfirm: () => void = () => {};
11
- export let onCancel: () => void = () => {};
12
- export let onRemoveOverride: () => void = () => {};
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
- export let onSliderStart: () => void = () => {};
19
-
20
- // Hue-chroma mode props (for neutral/gray base editing)
21
- export let mode: 'hsl' | 'hue-chroma' = 'hsl';
22
- export let hue: number = 0;
23
- export let chroma: number = 0.04;
24
- export let onHueChromaChange: (hue: number, chroma: number) => void = () => {};
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
- $: hsl = hexToHsl(color);
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
- $: previewHex = mode === 'hue-chroma'
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
- $: hueGradient = (() => {
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
- $: chromaGradient = (() => {
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
- on:click={pickScreenColor}
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
- on:keydown={handleHexKeydown}
180
- on:blur={commitHex}
200
+ onkeydown={handleHexKeydown}
201
+ onblur={commitHex}
181
202
  maxlength="7"
182
203
  use:autoFocus
183
204
  />
184
205
  {:else}
185
- <button class="hsl-hex" on:click={startHexEdit} title="Click to edit hex">{previewHex}</button>
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
- <slot name="actions" />
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
- <div class="slider-track" style="background: {hueGradient}" on:pointerdown={onSliderStart}>
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
- on:input={(e) => onHueChromaChange(+e.currentTarget.value, chroma)} />
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
- on:change={(e) => onHueChromaChange(Math.min(360, Math.max(0, +e.currentTarget.value)), chroma)}
246
+ onchange={(e) => onHueChromaChange(Math.min(360, Math.max(0, +e.currentTarget.value)), chroma)}
225
247
  /><span class="hsl-slider-unit">&deg;</span>
226
248
  </div>
227
249
  <div class="hsl-slider-row">
228
250
  <span class="hsl-slider-label">C</span>
229
- <div class="slider-track" style="background: {chromaGradient}" on:pointerdown={onSliderStart}>
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
- on:input={(e) => onHueChromaChange(hue, +e.currentTarget.value)} />
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
- on:change={(e) => onHueChromaChange(hue, Math.min(CHROMA_MAX, Math.max(0, +e.currentTarget.value)))}
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
- <div class="slider-track" style="background: {hueGrad(hsl[1], hsl[2])}" on:pointerdown={onSliderStart}>
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
- on:input={(e) => updateHsl(0, +e.currentTarget.value)} />
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
- on:change={(e) => updateHsl(0, Math.min(360, Math.max(0, +e.currentTarget.value)))}
280
+ onchange={(e) => updateHsl(0, Math.min(360, Math.max(0, +e.currentTarget.value)))}
257
281
  /><span class="hsl-slider-unit">&deg;</span>
258
282
  </div>
259
283
  <div class="hsl-slider-row">
260
284
  <span class="hsl-slider-label">S</span>
261
- <div class="slider-track" style="background: {satGrad(hsl[0], hsl[2])}" on:pointerdown={onSliderStart}>
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
- on:input={(e) => updateHsl(1, +e.currentTarget.value)} />
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
- on:change={(e) => updateHsl(1, Math.min(100, Math.max(0, +e.currentTarget.value)))}
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
- <div class="slider-track" style="background: {lightGrad(hsl[0], hsl[1])}" on:pointerdown={onSliderStart}>
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
- on:input={(e) => updateHsl(2, +e.currentTarget.value)} />
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
- on:change={(e) => updateHsl(2, Math.min(100, Math.max(0, +e.currentTarget.value)))}
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 {