@motion-proto/live-tokens 0.38.0 → 0.40.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/.claude/skills/live-tokens-pick-component/SKILL.md +9 -1
- package/CHANGELOG.md +71 -0
- package/dist-plugin/index.cjs +20 -72
- package/dist-plugin/index.js +20 -72
- package/package.json +1 -1
- package/src/editor/component-editor/IconButtonEditor.svelte +175 -0
- package/src/editor/component-editor/SectionDividerEditor.svelte +1 -1
- package/src/editor/component-editor/registry.ts +11 -0
- package/src/editor/core/palettes/paletteDerivation.ts +28 -83
- package/src/editor/core/store/editorStore.ts +2 -1
- package/src/editor/core/store/gradientSource.ts +49 -9
- package/src/editor/core/themes/migrations/2026-06-05-palette-unification.ts +90 -0
- package/src/editor/core/themes/themeTypes.ts +3 -8
- package/src/editor/docs/content/01-overview.md +3 -2
- package/src/editor/docs/content.generated.ts +1 -1
- package/src/editor/ui/ColorEditPanel.svelte +93 -172
- package/src/editor/ui/PaletteEditor.svelte +46 -207
- package/src/editor/ui/VariablesTab.svelte +2 -2
- package/src/editor/ui/palette/PaletteBase.svelte +29 -37
- package/src/editor/ui/palette/paletteEditorState.ts +12 -25
- package/src/editor/ui/palette/paletteMath.ts +22 -49
- package/src/live-tokens/data/themes/default.json +11 -391
- package/src/live-tokens/data/tokens.generated.css +14 -14
- package/src/system/components/IconButton.svelte +322 -0
|
@@ -7,10 +7,8 @@
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
|
-
color: string;
|
|
11
10
|
title?: string | null;
|
|
12
11
|
showRemoveOverride?: boolean;
|
|
13
|
-
onColorChange?: (hex: string) => void;
|
|
14
12
|
onConfirm?: () => void;
|
|
15
13
|
onCancel?: () => void;
|
|
16
14
|
onRemoveOverride?: () => void;
|
|
@@ -20,110 +18,67 @@
|
|
|
20
18
|
* doesn't route slider writes through the editor store, leave this unset.
|
|
21
19
|
*/
|
|
22
20
|
onSliderStart?: () => void;
|
|
23
|
-
// Hue-chroma mode props (for neutral/gray base editing)
|
|
24
|
-
mode?: 'hsl' | 'hue-chroma';
|
|
25
21
|
hue?: number;
|
|
26
22
|
chroma?: number;
|
|
27
|
-
|
|
23
|
+
lightness?: number;
|
|
24
|
+
/** Upper bound of the chroma slider (full sRGB gamut by default). */
|
|
25
|
+
chromaMax?: number;
|
|
26
|
+
/** Optional marker on the chroma track flagging the typical-neutral zone. */
|
|
27
|
+
chromaHint?: number;
|
|
28
|
+
onHueChromaChange?: (hue: number, chroma: number, lightness: number) => void;
|
|
28
29
|
actions?: import('svelte').Snippet;
|
|
29
30
|
}
|
|
30
31
|
|
|
31
32
|
let {
|
|
32
|
-
color,
|
|
33
33
|
title = null,
|
|
34
34
|
showRemoveOverride = false,
|
|
35
|
-
onColorChange = () => {},
|
|
36
35
|
onConfirm = () => {},
|
|
37
36
|
onCancel = () => {},
|
|
38
37
|
onRemoveOverride = () => {},
|
|
39
38
|
onSliderStart = () => {},
|
|
40
|
-
mode = 'hsl',
|
|
41
39
|
hue = 0,
|
|
42
40
|
chroma = 0.04,
|
|
41
|
+
lightness = 55,
|
|
42
|
+
chromaMax = 0.4,
|
|
43
|
+
chromaHint,
|
|
43
44
|
onHueChromaChange = () => {},
|
|
44
45
|
actions
|
|
45
46
|
}: Props = $props();
|
|
46
47
|
|
|
47
48
|
const hasEyeDropper = typeof window !== 'undefined' && 'EyeDropper' in window;
|
|
48
|
-
const PREVIEW_LIGHTNESS = 0.55;
|
|
49
|
-
const CHROMA_MAX = 0.15;
|
|
50
|
-
|
|
51
|
-
// --- HSL helpers (used in hsl mode) ---
|
|
52
|
-
|
|
53
|
-
function hexToHsl(hex: string): [number, number, number] {
|
|
54
|
-
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
55
|
-
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
56
|
-
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
57
|
-
const max = Math.max(r, g, b), min = Math.min(r, g, b);
|
|
58
|
-
let h = 0, s = 0;
|
|
59
|
-
const l = (max + min) / 2;
|
|
60
|
-
if (max !== min) {
|
|
61
|
-
const d = max - min;
|
|
62
|
-
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
|
63
|
-
if (max === r) h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
|
|
64
|
-
else if (max === g) h = ((b - r) / d + 2) / 6;
|
|
65
|
-
else h = ((r - g) / d + 4) / 6;
|
|
66
|
-
}
|
|
67
|
-
return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function hslToHex(h: number, s: number, l: number): string {
|
|
71
|
-
s /= 100; l /= 100;
|
|
72
|
-
const a = s * Math.min(l, 1 - l);
|
|
73
|
-
const f = (n: number) => {
|
|
74
|
-
const k = (n + h / 30) % 12;
|
|
75
|
-
const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
|
|
76
|
-
return Math.round(255 * color).toString(16).padStart(2, '0');
|
|
77
|
-
};
|
|
78
|
-
return `#${f(0)}${f(8)}${f(4)}`;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// --- HSL mode reactives ---
|
|
82
|
-
|
|
83
|
-
let hsl = $derived(hexToHsl(color));
|
|
84
|
-
|
|
85
|
-
function hueGrad(s: number, l: number): string {
|
|
86
|
-
return `linear-gradient(to right, ${
|
|
87
|
-
[0, 60, 120, 180, 240, 300, 360].map(h => `hsl(${h},${s}%,${l}%)`).join(',')
|
|
88
|
-
})`;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function satGrad(h: number, l: number): string {
|
|
92
|
-
return `linear-gradient(to right, hsl(${h},0%,${l}%), hsl(${h},100%,${l}%))`;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function lightGrad(h: number, s: number): string {
|
|
96
|
-
return `linear-gradient(to right, hsl(${h},${s}%,0%), hsl(${h},${s}%,50%), hsl(${h},${s}%,100%))`;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function updateHsl(component: 0 | 1 | 2, value: number) {
|
|
100
|
-
const current = hexToHsl(color);
|
|
101
|
-
current[component] = value;
|
|
102
|
-
onColorChange(hslToHex(current[0], current[1], current[2]));
|
|
103
|
-
}
|
|
104
49
|
|
|
105
|
-
|
|
50
|
+
let lPct = $derived(lightness);
|
|
106
51
|
|
|
107
|
-
let previewHex = $derived(
|
|
108
|
-
|
|
109
|
-
|
|
52
|
+
let previewHex = $derived((() => {
|
|
53
|
+
const c = gamutClamp(lPct / 100, chroma, hue);
|
|
54
|
+
return oklchToHex(c.l, c.c, c.h);
|
|
55
|
+
})());
|
|
110
56
|
|
|
111
57
|
let hueGradient = $derived((() => {
|
|
112
|
-
const
|
|
113
|
-
const displayChroma = Math.max(
|
|
58
|
+
const _l = lPct / 100;
|
|
59
|
+
const displayChroma = Math.max(chroma, chromaMax);
|
|
114
60
|
const stops = Array.from({ length: 13 }, (_, i) => {
|
|
115
61
|
const h = (i / 12) * 360;
|
|
116
|
-
const c = gamutClamp(
|
|
62
|
+
const c = gamutClamp(_l, displayChroma, h);
|
|
117
63
|
return oklchToHex(c.l, c.c, c.h);
|
|
118
64
|
});
|
|
119
65
|
return `linear-gradient(to right, ${stops.join(',')})`;
|
|
120
66
|
})());
|
|
121
67
|
|
|
122
68
|
let chromaGradient = $derived((() => {
|
|
123
|
-
const _h = hue;
|
|
69
|
+
const _h = hue, _l = lPct / 100;
|
|
124
70
|
const stops = Array.from({ length: 8 }, (_, i) => {
|
|
125
|
-
const c = (i / 7) *
|
|
126
|
-
const clamped = gamutClamp(
|
|
71
|
+
const c = (i / 7) * chromaMax;
|
|
72
|
+
const clamped = gamutClamp(_l, c, _h);
|
|
73
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
74
|
+
});
|
|
75
|
+
return `linear-gradient(to right, ${stops.join(',')})`;
|
|
76
|
+
})());
|
|
77
|
+
|
|
78
|
+
let lightnessGradient = $derived((() => {
|
|
79
|
+
const _h = hue, _c = chroma;
|
|
80
|
+
const stops = Array.from({ length: 9 }, (_, i) => {
|
|
81
|
+
const clamped = gamutClamp(i / 8, _c, _h);
|
|
127
82
|
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
128
83
|
});
|
|
129
84
|
return `linear-gradient(to right, ${stops.join(',')})`;
|
|
@@ -137,12 +92,8 @@
|
|
|
137
92
|
const dropper = new (window as any).EyeDropper();
|
|
138
93
|
const result = await dropper.open();
|
|
139
94
|
const hex = result.sRGBHex.toLowerCase();
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
onHueChromaChange(Math.round(oklch.h), Math.round(oklch.c * 1000) / 1000);
|
|
143
|
-
} else {
|
|
144
|
-
onColorChange(hex);
|
|
145
|
-
}
|
|
95
|
+
const oklch = hexToOklch(hex);
|
|
96
|
+
onHueChromaChange(Math.round(oklch.h), Math.round(oklch.c * 1000) / 1000, oklch.l * 100);
|
|
146
97
|
} catch {
|
|
147
98
|
// user cancelled the eyedropper
|
|
148
99
|
}
|
|
@@ -162,12 +113,8 @@
|
|
|
162
113
|
const v = hexDraft.startsWith('#') ? hexDraft : `#${hexDraft}`;
|
|
163
114
|
if (/^#[0-9a-f]{6}$/i.test(v)) {
|
|
164
115
|
const hex = v.toLowerCase();
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
onHueChromaChange(Math.round(oklch.h), Math.round(oklch.c * 1000) / 1000);
|
|
168
|
-
} else {
|
|
169
|
-
onColorChange(hex);
|
|
170
|
-
}
|
|
116
|
+
const oklch = hexToOklch(hex);
|
|
117
|
+
onHueChromaChange(Math.round(oklch.h), Math.round(oklch.c * 1000) / 1000, oklch.l * 100);
|
|
171
118
|
}
|
|
172
119
|
hexEditing = false;
|
|
173
120
|
}
|
|
@@ -205,11 +152,7 @@
|
|
|
205
152
|
{:else}
|
|
206
153
|
<button class="hsl-hex" onclick={startHexEdit} title="Click to edit hex">{previewHex}</button>
|
|
207
154
|
{/if}
|
|
208
|
-
{
|
|
209
|
-
<code class="hsl-values">oklch({PREVIEW_LIGHTNESS}, {chroma.toFixed(3)}, {Math.round(hue)})</code>
|
|
210
|
-
{:else}
|
|
211
|
-
<code class="hsl-values">hsl({hsl[0]}, {hsl[1]}%, {hsl[2]}%)</code>
|
|
212
|
-
{/if}
|
|
155
|
+
<code class="hsl-values">oklch({(lPct / 100).toFixed(2)}, {chroma.toFixed(3)}, {Math.round(hue)})</code>
|
|
213
156
|
{@render actions?.()}
|
|
214
157
|
<div class="hsl-panel-actions">
|
|
215
158
|
{#if showRemoveOverride}
|
|
@@ -229,90 +172,58 @@
|
|
|
229
172
|
</div>
|
|
230
173
|
</div>
|
|
231
174
|
<div class="hsl-sliders">
|
|
232
|
-
|
|
233
|
-
<
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
<
|
|
237
|
-
|
|
238
|
-
oninput={(e) => onHueChromaChange(+e.currentTarget.value, chroma)} />
|
|
239
|
-
</div>
|
|
240
|
-
<input
|
|
241
|
-
class="hsl-slider-input"
|
|
242
|
-
type="number"
|
|
243
|
-
min="0"
|
|
244
|
-
max="360"
|
|
245
|
-
value={hue}
|
|
246
|
-
onchange={(e) => onHueChromaChange(Math.min(360, Math.max(0, +e.currentTarget.value)), chroma)}
|
|
247
|
-
/><span class="hsl-slider-unit">°</span>
|
|
248
|
-
</div>
|
|
249
|
-
<div class="hsl-slider-row">
|
|
250
|
-
<span class="hsl-slider-label">C</span>
|
|
251
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
252
|
-
<div class="slider-track" style="background: {chromaGradient}" onpointerdown={onSliderStart}>
|
|
253
|
-
<input type="range" min="0" max={CHROMA_MAX} step="0.001" value={chroma}
|
|
254
|
-
oninput={(e) => onHueChromaChange(hue, +e.currentTarget.value)} />
|
|
255
|
-
</div>
|
|
256
|
-
<input
|
|
257
|
-
class="hsl-slider-input chroma-input"
|
|
258
|
-
type="number"
|
|
259
|
-
min="0"
|
|
260
|
-
max={CHROMA_MAX}
|
|
261
|
-
step="0.001"
|
|
262
|
-
value={chroma.toFixed(3)}
|
|
263
|
-
onchange={(e) => onHueChromaChange(hue, Math.min(CHROMA_MAX, Math.max(0, +e.currentTarget.value)))}
|
|
264
|
-
/>
|
|
175
|
+
<div class="hsl-slider-row">
|
|
176
|
+
<span class="hsl-slider-label">H</span>
|
|
177
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
178
|
+
<div class="slider-track" style="background: {hueGradient}" onpointerdown={onSliderStart}>
|
|
179
|
+
<input type="range" min="0" max="360" value={hue}
|
|
180
|
+
oninput={(e) => onHueChromaChange(+e.currentTarget.value, chroma, lPct)} />
|
|
265
181
|
</div>
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
<span class="hsl-slider-label">S</span>
|
|
285
|
-
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
286
|
-
<div class="slider-track" style="background: {satGrad(hsl[0], hsl[2])}" onpointerdown={onSliderStart}>
|
|
287
|
-
<input type="range" min="0" max="100" value={hsl[1]}
|
|
288
|
-
oninput={(e) => updateHsl(1, +e.currentTarget.value)} />
|
|
289
|
-
</div>
|
|
290
|
-
<input
|
|
291
|
-
class="hsl-slider-input"
|
|
292
|
-
type="number"
|
|
293
|
-
min="0"
|
|
294
|
-
max="100"
|
|
295
|
-
value={hsl[1]}
|
|
296
|
-
onchange={(e) => updateHsl(1, Math.min(100, Math.max(0, +e.currentTarget.value)))}
|
|
297
|
-
/><span class="hsl-slider-unit">%</span>
|
|
182
|
+
<input
|
|
183
|
+
class="hsl-slider-input"
|
|
184
|
+
type="number"
|
|
185
|
+
min="0"
|
|
186
|
+
max="360"
|
|
187
|
+
value={hue}
|
|
188
|
+
onchange={(e) => onHueChromaChange(Math.min(360, Math.max(0, +e.currentTarget.value)), chroma, lPct)}
|
|
189
|
+
/><span class="hsl-slider-unit">°</span>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="hsl-slider-row">
|
|
192
|
+
<span class="hsl-slider-label">C</span>
|
|
193
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
194
|
+
<div class="slider-track" style="background: {chromaGradient}" onpointerdown={onSliderStart}>
|
|
195
|
+
{#if chromaHint !== undefined && chromaHint < chromaMax}
|
|
196
|
+
<div class="chroma-hint" style="left: {(chromaHint / chromaMax) * 100}%" title="Typical neutral range"></div>
|
|
197
|
+
{/if}
|
|
198
|
+
<input type="range" min="0" max={chromaMax} step="0.001" value={chroma}
|
|
199
|
+
oninput={(e) => onHueChromaChange(hue, +e.currentTarget.value, lPct)} />
|
|
298
200
|
</div>
|
|
299
|
-
<
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
201
|
+
<input
|
|
202
|
+
class="hsl-slider-input chroma-input"
|
|
203
|
+
type="number"
|
|
204
|
+
min="0"
|
|
205
|
+
max={chromaMax}
|
|
206
|
+
step="0.001"
|
|
207
|
+
value={chroma.toFixed(3)}
|
|
208
|
+
onchange={(e) => onHueChromaChange(hue, Math.min(chromaMax, Math.max(0, +e.currentTarget.value)), lPct)}
|
|
209
|
+
/>
|
|
210
|
+
</div>
|
|
211
|
+
<div class="hsl-slider-row">
|
|
212
|
+
<span class="hsl-slider-label">L</span>
|
|
213
|
+
<!-- svelte-ignore a11y_no_static_element_interactions -->
|
|
214
|
+
<div class="slider-track" style="background: {lightnessGradient}" onpointerdown={onSliderStart}>
|
|
215
|
+
<input type="range" min="0" max="100" value={lPct}
|
|
216
|
+
oninput={(e) => onHueChromaChange(hue, chroma, +e.currentTarget.value)} />
|
|
314
217
|
</div>
|
|
315
|
-
|
|
218
|
+
<input
|
|
219
|
+
class="hsl-slider-input"
|
|
220
|
+
type="number"
|
|
221
|
+
min="0"
|
|
222
|
+
max="100"
|
|
223
|
+
value={Math.round(lPct)}
|
|
224
|
+
onchange={(e) => onHueChromaChange(hue, chroma, Math.min(100, Math.max(0, +e.currentTarget.value)))}
|
|
225
|
+
/><span class="hsl-slider-unit">%</span>
|
|
226
|
+
</div>
|
|
316
227
|
</div>
|
|
317
228
|
</div>
|
|
318
229
|
|
|
@@ -443,6 +354,16 @@
|
|
|
443
354
|
min-width: 6rem;
|
|
444
355
|
}
|
|
445
356
|
|
|
357
|
+
.chroma-hint {
|
|
358
|
+
position: absolute;
|
|
359
|
+
top: -2px;
|
|
360
|
+
bottom: -2px;
|
|
361
|
+
width: 1px;
|
|
362
|
+
background: var(--ui-text-secondary);
|
|
363
|
+
opacity: 0.5;
|
|
364
|
+
pointer-events: none;
|
|
365
|
+
}
|
|
366
|
+
|
|
446
367
|
.slider-track input[type="range"] {
|
|
447
368
|
-webkit-appearance: none;
|
|
448
369
|
appearance: none;
|