@motion-proto/live-tokens 0.55.0 → 0.55.1
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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.55.1 — The anchored step is the base color
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Editing the anchored palette step edits the base color.** The anchor pins
|
|
8
|
+
the lightness, saturation and hue curves through one step, so that swatch was
|
|
9
|
+
already rendering the base color. Clicking it opened a per-step override,
|
|
10
|
+
which forked the two apart: the ramp moved, the family's base swatch and hex
|
|
11
|
+
did not, and the palette stopped passing through the base color the anchor
|
|
12
|
+
guarantees it passes through. That step now opens the base color itself. The
|
|
13
|
+
whole ramp re-derives, a bound harmony axis follows the hue, and the header
|
|
14
|
+
swatch moves with it. Both swatches carry the selection ring together and the
|
|
15
|
+
panel titles itself "Base Color > 500", so the pairing is stated rather than
|
|
16
|
+
inferred. An override left on the anchored step by an earlier build is cleared
|
|
17
|
+
when the step is opened, inside the edit session, so Cancel and undo both put
|
|
18
|
+
it back. Every other step keeps its own override, unchanged.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- **The color editor docks under the row it edits.** It rendered at the bottom
|
|
23
|
+
of the family block, past the curve editors and the Text, Surfaces and Borders
|
|
24
|
+
section, far enough from the swatch that opened it to read as unrelated
|
|
25
|
+
chrome. It now opens directly beneath the ramp for a palette step, or beneath
|
|
26
|
+
the derived row that owns the step, with a caret on the step's own column. The
|
|
27
|
+
caret is placed on the grid rather than positioned, so it stays under its
|
|
28
|
+
swatch at any width. Showing and hiding use the disclosure motion the
|
|
29
|
+
expandable sections already use.
|
|
30
|
+
|
|
31
|
+
- **Selection reads by weight.** The editor's chrome is greyscale by design, so
|
|
32
|
+
a selected swatch is marked by a heavy ring drawn inside it: a white band
|
|
33
|
+
between two dark keylines, which survives both ends of a ramp running white to
|
|
34
|
+
black where a single-tone ring disappears at one end. The ring is inset rather
|
|
35
|
+
than an outline, which would close the gutter between neighbouring swatches,
|
|
36
|
+
or a thicker border, which would resize the swatch inside its own grid.
|
|
37
|
+
|
|
3
38
|
## 0.55.0 — A focused palette arrives ready
|
|
4
39
|
|
|
5
40
|
### Changed
|
package/package.json
CHANGED
|
@@ -8,6 +8,9 @@
|
|
|
8
8
|
|
|
9
9
|
interface Props {
|
|
10
10
|
title?: string | null;
|
|
11
|
+
/** Ties the panel to the swatch it was opened from, when it renders docked
|
|
12
|
+
* beneath that swatch's row. */
|
|
13
|
+
highlighted?: boolean;
|
|
11
14
|
showRemoveOverride?: boolean;
|
|
12
15
|
/** Hide the confirm/cancel actions for live-apply consumers (Colors view
|
|
13
16
|
* readout) where edits commit through the store as they happen. */
|
|
@@ -37,6 +40,7 @@
|
|
|
37
40
|
|
|
38
41
|
let {
|
|
39
42
|
title = null,
|
|
43
|
+
highlighted = false,
|
|
40
44
|
showRemoveOverride = false,
|
|
41
45
|
hideActions = false,
|
|
42
46
|
hidePreview = false,
|
|
@@ -133,7 +137,7 @@
|
|
|
133
137
|
}
|
|
134
138
|
</script>
|
|
135
139
|
|
|
136
|
-
<div class="hsl-panel">
|
|
140
|
+
<div class="hsl-panel" class:highlighted>
|
|
137
141
|
<div class="hsl-panel-header">
|
|
138
142
|
{#if !hidePreview}
|
|
139
143
|
<div class="hsl-preview" style="background: {previewHex}"></div>
|
|
@@ -250,6 +254,17 @@
|
|
|
250
254
|
border-radius: var(--ui-radius-md);
|
|
251
255
|
}
|
|
252
256
|
|
|
257
|
+
/* Docked to a swatch: the border thickens to the weight of that swatch's
|
|
258
|
+
selection ring, so the pair reads as one object. */
|
|
259
|
+
.hsl-panel.highlighted {
|
|
260
|
+
border-width: 2px;
|
|
261
|
+
border-color: var(--ui-border-higher);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
.hsl-panel.highlighted .hsl-panel-title {
|
|
265
|
+
color: var(--ui-text-primary);
|
|
266
|
+
}
|
|
267
|
+
|
|
253
268
|
.hsl-panel-header {
|
|
254
269
|
display: flex;
|
|
255
270
|
align-items: center;
|
|
@@ -207,8 +207,19 @@
|
|
|
207
207
|
openSession();
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
-
function handlePaletteClick(ps: { label: string;
|
|
210
|
+
function handlePaletteClick(ps: { label: string; index: number; anchored: boolean }) {
|
|
211
211
|
const k = paletteStepKey(ps.label);
|
|
212
|
+
// The anchored step is not a copy of the base color, it IS the base color:
|
|
213
|
+
// the anchor pins the curves through it. So editing it edits the base, and
|
|
214
|
+
// the header swatch and the whole ramp follow. An override here would fork
|
|
215
|
+
// the two apart and quietly break the guarantee the anchor makes.
|
|
216
|
+
if (ps.anchored) {
|
|
217
|
+
if (isEditingBase) { confirmEdit(); return; }
|
|
218
|
+
startBaseEdit();
|
|
219
|
+
// After openSession, so cancel and undo both put it back.
|
|
220
|
+
if (k in overrides) removeOverride(k);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
212
223
|
if (editingKey === k) {
|
|
213
224
|
confirmEdit();
|
|
214
225
|
return;
|
|
@@ -505,6 +516,10 @@
|
|
|
505
516
|
let overridesHex = $derived(Object.fromEntries(Object.entries(overrides).map(([k, v]) => [k, toHex(v)])));
|
|
506
517
|
|
|
507
518
|
let isEditingBase = $derived(isBaseEdit(editing));
|
|
519
|
+
// Naming the step the base sits on is what says the header swatch and that
|
|
520
|
+
// ramp swatch are one value, not two that happen to match.
|
|
521
|
+
let anchoredStep = $derived(paletteComputed.find(ps => ps.anchored) ?? null);
|
|
522
|
+
let basePanelTitle = $derived(anchoredStep ? `Base Color \u203A ${anchoredStep.label}` : 'Base Color');
|
|
508
523
|
let editingColor = $derived(isEditingBase ? baseColor : editingDraft);
|
|
509
524
|
let editingStepInfo = $derived((() => {
|
|
510
525
|
if (!editingKey || isEditingBase) return null;
|
|
@@ -520,11 +535,28 @@
|
|
|
520
535
|
return null;
|
|
521
536
|
})());
|
|
522
537
|
let panelOpen = $derived(editingKey !== null && (isEditingBase || (editingDraft !== null && editingStepInfo !== null)));
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
538
|
+
// The panel docks under the row that owns the step being edited, so these
|
|
539
|
+
// place it: a column for the palette caret, a row slot for the derived scales.
|
|
540
|
+
let editingPaletteIndex = $derived.by(() => {
|
|
541
|
+
if (!panelOpen || isEditingBase) return null;
|
|
542
|
+
const i = paletteComputed.findIndex(ps => ps.key === editingKey);
|
|
543
|
+
return i === -1 ? null : i;
|
|
544
|
+
});
|
|
545
|
+
let editingDerivedRow = $derived.by(() => {
|
|
546
|
+
if (!panelOpen || isEditingBase || editingPaletteIndex !== null) return null;
|
|
547
|
+
const scale = scales.find(s => s.title === editingStepInfo?.scale);
|
|
548
|
+
if (!scale) return null;
|
|
549
|
+
return scale.isText ? 'text' : 'solid';
|
|
550
|
+
});
|
|
551
|
+
let editPanelTitle = $derived(editingStepInfo ? `${editingStepInfo.scale} \u203A ${editingStepInfo.step}` : null);
|
|
552
|
+
// The reveal's outro needs something to render: closing the panel resets
|
|
553
|
+
// `editing` to idle, and every value below it goes null in the same tick, so a
|
|
554
|
+
// live read would slide an empty box shut. Held until the next edit replaces it.
|
|
555
|
+
let dock: { title: string | null; color: Oklch; paletteIndex: number | null } | null = $state(null);
|
|
556
|
+
$effect(() => {
|
|
557
|
+
if (!panelOpen || isEditingBase || !editingColor) return;
|
|
558
|
+
dock = { title: editPanelTitle, color: editingColor, paletteIndex: editingPaletteIndex };
|
|
559
|
+
});
|
|
528
560
|
$effect(() => {
|
|
529
561
|
// Touch each input so the effect tracks them (resnapScales reads indirectly).
|
|
530
562
|
void baseColor;
|
|
@@ -537,6 +569,24 @@
|
|
|
537
569
|
});
|
|
538
570
|
</script>
|
|
539
571
|
|
|
572
|
+
{#snippet stepEditPanel()}
|
|
573
|
+
{#if dock}
|
|
574
|
+
<ColorEditPanel
|
|
575
|
+
title={dock.title}
|
|
576
|
+
highlighted
|
|
577
|
+
showRemoveOverride
|
|
578
|
+
hue={dock.color.h}
|
|
579
|
+
chroma={dock.color.c}
|
|
580
|
+
lightness={dock.color.l * 100}
|
|
581
|
+
onHueChromaChange={(h, c, l) => handleColorChange({ l: l / 100, c, h })}
|
|
582
|
+
onConfirm={confirmEdit}
|
|
583
|
+
onCancel={cancelEdit}
|
|
584
|
+
onRemoveOverride={() => editingKey && removeOverride(editingKey)}
|
|
585
|
+
onSliderStart={() => beginSliderGesture(`edit ${label} ${editingKey ?? 'color'}`)}
|
|
586
|
+
/>
|
|
587
|
+
{/if}
|
|
588
|
+
{/snippet}
|
|
589
|
+
|
|
540
590
|
<div class="palette-editor" bind:this={rootEl} style="--editor-base: {toHex(baseColor)}">
|
|
541
591
|
<PaletteBase
|
|
542
592
|
{label}
|
|
@@ -544,6 +594,7 @@
|
|
|
544
594
|
{baseColor}
|
|
545
595
|
{isEditingBase}
|
|
546
596
|
pinnedOpen={paletteEditorOpen}
|
|
597
|
+
selected={isEditingBase}
|
|
547
598
|
{copiedKey}
|
|
548
599
|
onStartEdit={startBaseEdit}
|
|
549
600
|
onCopyBaseHex={copyHex}
|
|
@@ -573,7 +624,7 @@
|
|
|
573
624
|
<div class="swatch-grid" style="--swatch-cols: {paletteStepLightness.length + 2}">
|
|
574
625
|
<div class="base-panel" style="grid-column: 2 / {paletteStepLightness.length + 2}">
|
|
575
626
|
<ColorEditPanel
|
|
576
|
-
title={
|
|
627
|
+
title={basePanelTitle}
|
|
577
628
|
showRemoveOverride={false}
|
|
578
629
|
hideActions={!isEditingBase}
|
|
579
630
|
hidePreview
|
|
@@ -617,21 +668,21 @@
|
|
|
617
668
|
</div>
|
|
618
669
|
{#each paletteComputed as ps}
|
|
619
670
|
<div class="step-column">
|
|
620
|
-
<button class="step-label copyable-label" class:copied={copiedLabelKey === ps.key} type="button" onclick={(e) => copyVarName(ps.key, `--color-${cssNamespace}-${ps.label}`, e)}>
|
|
671
|
+
<button class="step-label copyable-label" class:copied={copiedLabelKey === ps.key} class:selected={editingKey === ps.key || (isEditingBase && ps.anchored)} type="button" onclick={(e) => copyVarName(ps.key, `--color-${cssNamespace}-${ps.label}`, e)}>
|
|
621
672
|
{copiedLabelKey === ps.key ? 'copied!' : ps.label}
|
|
622
673
|
</button>
|
|
623
674
|
<!-- svelte-ignore a11y_no_noninteractive_tabindex -->
|
|
624
675
|
<div
|
|
625
676
|
class="swatch gray-swatch"
|
|
626
|
-
class:active={editingKey === ps.key}
|
|
677
|
+
class:active={editingKey === ps.key || (isEditingBase && ps.anchored)}
|
|
627
678
|
class:overridden={ps.key in overrides}
|
|
628
679
|
class:anchored={ps.anchored}
|
|
629
680
|
title={ps.anchored ? 'Base color' : undefined}
|
|
630
681
|
style="background: {ps.effective}"
|
|
631
|
-
onclick={() => handlePaletteClick(
|
|
682
|
+
onclick={() => handlePaletteClick(ps)}
|
|
632
683
|
role="button"
|
|
633
684
|
tabindex="0"
|
|
634
|
-
onkeydown={(e) => e.key === 'Enter' && handlePaletteClick(
|
|
685
|
+
onkeydown={(e) => e.key === 'Enter' && handlePaletteClick(ps)}
|
|
635
686
|
>
|
|
636
687
|
{#if ps.key in overrides}
|
|
637
688
|
<span class="override-lock" title="Palette override: this step is set by hand, not derived from the curve">
|
|
@@ -653,6 +704,17 @@
|
|
|
653
704
|
</button>
|
|
654
705
|
<div class="swatch gray-swatch bookend" style="background: #000000"></div>
|
|
655
706
|
</div>
|
|
707
|
+
|
|
708
|
+
<UIReveal open={editingPaletteIndex !== null} style="grid-column: 2 / {paletteStepLightness.length + 2}">
|
|
709
|
+
<!-- The reveal has to own the grid slot, so the dock rebuilds the
|
|
710
|
+
ramp's tracks inside it: the caret is then placed on the step's
|
|
711
|
+
own column and points at it at any width, no arithmetic. -->
|
|
712
|
+
<div class="dock" style="--dock-cols: {paletteStepLightness.length}">
|
|
713
|
+
<div class="edit-caret" style="grid-column: {(dock?.paletteIndex ?? 0) + 1}"></div>
|
|
714
|
+
<div class="docked-panel">{@render stepEditPanel()}</div>
|
|
715
|
+
</div>
|
|
716
|
+
</UIReveal>
|
|
717
|
+
|
|
656
718
|
<UIReveal open={paletteEditorOpen} style="grid-column: 2 / {paletteStepLightness.length + 2}">
|
|
657
719
|
<div class="curve-grid-span">
|
|
658
720
|
{#if anchorUnlockPrompt && anchorToBase}
|
|
@@ -787,29 +849,19 @@
|
|
|
787
849
|
{@render overridesPanel(scale, true, derivedHexForBase)}
|
|
788
850
|
{/each}
|
|
789
851
|
</div>
|
|
852
|
+
<UIReveal open={editingDerivedRow === 'text'}>
|
|
853
|
+
<div class="docked-panel">{@render stepEditPanel()}</div>
|
|
854
|
+
</UIReveal>
|
|
790
855
|
<div class="scales-row">
|
|
791
856
|
{#each scales.filter(s => !s.isText) as scale}
|
|
792
857
|
{@render overridesPanel(scale, true, derivedHexForBase)}
|
|
793
858
|
{/each}
|
|
794
859
|
</div>
|
|
860
|
+
<UIReveal open={editingDerivedRow === 'solid'}>
|
|
861
|
+
<div class="docked-panel">{@render stepEditPanel()}</div>
|
|
862
|
+
</UIReveal>
|
|
795
863
|
</div>
|
|
796
864
|
</UIReveal>
|
|
797
|
-
|
|
798
|
-
<!-- Color Edit Panel (non-base edits) -->
|
|
799
|
-
{#if !isEditingBase && panelOpen && editingColor}
|
|
800
|
-
<ColorEditPanel
|
|
801
|
-
title={editPanelTitle}
|
|
802
|
-
showRemoveOverride={!!editingKey}
|
|
803
|
-
hue={editingColor.h}
|
|
804
|
-
chroma={editingColor.c}
|
|
805
|
-
lightness={editingColor.l * 100}
|
|
806
|
-
onHueChromaChange={(h, c, l) => handleColorChange({ l: l / 100, c, h })}
|
|
807
|
-
onConfirm={confirmEdit}
|
|
808
|
-
onCancel={cancelEdit}
|
|
809
|
-
onRemoveOverride={() => editingKey && removeOverride(editingKey)}
|
|
810
|
-
onSliderStart={() => beginSliderGesture(`edit ${label} ${editingKey ?? 'color'}`)}
|
|
811
|
-
/>
|
|
812
|
-
{/if}
|
|
813
865
|
</div>
|
|
814
866
|
|
|
815
867
|
<style>
|
|
@@ -937,6 +989,12 @@
|
|
|
937
989
|
color: var(--ui-text-accent, var(--ui-text-primary));
|
|
938
990
|
}
|
|
939
991
|
|
|
992
|
+
/* The name the docked panel's title repeats back. */
|
|
993
|
+
.step-label.copyable-label.selected {
|
|
994
|
+
color: var(--ui-text-primary);
|
|
995
|
+
font-weight: var(--ui-font-weight-semibold);
|
|
996
|
+
}
|
|
997
|
+
|
|
940
998
|
/* Swatches */
|
|
941
999
|
|
|
942
1000
|
.swatch {
|
|
@@ -1013,6 +1071,34 @@
|
|
|
1013
1071
|
gap: var(--ui-space-8);
|
|
1014
1072
|
}
|
|
1015
1073
|
|
|
1074
|
+
/* Same tracks and gutter as the ramp above, so a column here lands under the
|
|
1075
|
+
swatch of the same index. */
|
|
1076
|
+
.dock {
|
|
1077
|
+
display: grid;
|
|
1078
|
+
grid-template-columns: repeat(var(--dock-cols), minmax(0, 1fr));
|
|
1079
|
+
gap: var(--ui-space-4) var(--swatch-gap, var(--ui-space-4));
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
/* Docked under the row it edits, pointed at the swatch it edits. */
|
|
1083
|
+
.docked-panel {
|
|
1084
|
+
grid-column: 1 / -1;
|
|
1085
|
+
min-width: 0;
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
.edit-caret {
|
|
1089
|
+
display: flex;
|
|
1090
|
+
justify-content: center;
|
|
1091
|
+
align-items: flex-end;
|
|
1092
|
+
height: var(--ui-space-6);
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
.edit-caret::after {
|
|
1096
|
+
content: '';
|
|
1097
|
+
border-left: var(--ui-space-6) solid transparent;
|
|
1098
|
+
border-right: var(--ui-space-6) solid transparent;
|
|
1099
|
+
border-bottom: var(--ui-space-6) solid var(--ui-border-higher);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1016
1102
|
.anchor-unlock-notice {
|
|
1017
1103
|
display: flex;
|
|
1018
1104
|
align-items: center;
|
|
@@ -1057,10 +1143,17 @@
|
|
|
1057
1143
|
border-color: var(--ui-border-high);
|
|
1058
1144
|
}
|
|
1059
1145
|
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1146
|
+
/* A ring drawn inside the swatch, weight only: the editor stays greyscale, so
|
|
1147
|
+
selection has to out-weigh the anchored step's emphasized border rather
|
|
1148
|
+
than out-colour it. Insets, because a 3px outline would eat the column gap
|
|
1149
|
+
and a 3px border would resize the swatch mid-grid. The dark keylines either
|
|
1150
|
+
side of the white band keep it legible at both ends of the ramp. */
|
|
1151
|
+
.swatch.gray-swatch.active,
|
|
1152
|
+
.swatch.gray-swatch.anchored.active {
|
|
1153
|
+
border-color: var(--ui-surface-lowest);
|
|
1154
|
+
box-shadow:
|
|
1155
|
+
inset 0 0 0 3px var(--ui-text-primary),
|
|
1156
|
+
inset 0 0 0 4px var(--ui-surface-lowest);
|
|
1064
1157
|
}
|
|
1065
1158
|
|
|
1066
1159
|
/* The step the base color is placed at: taller, with an emphasized border. It
|
|
@@ -424,9 +424,12 @@
|
|
|
424
424
|
border-color: var(--ui-border-high);
|
|
425
425
|
}
|
|
426
426
|
|
|
427
|
+
/* Same weight-only selection language as the palette ramp, at the smaller
|
|
428
|
+
swatch's scale. */
|
|
427
429
|
.override-slot.active {
|
|
428
|
-
border-
|
|
429
|
-
|
|
430
|
+
border-style: solid;
|
|
431
|
+
border-color: var(--ui-surface-lowest);
|
|
432
|
+
outline: 2px solid var(--ui-text-primary);
|
|
430
433
|
outline-offset: 1px;
|
|
431
434
|
}
|
|
432
435
|
|
|
@@ -14,6 +14,9 @@
|
|
|
14
14
|
isEditingBase: boolean;
|
|
15
15
|
/** Keep the swatch reading as active while the curve editors are visible. */
|
|
16
16
|
pinnedOpen?: boolean;
|
|
17
|
+
/** The same ring the ramp's anchored step wears, so the two swatches read
|
|
18
|
+
* as the one value they are. */
|
|
19
|
+
selected?: boolean;
|
|
17
20
|
copiedKey: string | null;
|
|
18
21
|
onStartEdit: () => void;
|
|
19
22
|
onCopyBaseHex: (key: string, hex: string, event?: MouseEvent) => void;
|
|
@@ -29,6 +32,7 @@
|
|
|
29
32
|
baseColor,
|
|
30
33
|
isEditingBase,
|
|
31
34
|
pinnedOpen = false,
|
|
35
|
+
selected = false,
|
|
32
36
|
copiedKey,
|
|
33
37
|
onStartEdit,
|
|
34
38
|
onCopyBaseHex,
|
|
@@ -49,6 +53,7 @@
|
|
|
49
53
|
<div
|
|
50
54
|
class="header-swatch"
|
|
51
55
|
class:active={isEditingBase || pinnedOpen}
|
|
56
|
+
class:selected
|
|
52
57
|
style="background: {baseHex}"
|
|
53
58
|
onclick={onStartEdit}
|
|
54
59
|
role="button"
|
|
@@ -150,6 +155,14 @@
|
|
|
150
155
|
outline-offset: 1px;
|
|
151
156
|
}
|
|
152
157
|
|
|
158
|
+
.header-swatch.selected {
|
|
159
|
+
border-color: var(--ui-surface-lowest);
|
|
160
|
+
outline: none;
|
|
161
|
+
box-shadow:
|
|
162
|
+
inset 0 0 0 3px var(--ui-text-primary),
|
|
163
|
+
inset 0 0 0 4px var(--ui-surface-lowest);
|
|
164
|
+
}
|
|
165
|
+
|
|
153
166
|
.editor-label {
|
|
154
167
|
font-size: var(--ui-font-size-xl);
|
|
155
168
|
font-weight: var(--ui-font-weight-semibold);
|