@motion-proto/live-tokens 0.3.7 → 0.5.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 (104) hide show
  1. package/README.md +1 -1
  2. package/package.json +11 -9
  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 +260 -37
  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 +73 -36
  61. package/src/pages/ComponentEditorPage.svelte +17 -13
  62. package/src/pages/EditorShell.svelte +24 -20
  63. package/src/styles/form-controls.css +2 -2
  64. package/src/styles/tokens.css +59 -81
  65. package/src/ui/BezierCurveEditor.svelte +59 -43
  66. package/src/ui/ColorEditPanel.svelte +71 -44
  67. package/src/ui/EditorViewSwitcher.svelte +9 -5
  68. package/src/ui/FontStackEditor.svelte +16 -15
  69. package/src/ui/GradientEditor.svelte +42 -33
  70. package/src/ui/GradientStopPicker.svelte +18 -29
  71. package/src/ui/PaletteEditor.svelte +238 -212
  72. package/src/ui/PresetFileManager.svelte +20 -18
  73. package/src/ui/ProjectFontsSection.svelte +30 -30
  74. package/src/ui/SurfacesTab.svelte +3 -3
  75. package/src/ui/TextTab.svelte +2 -2
  76. package/src/ui/ThemeFileManager.svelte +38 -35
  77. package/src/ui/Toggle.svelte +11 -9
  78. package/src/ui/UICopyPopover.svelte +19 -15
  79. package/src/ui/UIDialog.svelte +48 -30
  80. package/src/ui/UIFontFamilySelector.svelte +104 -78
  81. package/src/ui/UIFontSizeSelector.svelte +38 -20
  82. package/src/ui/UIFontWeightSelector.svelte +33 -13
  83. package/src/ui/UILineHeightSelector.svelte +33 -13
  84. package/src/ui/UILinkToggle.svelte +7 -6
  85. package/src/ui/UIOptionItem.svelte +21 -7
  86. package/src/ui/UIOptionList.svelte +9 -3
  87. package/src/ui/UIPaddingSelector.svelte +108 -82
  88. package/src/ui/UIPaletteSelector.svelte +186 -161
  89. package/src/ui/UIRadio.svelte +23 -8
  90. package/src/ui/UIRadioGroup.svelte +9 -8
  91. package/src/ui/UIRelinkConfirmPopover.svelte +26 -16
  92. package/src/ui/UITokenSelector.svelte +112 -68
  93. package/src/ui/UIVariantSelector.svelte +79 -57
  94. package/src/ui/VariablesTab.svelte +15 -15
  95. package/src/ui/palette/GradientStopEditor.svelte +45 -26
  96. package/src/ui/palette/OverridesPanel.svelte +85 -49
  97. package/src/ui/palette/PaletteBase.svelte +60 -32
  98. package/src/ui/palette/ScaleCurveEditor.svelte +25 -10
  99. package/src/ui/sections/ColumnsSection.svelte +13 -13
  100. package/src/ui/sections/GradientsSection.svelte +12 -9
  101. package/src/ui/sections/OverlaysSection.svelte +50 -47
  102. package/src/ui/sections/ShadowsSection.svelte +110 -104
  103. package/src/ui/sections/TokenScaleTable.svelte +38 -22
  104. package/src/ui/sections/tokenScales.ts +2 -2
@@ -1,27 +1,39 @@
1
1
  <script lang="ts">
2
- import { onMount, onDestroy, createEventDispatcher } from 'svelte';
2
+ import { stopPropagation } from 'svelte/legacy';
3
+
4
+ import { onMount, onDestroy } from 'svelte';
3
5
  import type { ComponentConfigMeta } from '../../lib/themeTypes';
4
6
  import UIDialog from '../../ui/UIDialog.svelte';
5
7
 
6
- /** Component slug used in the load-dialog title (e.g. "button"). */
7
- export let component: string;
8
- /** Files shown in the load dialog. */
9
- export let files: ComponentConfigMeta[] = [];
10
- /** Currently active file — highlighted in the load list. */
11
- export let activeFileName: string = 'default';
12
-
13
- const dispatch = createEventDispatcher<{
14
- save: void;
15
- saveAs: void;
8
+ interface Props {
9
+ /** Component slug used in the load-dialog title (e.g. "button"). */
10
+ component: string;
11
+ /** Files shown in the load dialog. */
12
+ files?: ComponentConfigMeta[];
13
+ /** Currently active file highlighted in the load list. */
14
+ activeFileName?: string;
15
+ onsave?: () => void;
16
+ onsaveAs?: () => void;
16
17
  /** Fired when the user clicks "Load…" in the menu — parent should refresh `files`. */
17
- openLoad: void;
18
- load: ComponentConfigMeta;
19
- delete: ComponentConfigMeta;
20
- }>();
18
+ onopenLoad?: () => void;
19
+ onload?: (file: ComponentConfigMeta) => void;
20
+ ondelete?: (file: ComponentConfigMeta) => void;
21
+ }
21
22
 
22
- let fileMenuOpen = false;
23
- let fileMenuRoot: HTMLElement;
24
- let showFileList = false;
23
+ let {
24
+ component,
25
+ files = [],
26
+ activeFileName = 'default',
27
+ onsave,
28
+ onsaveAs,
29
+ onopenLoad,
30
+ onload,
31
+ ondelete,
32
+ }: Props = $props();
33
+
34
+ let fileMenuOpen = $state(false);
35
+ let fileMenuRoot: HTMLElement | undefined = $state();
36
+ let showFileList = $state(false);
25
37
 
26
38
  onMount(() => {
27
39
  document.addEventListener('click', handleDocClick, true);
@@ -40,28 +52,28 @@
40
52
 
41
53
  function handleSave() {
42
54
  fileMenuOpen = false;
43
- dispatch('save');
55
+ onsave?.();
44
56
  }
45
57
 
46
58
  function handleSaveAs() {
47
59
  fileMenuOpen = false;
48
- dispatch('saveAs');
60
+ onsaveAs?.();
49
61
  }
50
62
 
51
63
  function handleOpenLoad() {
52
64
  fileMenuOpen = false;
53
65
  showFileList = true;
54
- dispatch('openLoad');
66
+ onopenLoad?.();
55
67
  }
56
68
 
57
69
  function handleLoad(file: ComponentConfigMeta) {
58
70
  showFileList = false;
59
- dispatch('load', file);
71
+ onload?.(file);
60
72
  }
61
73
 
62
74
  function handleDelete(file: ComponentConfigMeta) {
63
75
  if (file.fileName === 'default') return;
64
- dispatch('delete', file);
76
+ ondelete?.(file);
65
77
  }
66
78
  </script>
67
79
 
@@ -69,7 +81,7 @@
69
81
  <button
70
82
  class="cfm-btn"
71
83
  class:active={fileMenuOpen}
72
- on:click={() => (fileMenuOpen = !fileMenuOpen)}
84
+ onclick={() => (fileMenuOpen = !fileMenuOpen)}
73
85
  title="File menu"
74
86
  >
75
87
  <i class="fas fa-file"></i>
@@ -78,15 +90,15 @@
78
90
  </button>
79
91
  {#if fileMenuOpen}
80
92
  <div class="file-menu-dropdown" role="menu">
81
- <button class="file-menu-item" on:click={handleSave} role="menuitem">
93
+ <button class="file-menu-item" onclick={handleSave} role="menuitem">
82
94
  <i class="fas fa-save"></i>
83
95
  <span>Save</span>
84
96
  </button>
85
- <button class="file-menu-item" on:click={handleSaveAs} role="menuitem">
97
+ <button class="file-menu-item" onclick={handleSaveAs} role="menuitem">
86
98
  <i class="fas fa-copy"></i>
87
99
  <span>Save As…</span>
88
100
  </button>
89
- <button class="file-menu-item" on:click={handleOpenLoad} role="menuitem">
101
+ <button class="file-menu-item" onclick={handleOpenLoad} role="menuitem">
90
102
  <i class="fas fa-folder-open"></i>
91
103
  <span>Load…</span>
92
104
  </button>
@@ -103,7 +115,7 @@
103
115
  <div class="load-list">
104
116
  {#each files as file}
105
117
  <div class="load-item" class:active={file.fileName === activeFileName}>
106
- <button class="load-name-btn" on:click={() => handleLoad(file)}>
118
+ <button class="load-name-btn" onclick={() => handleLoad(file)}>
107
119
  {file.name}
108
120
  </button>
109
121
  {#if file.fileName === activeFileName}
@@ -112,7 +124,7 @@
112
124
  {#if file.fileName !== 'default'}
113
125
  <button
114
126
  class="file-delete-btn"
115
- on:click|stopPropagation={() => handleDelete(file)}
127
+ onclick={stopPropagation(() => handleDelete(file))}
116
128
  title="Delete {file.name}"
117
129
  >
118
130
  <i class="fas fa-trash-alt"></i>
@@ -2,14 +2,18 @@
2
2
  import type { ComponentSection } from './componentSectionType';
3
3
  import { defaultSections } from './defaultSections';
4
4
 
5
- export let sections: ComponentSection[] = defaultSections;
6
- export let selectedComponent: string = sections[0]?.id ?? '';
5
+ interface Props {
6
+ sections?: ComponentSection[];
7
+ selectedComponent?: string;
8
+ }
9
+
10
+ let { sections = defaultSections, selectedComponent = sections[0]?.id ?? '' }: Props = $props();
7
11
  </script>
8
12
 
9
13
  <div class="components-container">
10
14
  {#each sections as section (section.id)}
11
15
  {#if selectedComponent === section.id}
12
- <svelte:component this={section.component} {...(section.props ?? {})} />
16
+ <section.component {...(section.props ?? {})} />
13
17
  {/if}
14
18
  {/each}
15
19
  </div>
@@ -1,17 +1,26 @@
1
1
  <script lang="ts">
2
- import { createEventDispatcher, onMount, onDestroy } from 'svelte';
2
+ import { onMount, onDestroy } from 'svelte';
3
3
 
4
4
  type Source = { name: string; label: string; states: string[] };
5
5
 
6
- export let toState: string;
7
- export let variantName: string;
8
- export let copySources: Source[] = [];
9
- export let placement: 'start' | 'end' = 'start';
6
+ interface Props {
7
+ toState: string;
8
+ variantName: string;
9
+ copySources?: Source[];
10
+ placement?: 'start' | 'end';
11
+ onselect?: (payload: { fromVariant: string; fromState: string }) => void;
12
+ }
10
13
 
11
- const dispatch = createEventDispatcher<{ select: { fromVariant: string; fromState: string } }>();
14
+ let {
15
+ toState,
16
+ variantName,
17
+ copySources = [],
18
+ placement = 'start',
19
+ onselect
20
+ }: Props = $props();
12
21
 
13
- let open = false;
14
- let root: HTMLElement;
22
+ let open = $state(false);
23
+ let root: HTMLElement | undefined = $state();
15
24
 
16
25
  function toggle() {
17
26
  open = !open;
@@ -19,7 +28,7 @@
19
28
 
20
29
  function pick(fromVariant: string, fromState: string) {
21
30
  open = false;
22
- dispatch('select', { fromVariant, fromState });
31
+ onselect?.({ fromVariant, fromState });
23
32
  }
24
33
 
25
34
  function handleDocClick(e: MouseEvent) {
@@ -47,7 +56,7 @@
47
56
  type="button"
48
57
  class="copy-from-btn"
49
58
  class:active={open}
50
- on:click={toggle}
59
+ onclick={toggle}
51
60
  title="Copy values from another variant/state"
52
61
  >
53
62
  <i class="fas fa-clone"></i>
@@ -63,7 +72,7 @@
63
72
  type="button"
64
73
  class="copy-menu-item"
65
74
  disabled={isSelf}
66
- on:click={() => pick(src.name, onlyState)}
75
+ onclick={() => pick(src.name, onlyState)}
67
76
  role="menuitem"
68
77
  >
69
78
  <span>{src.label}</span>
@@ -87,7 +96,7 @@
87
96
  type="button"
88
97
  class="copy-menu-item"
89
98
  disabled={isSelf}
90
- on:click={() => pick(src.name, fromState)}
99
+ onclick={() => pick(src.name, fromState)}
91
100
  role="menuitem"
92
101
  >
93
102
  <span>{fromState}</span>
@@ -1,10 +1,19 @@
1
1
  <script lang="ts">
2
2
  import ComponentFileManager from './ComponentFileManager.svelte';
3
3
 
4
- export let component: string;
5
- export let title: string;
6
- export let description: string = '';
7
- export let resetVariables: string[] | null = null;
4
+ interface Props {
5
+ component: string;
6
+ title: string;
7
+ description?: string;
8
+ resetVariables?: string[] | null;
9
+ }
10
+
11
+ let {
12
+ component,
13
+ title,
14
+ description = '',
15
+ resetVariables = null
16
+ }: Props = $props();
8
17
  </script>
9
18
 
10
19
  <ComponentFileManager {component} {title} {resetVariables} />
@@ -6,16 +6,29 @@
6
6
  import { BORDER_WIDTH, DIVIDER_HEIGHT } from '../../ui/variantScales';
7
7
  import FieldsetWrapper from './FieldsetWrapper.svelte';
8
8
 
9
- export let colorVariable: string | undefined = undefined;
10
- export let colorLabel: string | undefined = undefined;
11
- export let widthVariable: string | undefined = undefined;
12
- export let widthLabel: string | undefined = undefined;
13
- export let heightVariable: string | undefined = undefined;
14
- export let heightLabel: string | undefined = undefined;
15
- /** When set, writes persist through the editor store under this component. */
16
- export let component: string | undefined = undefined;
9
+
10
+ interface Props {
11
+ colorVariable?: string | undefined;
12
+ colorLabel?: string | undefined;
13
+ widthVariable?: string | undefined;
14
+ widthLabel?: string | undefined;
15
+ heightVariable?: string | undefined;
16
+ heightLabel?: string | undefined;
17
+ /** When set, writes persist through the editor store under this component. */
18
+ component?: string | undefined;
19
+ }
20
+
21
+ let {
22
+ colorVariable = undefined,
23
+ colorLabel = undefined,
24
+ widthVariable = undefined,
25
+ widthLabel = undefined,
26
+ heightVariable = undefined,
27
+ heightLabel = undefined,
28
+ component = undefined
29
+ }: Props = $props();
17
30
 
18
- let heightResolved = '';
31
+ let heightResolved = $state('');
19
32
 
20
33
  function readHeight() {
21
34
  if (!heightVariable) {
@@ -41,26 +54,26 @@
41
54
  document.removeEventListener(CSS_VAR_CHANGE_EVENT, handleVarChange);
42
55
  });
43
56
 
44
- $: heightIsZero = /^0+(?:\.0+)?(px|rem|em|%)?$/.test(heightResolved);
45
- $: siblingDisabled = heightIsZero;
57
+ let heightIsZero = $derived(/^0+(?:\.0+)?(px|rem|em|%)?$/.test(heightResolved));
58
+ let siblingDisabled = $derived(heightIsZero);
46
59
  </script>
47
60
 
48
61
  <FieldsetWrapper legend="divider">
49
62
  {#if colorVariable}
50
63
  <div class="entry">
51
- <UIPaletteSelector variable={colorVariable} {component} disabled={siblingDisabled} on:change={readHeight} />
64
+ <UIPaletteSelector variable={colorVariable} {component} disabled={siblingDisabled} onchange={readHeight} />
52
65
  <span class="label">{colorLabel ?? ''}</span>
53
66
  </div>
54
67
  {/if}
55
68
  {#if widthVariable}
56
69
  <div class="entry">
57
- <UIVariantSelector variable={widthVariable} {component} disabled={siblingDisabled} {...BORDER_WIDTH} on:change={readHeight} />
70
+ <UIVariantSelector variable={widthVariable} {component} disabled={siblingDisabled} {...BORDER_WIDTH} onchange={readHeight} />
58
71
  <span class="label">{widthLabel ?? ''}</span>
59
72
  </div>
60
73
  {/if}
61
74
  {#if heightVariable}
62
75
  <div class="entry">
63
- <UIVariantSelector variable={heightVariable} {component} {...DIVIDER_HEIGHT} on:change={readHeight} />
76
+ <UIVariantSelector variable={heightVariable} {component} {...DIVIDER_HEIGHT} onchange={readHeight} />
64
77
  <span class="label">{heightLabel ?? ''}</span>
65
78
  </div>
66
79
  {/if}
@@ -1,7 +1,13 @@
1
1
  <script lang="ts">
2
- export let legend: string = '';
3
- /** When true, the fieldset is rendered with a strong outline to mark it as the one currently driving the rendered preview. */
4
- export let active: boolean = false;
2
+
3
+ interface Props {
4
+ legend?: string;
5
+ /** When true, the fieldset is rendered with a strong outline to mark it as the one currently driving the rendered preview. */
6
+ active?: boolean;
7
+ children?: import('svelte').Snippet;
8
+ }
9
+
10
+ let { legend = '', active = false, children }: Props = $props();
5
11
  </script>
6
12
 
7
13
  <fieldset class="fieldset-wrapper" class:active>
@@ -9,7 +15,7 @@
9
15
  <legend class="fieldset-legend">{legend}</legend>
10
16
  {/if}
11
17
  <div class="fieldset-controls">
12
- <slot />
18
+ {@render children?.()}
13
19
  </div>
14
20
  </fieldset>
15
21
 
@@ -16,14 +16,19 @@
16
16
  import UIPaletteSelector from '../../ui/UIPaletteSelector.svelte';
17
17
  import AngleDial from './AngleDial.svelte';
18
18
 
19
- export let component: string;
20
- /** Prefix shared by all 7 token names — e.g. `--sectiondivider-canvas`. */
21
- export let prefix: string;
19
+
20
+ interface Props {
21
+ component: string;
22
+ /** Prefix shared by all 7 token names — e.g. `--sectiondivider-canvas`. */
23
+ prefix: string;
24
+ }
25
+
26
+ let { component, prefix }: Props = $props();
22
27
 
23
28
  type StopIndex = 1 | 2 | 3;
24
29
  const STOPS: readonly StopIndex[] = [1, 2, 3] as const;
25
30
 
26
- $: angleVar = `${prefix}-gradient-angle`;
31
+ let angleVar = $derived(`${prefix}-gradient-angle`);
27
32
  function stopColorVar(i: StopIndex): string {
28
33
  return `${prefix}-gradient-stop-${i}-color`;
29
34
  }
@@ -52,27 +57,27 @@
52
57
  return m ? parseFloat(m[1]) : null;
53
58
  }
54
59
 
55
- $: angleDeg = parseNumberFromCss(resolveLiteralWith($tokenRegistry$, angleVar), 'deg') ?? 135;
56
- $: positions = [
60
+ let angleDeg = $derived(parseNumberFromCss(resolveLiteralWith($tokenRegistry$, angleVar), 'deg') ?? 135);
61
+ let positions = $derived([
57
62
  parseNumberFromCss(resolveLiteralWith($tokenRegistry$, stopPositionVar(1)), '%') ?? 0,
58
63
  parseNumberFromCss(resolveLiteralWith($tokenRegistry$, stopPositionVar(2)), '%') ?? 50,
59
64
  parseNumberFromCss(resolveLiteralWith($tokenRegistry$, stopPositionVar(3)), '%') ?? 100,
60
- ] as [number, number, number];
65
+ ] as [number, number, number]);
61
66
 
62
67
  // Reference the per-stop CSS var directly so the cascade fills in the
63
68
  // component's CSS defaults when the user hasn't overridden a stop. Reading
64
69
  // `aliases[...]` alone would miss defaults (no override → `#888`) even
65
70
  // though the component is rendering the color via its own `:root` block.
66
- $: stopColors = ([1, 2, 3] as StopIndex[]).map((i) => `var(${stopColorVar(i)})`) as [string, string, string];
71
+ let stopColors = $derived(([1, 2, 3] as StopIndex[]).map((i) => `var(${stopColorVar(i)})`) as [string, string, string]);
67
72
 
68
73
  // Build the live gradient string from current positions + colors so the
69
74
  // ribbon reflects edits even mid-drag (before the component re-renders via
70
75
  // its own CSS var consumption).
71
- $: ribbonBg = `linear-gradient(90deg, ${stopColors
76
+ let ribbonBg = $derived(`linear-gradient(90deg, ${stopColors
72
77
  .map((c, i) => `${c} ${positions[i]}%`)
73
- .join(', ')})`;
78
+ .join(', ')})`);
74
79
 
75
- let selected: StopIndex = 1;
80
+ let selected: StopIndex = $state(1);
76
81
 
77
82
  function setAngle(deg: number) {
78
83
  setComponentAlias(component, angleVar, { kind: 'literal', value: `${Math.round(deg)}deg` });
@@ -84,11 +89,11 @@
84
89
  }
85
90
 
86
91
  // ── Position handle drag ────────────────────────────────────────────────
87
- let barEl: HTMLDivElement;
88
- let dragIndex: StopIndex | null = null;
92
+ let barEl: HTMLDivElement | undefined = $state();
93
+ let dragIndex: StopIndex | null = $state(null);
89
94
 
90
95
  function pctFromEvent(e: PointerEvent): number {
91
- const rect = barEl.getBoundingClientRect();
96
+ const rect = barEl!.getBoundingClientRect();
92
97
  const x = e.clientX - rect.left;
93
98
  return (x / rect.width) * 100;
94
99
  }
@@ -126,10 +131,10 @@
126
131
  class:selected={selected === i}
127
132
  class:dragging={dragIndex === i}
128
133
  style="left: {positions[i - 1]}%; --stop-color: {stopColors[i - 1]};"
129
- on:pointerdown={(e) => onHandleDown(e, i)}
130
- on:pointermove={onHandleMove}
131
- on:pointerup={onHandleUp}
132
- on:pointercancel={onHandleUp}
134
+ onpointerdown={(e) => onHandleDown(e, i)}
135
+ onpointermove={onHandleMove}
136
+ onpointerup={onHandleUp}
137
+ onpointercancel={onHandleUp}
133
138
  title="Stop {i} ({positions[i - 1]}%)"
134
139
  aria-label="Gradient stop {i}"
135
140
  >
@@ -148,7 +153,7 @@
148
153
  max="100"
149
154
  step="0.1"
150
155
  value={positions[selected - 1]}
151
- on:change={(e) => onPositionInput(selected, e)}
156
+ onchange={(e) => onPositionInput(selected, e)}
152
157
  />
153
158
  <span class="suffix">%</span>
154
159
  </label>
@@ -156,7 +161,7 @@
156
161
  <UIPaletteSelector variable={stopColorVar(selected)} {component} />
157
162
  </div>
158
163
  <div class="angle-slot">
159
- <AngleDial value={angleDeg} on:change={(e) => setAngle(e.detail.value)} />
164
+ <AngleDial value={angleDeg} onchange={(d) => setAngle(d.value)} />
160
165
  </div>
161
166
  </div>
162
167
  </div>
@@ -1,4 +1,4 @@
1
- <script context="module" lang="ts">
1
+ <script module lang="ts">
2
2
  type CellStatus = 'linked' | 'broken' | 'absent';
3
3
  type RowEntry = { label: string; key: string };
4
4
  type Axes =
@@ -34,37 +34,46 @@
34
34
  </script>
35
35
 
36
36
  <script lang="ts">
37
- import { createEventDispatcher } from 'svelte';
38
-
39
- export let contexts: string[] = [];
40
- export let broken: string[] = [];
41
- export let singleAxisLabel: string = '';
42
- /** Caption rendered above the grid. Use to describe the linkage scope
37
+ interface Props {
38
+ contexts?: string[];
39
+ broken?: string[];
40
+ singleAxisLabel?: string;
41
+ /** Caption rendered above the grid. Use to describe the linkage scope
43
42
  (e.g. "Links across variants and states"). Defaults to the legacy
44
43
  "Linked Properties" label so consumers that don't pass a caption
45
44
  keep their existing rendering. */
46
- export let caption: string = 'Linked Properties';
47
- /** Currently focused variant (matches a row in 2d / a row label in 1d). When set,
45
+ caption?: string;
46
+ /** Currently focused variant (matches a row in 2d / a row label in 1d). When set,
48
47
  the matching row is highlighted with the same active style as the variant tab strip. */
49
- export let selectedRow: string | null = null;
50
- /** Currently focused state (matches a column in 2d). The cell at (selectedRow, selectedCol)
48
+ selectedRow?: string | null;
49
+ /** Currently focused state (matches a column in 2d). The cell at (selectedRow, selectedCol)
51
50
  gets an additional accent. Ignored in 1d charts. */
52
- export let selectedCol: string | null = null;
51
+ selectedCol?: string | null;
52
+ onselect?: (label: string) => void;
53
+ }
53
54
 
54
- const dispatch = createEventDispatcher<{ select: string }>();
55
+ let {
56
+ contexts = [],
57
+ broken = [],
58
+ singleAxisLabel = '',
59
+ caption = 'Linked Properties',
60
+ selectedRow = null,
61
+ selectedCol = null,
62
+ onselect
63
+ }: Props = $props();
55
64
 
56
- $: axes = deriveAxes(contexts);
57
- $: status = (() => {
65
+ let axes = $derived(deriveAxes(contexts));
66
+ let status = $derived((() => {
58
67
  const brokenSet = new Set(broken);
59
68
  const m = new Map<string, CellStatus>();
60
69
  for (const c of contexts) m.set(c, brokenSet.has(c) ? 'broken' : 'linked');
61
70
  return m;
62
- })();
71
+ })());
63
72
 
64
- let hoveredRow: number = -1;
73
+ let hoveredRow: number = $state(-1);
65
74
 
66
75
  function key2d(r: string, c: string): string { return `${r} ${c}`; }
67
- function selectRow(label: string) { dispatch('select', label); }
76
+ function selectRow(label: string) { onselect?.(label); }
68
77
  /** True when this 1d row's key matches focus — either as a bare label
69
78
  (`"primary"` matches focusedVariant `"primary"`) or as a compound
70
79
  `"variant state"` matching the focused pair. */
@@ -92,11 +101,11 @@
92
101
  class="row-h row-target"
93
102
  class:hovered={hoveredRow === i}
94
103
  class:selected={selectedRow === r}
95
- on:click={() => selectRow(r)}
96
- on:mouseenter={() => (hoveredRow = i)}
97
- on:mouseleave={() => (hoveredRow = -1)}
98
- on:focus={() => (hoveredRow = i)}
99
- on:blur={() => (hoveredRow = -1)}
104
+ onclick={() => selectRow(r)}
105
+ onmouseenter={() => (hoveredRow = i)}
106
+ onmouseleave={() => (hoveredRow = -1)}
107
+ onfocus={() => (hoveredRow = i)}
108
+ onblur={() => (hoveredRow = -1)}
100
109
  >{r}</button>
101
110
  {#each axes.cols as c (c)}
102
111
  {@const st = status.get(key2d(r, c)) ?? 'absent'}
@@ -108,9 +117,9 @@
108
117
  class:selected={selectedRow === r && selectedCol === c}
109
118
  class:in-selected-row={selectedRow === r && selectedCol !== c}
110
119
  aria-label="{r} {c}: {st}"
111
- on:click={() => selectRow(r)}
112
- on:mouseenter={() => (hoveredRow = i)}
113
- on:mouseleave={() => (hoveredRow = -1)}
120
+ onclick={() => selectRow(r)}
121
+ onmouseenter={() => (hoveredRow = i)}
122
+ onmouseleave={() => (hoveredRow = -1)}
114
123
  >
115
124
  {#if st === 'linked'}
116
125
  <span class="dot" aria-hidden="true"></span>
@@ -138,11 +147,11 @@
138
147
  class="row-h row-target"
139
148
  class:hovered={hoveredRow === i}
140
149
  class:selected={isSel}
141
- on:click={() => selectRow(r.label)}
142
- on:mouseenter={() => (hoveredRow = i)}
143
- on:mouseleave={() => (hoveredRow = -1)}
144
- on:focus={() => (hoveredRow = i)}
145
- on:blur={() => (hoveredRow = -1)}
150
+ onclick={() => selectRow(r.label)}
151
+ onmouseenter={() => (hoveredRow = i)}
152
+ onmouseleave={() => (hoveredRow = -1)}
153
+ onfocus={() => (hoveredRow = i)}
154
+ onblur={() => (hoveredRow = -1)}
146
155
  >{r.label}</button>
147
156
  <button
148
157
  type="button"
@@ -151,9 +160,9 @@
151
160
  class:hovered={hoveredRow === i}
152
161
  class:selected={isSel}
153
162
  aria-label="{r.label}: {st}"
154
- on:click={() => selectRow(r.label)}
155
- on:mouseenter={() => (hoveredRow = i)}
156
- on:mouseleave={() => (hoveredRow = -1)}
163
+ onclick={() => selectRow(r.label)}
164
+ onmouseenter={() => (hoveredRow = i)}
165
+ onmouseleave={() => (hoveredRow = -1)}
157
166
  >
158
167
  {#if st === 'linked'}
159
168
  <span class="dot" aria-hidden="true"></span>