@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.
- package/README.md +1 -1
- package/package.json +11 -9
- package/src/component-editor/BadgeEditor.svelte +24 -22
- package/src/component-editor/CalloutEditor.svelte +3 -3
- package/src/component-editor/CardEditor.svelte +25 -21
- package/src/component-editor/CollapsibleSectionEditor.svelte +27 -25
- package/src/component-editor/CornerBadgeEditor.svelte +37 -35
- package/src/component-editor/DialogEditor.svelte +26 -24
- package/src/component-editor/ImageEditor.svelte +11 -9
- package/src/component-editor/InlineEditActionsEditor.svelte +17 -15
- package/src/component-editor/NotificationEditor.svelte +32 -30
- package/src/component-editor/ProgressBarEditor.svelte +3 -3
- package/src/component-editor/RadioButtonEditor.svelte +31 -29
- package/src/component-editor/SectionDividerEditor.svelte +30 -28
- package/src/component-editor/SegmentedControlEditor.svelte +29 -25
- package/src/component-editor/StandardButtonsEditor.svelte +42 -38
- package/src/component-editor/TabBarEditor.svelte +20 -18
- package/src/component-editor/TableEditor.svelte +4 -4
- package/src/component-editor/TooltipEditor.svelte +11 -9
- package/src/component-editor/registry.ts +2 -2
- package/src/component-editor/scaffolding/AngleDial.svelte +20 -19
- package/src/component-editor/scaffolding/ComponentEditorBase.svelte +44 -20
- package/src/component-editor/scaffolding/ComponentFileManager.svelte +260 -37
- package/src/component-editor/scaffolding/ComponentFileMenu.svelte +41 -29
- package/src/component-editor/scaffolding/ComponentsTab.svelte +7 -3
- package/src/component-editor/scaffolding/CopyFromMenu.svelte +21 -12
- package/src/component-editor/scaffolding/DemoHeader.svelte +13 -4
- package/src/component-editor/scaffolding/DividerEditor.svelte +27 -14
- package/src/component-editor/scaffolding/FieldsetWrapper.svelte +10 -4
- package/src/component-editor/scaffolding/GradientCard.svelte +25 -20
- package/src/component-editor/scaffolding/LinkageChart.svelte +43 -34
- package/src/component-editor/scaffolding/LinkedBlock.svelte +24 -21
- package/src/component-editor/scaffolding/NonStylableConfig.svelte +6 -1
- package/src/component-editor/scaffolding/SaveAsDialog.svelte +39 -35
- package/src/component-editor/scaffolding/ShadowBackdrop.svelte +21 -9
- package/src/component-editor/scaffolding/ShadowBackdropControls.svelte +8 -3
- package/src/component-editor/scaffolding/StateBlock.svelte +30 -13
- package/src/component-editor/scaffolding/TokenLayout.svelte +46 -30
- package/src/component-editor/scaffolding/TypeEditor.svelte +52 -26
- package/src/component-editor/scaffolding/VariantGroup.svelte +81 -48
- package/src/component-editor/scaffolding/componentSectionType.ts +2 -2
- package/src/components/Badge.svelte +45 -26
- package/src/components/Button.svelte +44 -21
- package/src/components/Callout.svelte +17 -12
- package/src/components/Card.svelte +23 -11
- package/src/components/CollapsibleSection.svelte +56 -27
- package/src/components/CornerBadge.svelte +32 -18
- package/src/components/Dialog.svelte +55 -31
- package/src/components/Image.svelte +14 -5
- package/src/components/InlineEditActions.svelte +22 -10
- package/src/components/Notification.svelte +39 -19
- package/src/components/ProgressBar.svelte +27 -17
- package/src/components/RadioButton.svelte +27 -10
- package/src/components/SectionDivider.svelte +34 -26
- package/src/components/SegmentedControl.svelte +23 -9
- package/src/components/TabBar.svelte +23 -10
- package/src/components/Table.svelte +8 -3
- package/src/components/Tooltip.svelte +15 -5
- package/src/lib/ColumnsOverlay.svelte +3 -3
- package/src/lib/LiveEditorOverlay.svelte +73 -36
- package/src/pages/ComponentEditorPage.svelte +17 -13
- package/src/pages/EditorShell.svelte +24 -20
- package/src/styles/form-controls.css +2 -2
- package/src/styles/tokens.css +59 -81
- package/src/ui/BezierCurveEditor.svelte +59 -43
- package/src/ui/ColorEditPanel.svelte +71 -44
- package/src/ui/EditorViewSwitcher.svelte +9 -5
- package/src/ui/FontStackEditor.svelte +16 -15
- package/src/ui/GradientEditor.svelte +42 -33
- package/src/ui/GradientStopPicker.svelte +18 -29
- package/src/ui/PaletteEditor.svelte +238 -212
- package/src/ui/PresetFileManager.svelte +20 -18
- package/src/ui/ProjectFontsSection.svelte +30 -30
- package/src/ui/SurfacesTab.svelte +3 -3
- package/src/ui/TextTab.svelte +2 -2
- package/src/ui/ThemeFileManager.svelte +38 -35
- package/src/ui/Toggle.svelte +11 -9
- package/src/ui/UICopyPopover.svelte +19 -15
- package/src/ui/UIDialog.svelte +48 -30
- package/src/ui/UIFontFamilySelector.svelte +104 -78
- package/src/ui/UIFontSizeSelector.svelte +38 -20
- package/src/ui/UIFontWeightSelector.svelte +33 -13
- package/src/ui/UILineHeightSelector.svelte +33 -13
- package/src/ui/UILinkToggle.svelte +7 -6
- package/src/ui/UIOptionItem.svelte +21 -7
- package/src/ui/UIOptionList.svelte +9 -3
- package/src/ui/UIPaddingSelector.svelte +108 -82
- package/src/ui/UIPaletteSelector.svelte +186 -161
- package/src/ui/UIRadio.svelte +23 -8
- package/src/ui/UIRadioGroup.svelte +9 -8
- package/src/ui/UIRelinkConfirmPopover.svelte +26 -16
- package/src/ui/UITokenSelector.svelte +112 -68
- package/src/ui/UIVariantSelector.svelte +79 -57
- package/src/ui/VariablesTab.svelte +15 -15
- package/src/ui/palette/GradientStopEditor.svelte +45 -26
- package/src/ui/palette/OverridesPanel.svelte +85 -49
- package/src/ui/palette/PaletteBase.svelte +60 -32
- package/src/ui/palette/ScaleCurveEditor.svelte +25 -10
- package/src/ui/sections/ColumnsSection.svelte +13 -13
- package/src/ui/sections/GradientsSection.svelte +12 -9
- package/src/ui/sections/OverlaysSection.svelte +50 -47
- package/src/ui/sections/ShadowsSection.svelte +110 -104
- package/src/ui/sections/TokenScaleTable.svelte +38 -22
- package/src/ui/sections/tokenScales.ts +2 -2
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
|
-
import {
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
}
|
|
18
|
+
onopenLoad?: () => void;
|
|
19
|
+
onload?: (file: ComponentConfigMeta) => void;
|
|
20
|
+
ondelete?: (file: ComponentConfigMeta) => void;
|
|
21
|
+
}
|
|
21
22
|
|
|
22
|
-
let
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
55
|
+
onsave?.();
|
|
44
56
|
}
|
|
45
57
|
|
|
46
58
|
function handleSaveAs() {
|
|
47
59
|
fileMenuOpen = false;
|
|
48
|
-
|
|
60
|
+
onsaveAs?.();
|
|
49
61
|
}
|
|
50
62
|
|
|
51
63
|
function handleOpenLoad() {
|
|
52
64
|
fileMenuOpen = false;
|
|
53
65
|
showFileList = true;
|
|
54
|
-
|
|
66
|
+
onopenLoad?.();
|
|
55
67
|
}
|
|
56
68
|
|
|
57
69
|
function handleLoad(file: ComponentConfigMeta) {
|
|
58
70
|
showFileList = false;
|
|
59
|
-
|
|
71
|
+
onload?.(file);
|
|
60
72
|
}
|
|
61
73
|
|
|
62
74
|
function handleDelete(file: ComponentConfigMeta) {
|
|
63
75
|
if (file.fileName === 'default') return;
|
|
64
|
-
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
<
|
|
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 {
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
3
|
|
|
4
4
|
type Source = { name: string; label: string; states: string[] };
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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
|
-
|
|
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}
|
|
164
|
+
<AngleDial value={angleDeg} onchange={(d) => setAngle(d.value)} />
|
|
160
165
|
</div>
|
|
161
166
|
</div>
|
|
162
167
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
47
|
-
|
|
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
|
-
|
|
50
|
-
|
|
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
|
-
|
|
51
|
+
selectedCol?: string | null;
|
|
52
|
+
onselect?: (label: string) => void;
|
|
53
|
+
}
|
|
53
54
|
|
|
54
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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) {
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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>
|