@motion-proto/live-tokens 0.54.2 → 0.55.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/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.55.0 — A focused palette arrives ready
4
+
5
+ ### Changed
6
+
7
+ - **A palette focused before mount opens without the reveal.** `paletteEditorOpen`
8
+ started false and flipped in an effect, so a deep link that set the focus
9
+ ahead of mount landed the visitor mid-animation, reading as a page still
10
+ loading. The editor now reads the pending focus when it constructs and opens
11
+ on the first frame; the scroll that brings it into view jumps rather than
12
+ gliding, since nothing expanded for the motion to match. A jump from the
13
+ Colors view, where the editor is already mounted, keeps its reveal.
14
+
15
+ - **"Base color must appear in palette" moved to the family band.** The toggle
16
+ sets `anchorToBase`, which is family-level config persisted in the theme,
17
+ but it sat in the base-colour panel among the controls that edit the colour's
18
+ *value* — and it only occupied a row of its own because it wrapped out of that
19
+ panel's title row. It now sits beside the family's name as
20
+ "Must appear in palette", the swatch alongside supplying the subject the label
21
+ drops; the full sentence stays as its title. The colour panel closes back up
22
+ to a title row and its sliders. Collapsed families are untouched: the setting
23
+ shows only while that family's editor is open, so a page of ten collapsed
24
+ palettes does not repeat it ten times.
25
+
26
+ - **The palette band holds two columns at every width.** With the setting on
27
+ the band's row, a band too narrow for it wrapped the buttons to the left edge,
28
+ under the swatch. The setting sits in the identity column instead, a third
29
+ line under the hex — name, value and constraint all describe the family and
30
+ share its left rail — which leaves the buttons a column of their own that no
31
+ width takes away. They wrap among themselves against the right edge before the
32
+ cluster ever drops a line. The swatch keeps its square: the taller column
33
+ would otherwise stretch the chip into a rectangle.
34
+
3
35
  ## 0.54.2 — Palette focus is public API
4
36
 
5
37
  ### Added
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motion-proto/live-tokens",
3
- "version": "0.54.2",
3
+ "version": "0.55.0",
4
4
  "type": "module",
5
5
  "description": "Design token editor with live CSS variable editing. Svelte 5 + Vite 8.",
6
6
  "keywords": [
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { onMount, onDestroy, tick } from 'svelte';
3
+ import { get } from 'svelte/store';
3
4
  import { oklchToHexClamped, type Oklch } from '../core/palettes/oklch';
4
5
  import { type CurveAnchor, lightnessCurveConfig, saturationCurveConfig, hueCurveConfig } from './curveEngine';
5
6
  import ColorEditPanel from './ColorEditPanel.svelte';
@@ -114,7 +115,14 @@
114
115
  }
115
116
 
116
117
  let showDerived = $state(false);
117
- let paletteEditorOpen = $state(false);
118
+ // A deep link sets the focus before this mounts, so the controls are open on
119
+ // the first frame: UIReveal's `{#if}` never toggles and there is no reveal to
120
+ // sit through. Landing mid-animation reads as a page still loading.
121
+ // svelte-ignore state_referenced_locally
122
+ // The mount-time value is the whole point: a family's label is fixed for the
123
+ // life of the instance (PALETTE_SPECS keys the `{#each}` by it).
124
+ const focusedOnMount = get(pendingPaletteFocus) === label;
125
+ let paletteEditorOpen = $state(focusedOnMount);
118
126
  let rootEl: HTMLElement | undefined = $state();
119
127
 
120
128
  // Arriving from the Colors view's Edit button: open this family's controls and
@@ -123,7 +131,11 @@
123
131
  if ($pendingPaletteFocus !== label) return;
124
132
  pendingPaletteFocus.set(null);
125
133
  paletteEditorOpen = true;
126
- tick().then(() => rootEl?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
134
+ // Nothing expanded when we opened on mount, so there is no motion for a
135
+ // smooth scroll to match — jump, and the first frame is the final state.
136
+ tick().then(() =>
137
+ rootEl?.scrollIntoView({ behavior: focusedOnMount ? 'auto' : 'smooth', block: 'start' }),
138
+ );
127
139
  });
128
140
 
129
141
  function setLightnessCurve(a: CurveAnchor[]) { edit('lightnessCurve', a); }
@@ -536,6 +548,18 @@
536
548
  onStartEdit={startBaseEdit}
537
549
  onCopyBaseHex={copyHex}
538
550
  >
551
+ {#snippet setting()}
552
+ <!-- The family's own swatch and name sit alongside, so the label drops
553
+ its subject; the title carries the full sentence. -->
554
+ <span class="base-anchor-toggle" title="Base color must appear in palette">
555
+ <Toggle
556
+ checked={anchorToBase}
557
+ onchange={(v) => setAnchorToBase(v ?? !anchorToBase)}
558
+ label="Must appear in palette"
559
+ />
560
+ </span>
561
+ {/snippet}
562
+
539
563
  {#snippet actions()}
540
564
  <PaletteJumpButton family={label} {displayLabel} target="wheel" />
541
565
  <UIPillButton size="compact" variant="outline" onclick={clearPaletteOverrides}>Clear Overrides</UIPillButton>
@@ -563,18 +587,7 @@
563
587
  onCancel={cancelEdit}
564
588
  onRemoveOverride={() => {}}
565
589
  onSliderStart={() => beginSliderGesture(`edit ${label} base`)}
566
- >
567
- {#snippet actions()}
568
- <div class="base-anchor-toggle">
569
- <Toggle
570
- checked={anchorToBase}
571
- onchange={(v) => setAnchorToBase(v ?? !anchorToBase)}
572
- label="Base color must appear in palette"
573
- labelFirst
574
- />
575
- </div>
576
- {/snippet}
577
- </ColorEditPanel>
590
+ />
578
591
  </div>
579
592
  </div>
580
593
  </UIReveal>
@@ -988,8 +1001,10 @@
988
1001
  min-width: 0;
989
1002
  }
990
1003
 
1004
+ /* A box of its own so the title has something to hover; placement in the
1005
+ band belongs to PaletteBase. */
991
1006
  .base-anchor-toggle {
992
- margin-left: auto;
1007
+ display: flex;
993
1008
  }
994
1009
 
995
1010
  .curve-grid-span {
@@ -18,6 +18,9 @@
18
18
  onStartEdit: () => void;
19
19
  onCopyBaseHex: (key: string, hex: string, event?: MouseEvent) => void;
20
20
  actions?: Snippet;
21
+ /** A family-level setting, shown beside the name while this family's
22
+ * editor is open. Collapsed families keep the band to identity + actions. */
23
+ setting?: Snippet;
21
24
  }
22
25
 
23
26
  let {
@@ -29,10 +32,15 @@
29
32
  copiedKey,
30
33
  onStartEdit,
31
34
  onCopyBaseHex,
32
- actions
35
+ actions,
36
+ setting
33
37
  }: Props = $props();
34
38
 
35
39
  let baseHex = $derived(oklchToHexClamped(baseColor.l, baseColor.c, baseColor.h));
40
+
41
+ // `pinnedOpen || isEditingBase` is exactly when the base panel shows: editing
42
+ // the base implies both a panel and a colour to put in it.
43
+ let showSetting = $derived(!!setting && (pinnedOpen || isEditingBase));
36
44
  </script>
37
45
 
38
46
  <div class="editor-top">
@@ -64,6 +72,10 @@
64
72
  aria-hidden="true"
65
73
  ></i>
66
74
  </button>
75
+
76
+ {#if showSetting}
77
+ <div class="header-setting">{@render setting?.()}</div>
78
+ {/if}
67
79
  </div>
68
80
  </div>
69
81
 
@@ -100,12 +112,28 @@
100
112
  .header-actions {
101
113
  display: flex;
102
114
  align-items: center;
115
+ flex-wrap: wrap;
116
+ justify-content: flex-end;
103
117
  gap: var(--ui-space-8);
104
118
  }
105
119
 
120
+ /* Third line of the identity, under the hex: name, value, constraint all
121
+ describe the family and share its left rail. Keeping it out of the band's
122
+ row is what leaves the buttons a column of their own at every width. */
123
+ .header-setting {
124
+ display: flex;
125
+ align-items: center;
126
+ margin-top: var(--ui-space-4);
127
+ }
128
+
129
+ /* The setting makes the identity column taller than the swatch, and the row
130
+ stretches its items; left alone the chip would grow into a rectangle. It is
131
+ a colour sample, so it stays square and hangs from the top of the column. */
106
132
  .header-swatch {
107
133
  width: 4.5rem;
108
134
  min-height: 4.5rem;
135
+ aspect-ratio: 1;
136
+ align-self: flex-start;
109
137
  border-radius: var(--ui-radius-md);
110
138
  border: 2px solid var(--ui-border);
111
139
  flex-shrink: 0;