@motion-proto/live-tokens 0.10.0 → 0.11.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/package.json +1 -1
- package/src/editor/component-editor/CardEditor.svelte +2 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +10 -1
- package/src/editor/core/store/editorRenderer.ts +1 -4
- package/src/editor/core/store/editorStore.ts +5 -9
- package/src/editor/core/store/editorTypes.ts +6 -13
- package/src/editor/core/themes/migrations/2026-05-13-primary-to-brand.ts +2 -29
- package/src/editor/core/themes/migrations/2026-05-26-drop-overlay-extra-stops.ts +46 -0
- package/src/editor/core/themes/migrations/index.ts +6 -0
- package/src/editor/core/themes/slices/overlays.ts +44 -75
- package/src/editor/ui/SurfacesTab.svelte +3 -7
- package/src/editor/ui/UIPaddingSelector.svelte +2 -2
- package/src/editor/ui/UIPaletteSelector.svelte +151 -36
- package/src/editor/ui/{UIRelinkConfirmPopover.svelte → UIRelinkConfirmDialog.svelte} +107 -75
- package/src/editor/ui/UITokenSelector.svelte +15 -2
- package/src/editor/ui/sections/OverlaysSection.svelte +107 -540
- package/src/system/components/Card.svelte +2 -1
- package/src/system/components/ImageLightbox.svelte +1 -1
- package/src/system/components/TabBar.svelte +1 -1
- package/src/system/styles/tokens.css +5 -5
- package/src/system/styles/tokens.generated.css +31 -4
package/package.json
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
{ label: 'background blur', canBeLinked: true, groupKey: 'blur', variable: '--card-default-blur', element: 'frame' },
|
|
16
16
|
{ label: 'header color', groupKey: 'surface', variable: '--card-default-header-surface', element: 'header' },
|
|
17
17
|
{ label: 'header padding', canBeLinked: true, groupKey: 'header-padding', variable: '--card-default-header-padding', element: 'header' },
|
|
18
|
+
{ label: 'icon gap', canBeLinked: true, groupKey: 'header-gap', variable: '--card-default-header-gap', element: 'header' },
|
|
18
19
|
{ label: 'icon size', canBeLinked: true, groupKey: 'icon-size', variable: '--card-default-icon-size', element: 'header' },
|
|
19
20
|
{ label: 'body padding', canBeLinked: true, groupKey: 'body-padding', variable: '--card-default-body-padding', element: 'body' },
|
|
20
21
|
],
|
|
@@ -63,6 +64,7 @@
|
|
|
63
64
|
['--card-default-border-width', 'card'],
|
|
64
65
|
['--card-default-radius', 'card'],
|
|
65
66
|
['--card-default-header-padding', 'card'],
|
|
67
|
+
['--card-default-header-gap', 'card'],
|
|
66
68
|
['--card-default-body-padding', 'card'],
|
|
67
69
|
['--card-default-shadow', 'card'],
|
|
68
70
|
['--card-default-blur', 'card'],
|
|
@@ -53,7 +53,6 @@
|
|
|
53
53
|
min-height: 12rem;
|
|
54
54
|
border-radius: var(--ui-radius-md);
|
|
55
55
|
box-sizing: border-box;
|
|
56
|
-
overflow: hidden;
|
|
57
56
|
}
|
|
58
57
|
|
|
59
58
|
.shadow-backdrop.with-controls {
|
|
@@ -61,12 +60,22 @@
|
|
|
61
60
|
grid-template-areas: "preview controls";
|
|
62
61
|
}
|
|
63
62
|
|
|
63
|
+
/* Clip slotted children (e.g. Dialog's full-bleed overlay in padding=0 mode)
|
|
64
|
+
to the backdrop's rounded corners here rather than on the wrapper, so the
|
|
65
|
+
controls cell's dropdowns can escape vertically. The wrapper's image/color
|
|
66
|
+
background is still clipped naturally by its own border-radius. */
|
|
64
67
|
.shadow-backdrop-content {
|
|
65
68
|
display: grid;
|
|
66
69
|
align-items: center;
|
|
67
70
|
justify-items: start;
|
|
68
71
|
min-width: 0;
|
|
69
72
|
grid-area: preview;
|
|
73
|
+
border-radius: var(--ui-radius-md);
|
|
74
|
+
overflow: hidden;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.shadow-backdrop.with-controls .shadow-backdrop-content {
|
|
78
|
+
border-radius: var(--ui-radius-md) 0 0 var(--ui-radius-md);
|
|
70
79
|
}
|
|
71
80
|
|
|
72
81
|
.shadow-backdrop-controls {
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
columnsToVars,
|
|
20
20
|
columnsEqualsDefault,
|
|
21
21
|
overlaysToVars,
|
|
22
|
-
overlaysEqualsDefault,
|
|
23
22
|
shadowsToVars,
|
|
24
23
|
gradientsToVars,
|
|
25
24
|
componentsToVars,
|
|
@@ -30,9 +29,7 @@ export function deriveCssVars(state: EditorState): Record<string, string> {
|
|
|
30
29
|
if (!columnsEqualsDefault(state.columns)) {
|
|
31
30
|
Object.assign(out, columnsToVars(state.columns));
|
|
32
31
|
}
|
|
33
|
-
|
|
34
|
-
Object.assign(out, overlaysToVars(state.overlays));
|
|
35
|
-
}
|
|
32
|
+
Object.assign(out, overlaysToVars(state.overlays));
|
|
36
33
|
if (state.shadows.tokens.length > 0) {
|
|
37
34
|
Object.assign(out, shadowsToVars(state.shadows));
|
|
38
35
|
}
|
|
@@ -51,7 +51,6 @@ import {
|
|
|
51
51
|
import {
|
|
52
52
|
loadOverlaysFromVars,
|
|
53
53
|
makeDefaultOverlaysState,
|
|
54
|
-
overlaysEqualsDefault,
|
|
55
54
|
overlaysToVars,
|
|
56
55
|
} from '../themes/slices/overlays';
|
|
57
56
|
import {
|
|
@@ -133,14 +132,13 @@ export {
|
|
|
133
132
|
|
|
134
133
|
export {
|
|
135
134
|
overlaysToVars,
|
|
136
|
-
overlaysEqualsDefault,
|
|
137
135
|
OVERLAY_VAR_NAMES,
|
|
138
136
|
applyOverlayVarsToState,
|
|
139
137
|
makeDefaultOverlaysState,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
138
|
+
makeDefaultOverlayTokens,
|
|
139
|
+
makeDefaultHoverTokens,
|
|
140
|
+
overlayTokenToCss,
|
|
141
|
+
parseOverlayCss,
|
|
144
142
|
} from '../themes/slices/overlays';
|
|
145
143
|
|
|
146
144
|
export {
|
|
@@ -548,9 +546,7 @@ export function toTheme(state: EditorState, meta: { name: string }): Theme {
|
|
|
548
546
|
if (!columnsEqualsDefault(state.columns)) {
|
|
549
547
|
Object.assign(cssVariables, columnsToVars(state.columns));
|
|
550
548
|
}
|
|
551
|
-
|
|
552
|
-
Object.assign(cssVariables, overlaysToVars(state.overlays));
|
|
553
|
-
}
|
|
549
|
+
Object.assign(cssVariables, overlaysToVars(state.overlays));
|
|
554
550
|
if (state.shadows.tokens.length > 0) {
|
|
555
551
|
Object.assign(cssVariables, shadowsToVars(state.shadows));
|
|
556
552
|
}
|
|
@@ -21,19 +21,13 @@ export interface ShadowOverrideFlags {
|
|
|
21
21
|
distance: boolean; blur: boolean; size: boolean;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
/** Overlay stop: an aliased color token + an opacity. Emits as
|
|
25
|
+
* `color-mix(in srgb, var(<alias>) <opacity%>, transparent)`. */
|
|
24
26
|
export interface OverlayToken {
|
|
25
|
-
variable: string;
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
export interface OverlayChannelGlobals {
|
|
30
|
-
hue: number; saturation: number; lightness: number;
|
|
31
|
-
opacityMin: number; opacityMax: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface OverlayGlobals {
|
|
35
|
-
overlay: OverlayChannelGlobals;
|
|
36
|
-
hover: OverlayChannelGlobals;
|
|
27
|
+
variable: string;
|
|
28
|
+
label: string;
|
|
29
|
+
alias: string;
|
|
30
|
+
opacity: number;
|
|
37
31
|
}
|
|
38
32
|
|
|
39
33
|
export interface ColumnsState {
|
|
@@ -134,7 +128,6 @@ export interface EditorState {
|
|
|
134
128
|
overlays: {
|
|
135
129
|
tokens: OverlayToken[];
|
|
136
130
|
hoverTokens: OverlayToken[];
|
|
137
|
-
globals: OverlayGlobals;
|
|
138
131
|
};
|
|
139
132
|
columns: ColumnsState;
|
|
140
133
|
components: Record<string, ComponentSlice>;
|
|
@@ -1,26 +1,6 @@
|
|
|
1
1
|
import type { Migration } from './index';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
* 2026-05-13: rename the `primary` color family to `brand`.
|
|
5
|
-
*
|
|
6
|
-
* The word "primary" was overloaded — both a color-family slot (alongside
|
|
7
|
-
* info/success/danger/etc.) AND the top of the neutral text-emphasis ramp
|
|
8
|
-
* (`--text-primary` / `--text-secondary` / `--text-tertiary`). Tokens like
|
|
9
|
-
* `--text-primary-color` only existed because `--text-primary` was already
|
|
10
|
-
* taken by neutral. Renaming the family to `brand` dissolves the collision:
|
|
11
|
-
*
|
|
12
|
-
* --color-primary-{step} → --color-brand-{step}
|
|
13
|
-
* --surface-primary[-step] → --surface-brand[-step]
|
|
14
|
-
* --border-primary[-step] → --border-brand[-step]
|
|
15
|
-
* --text-primary-color → --text-brand
|
|
16
|
-
* --text-primary-{step} → --text-brand-{step} (step in secondary|tertiary|muted|disabled)
|
|
17
|
-
*
|
|
18
|
-
* The neutral text ramp (`--text-primary`, `--text-secondary`, …) is **left
|
|
19
|
-
* untouched** — it's a different namespace that just happens to share the
|
|
20
|
-
* word. The match list below enumerates the brand-family names explicitly so
|
|
21
|
-
* a substring-on-`primary` mistake can't clobber neutral text or unrelated
|
|
22
|
-
* tokens like `--button-primary-*` (component variant, not family).
|
|
23
|
-
*/
|
|
3
|
+
// Match list is explicit so the neutral `--text-primary` ramp (different namespace) isn't clobbered.
|
|
24
4
|
|
|
25
5
|
const PALETTE_STEPS = ['100','200','300','400','500','600','700','800','850','900','950'] as const;
|
|
26
6
|
const SURFACE_SUFFIXES = ['lowest','lower','low','high','higher','highest'] as const;
|
|
@@ -43,7 +23,6 @@ function renameToken(name: string): string {
|
|
|
43
23
|
return RENAME_MAP[name] ?? name;
|
|
44
24
|
}
|
|
45
25
|
|
|
46
|
-
/** Rewrite keys only — used for theme cssVariables (values are hex). */
|
|
47
26
|
function renameKeys(rawVars: Record<string, string>): Record<string, string> {
|
|
48
27
|
const out: Record<string, string> = {};
|
|
49
28
|
for (const [k, v] of Object.entries(rawVars)) {
|
|
@@ -52,7 +31,6 @@ function renameKeys(rawVars: Record<string, string>): Record<string, string> {
|
|
|
52
31
|
return out;
|
|
53
32
|
}
|
|
54
33
|
|
|
55
|
-
/** Rewrite both keys and values — used for component-config aliases. */
|
|
56
34
|
function renameKeysAndValues(rawVars: Record<string, string>): Record<string, string> {
|
|
57
35
|
const out: Record<string, string> = {};
|
|
58
36
|
for (const [k, v] of Object.entries(rawVars)) {
|
|
@@ -77,12 +55,7 @@ export const componentMigration_2026_05_13_primaryToBrand: Migration = {
|
|
|
77
55
|
apply: renameKeysAndValues,
|
|
78
56
|
};
|
|
79
57
|
|
|
80
|
-
|
|
81
|
-
* Helper for loadFromFile: rename `theme.editorConfigs.Primary` → `Brand`.
|
|
82
|
-
* Lives here (with the rest of the rename) instead of in the migration
|
|
83
|
-
* framework because the framework's contract is `Record<string, string>` —
|
|
84
|
-
* editorConfigs is a structured palette-config map and can't pass through it.
|
|
85
|
-
*/
|
|
58
|
+
// Lives outside the migration framework because its contract is Record<string, string>; editorConfigs is structured.
|
|
86
59
|
export function renamePrimaryPaletteKey<T>(editorConfigs: Record<string, T>): Record<string, T> {
|
|
87
60
|
if (!('Primary' in editorConfigs) || 'Brand' in editorConfigs) return editorConfigs;
|
|
88
61
|
const { Primary, ...rest } = editorConfigs;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Migration } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Overlay scale trim (2026-05-26): `--overlay-lowest`, `--overlay-lower`,
|
|
5
|
+
* `--overlay-higher`, and `--overlay-highest` were retired. The kept stops
|
|
6
|
+
* are `--overlay-low`, `--overlay`, and `--overlay-high`. Theme files
|
|
7
|
+
* stripped of the dropped keys; component configs whose aliases referenced
|
|
8
|
+
* a dropped stop rebind to the nearest survivor.
|
|
9
|
+
*/
|
|
10
|
+
const DROPPED_TO_KEPT: Record<string, string> = {
|
|
11
|
+
'--overlay-lowest': '--overlay-low',
|
|
12
|
+
'--overlay-lower': '--overlay-low',
|
|
13
|
+
'--overlay-higher': '--overlay-high',
|
|
14
|
+
'--overlay-highest': '--overlay-high',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const themeMigration_2026_05_26_dropOverlayExtraStops: Migration = {
|
|
18
|
+
id: '2026-05-26-drop-overlay-extra-stops-theme',
|
|
19
|
+
fromVersion: 2,
|
|
20
|
+
toVersion: 3,
|
|
21
|
+
appliesTo: 'theme',
|
|
22
|
+
apply(rawVars) {
|
|
23
|
+
const out: Record<string, string> = {};
|
|
24
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
25
|
+
if (key in DROPPED_TO_KEPT) continue;
|
|
26
|
+
out[key] = value;
|
|
27
|
+
}
|
|
28
|
+
return out;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export const componentMigration_2026_05_26_dropOverlayExtraStops: Migration = {
|
|
33
|
+
id: '2026-05-26-drop-overlay-extra-stops-component',
|
|
34
|
+
fromVersion: 16,
|
|
35
|
+
toVersion: 17,
|
|
36
|
+
appliesTo: 'component-config',
|
|
37
|
+
apply(rawVars) {
|
|
38
|
+
const out: Record<string, string> = {};
|
|
39
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
40
|
+
// Alias values are bare token names like "--overlay-higher" — rebind
|
|
41
|
+
// those to the nearest kept stop. Literal CSS values are left alone.
|
|
42
|
+
out[key] = DROPPED_TO_KEPT[value] ?? value;
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
@@ -50,6 +50,10 @@ import { componentMigration_2026_05_24_promoteStateSharedTokens } from './2026-0
|
|
|
50
50
|
import { componentMigration_2026_05_24_progressbarCollapseVariants } from './2026-05-24-progressbar-collapse-variants';
|
|
51
51
|
import { componentMigration_2026_05_24_collapsiblesectionDropActiveState } from './2026-05-24-collapsiblesection-drop-active-state';
|
|
52
52
|
import { componentMigration_2026_05_25_cornerbadgeFlattenVariants } from './2026-05-25-cornerbadge-flatten-variants';
|
|
53
|
+
import {
|
|
54
|
+
themeMigration_2026_05_26_dropOverlayExtraStops,
|
|
55
|
+
componentMigration_2026_05_26_dropOverlayExtraStops,
|
|
56
|
+
} from './2026-05-26-drop-overlay-extra-stops';
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Registered migrations. Order in this array does not matter — the runner
|
|
@@ -74,6 +78,8 @@ export const MIGRATIONS: Migration[] = [
|
|
|
74
78
|
componentMigration_2026_05_24_progressbarCollapseVariants,
|
|
75
79
|
componentMigration_2026_05_24_collapsiblesectionDropActiveState,
|
|
76
80
|
componentMigration_2026_05_25_cornerbadgeFlattenVariants,
|
|
81
|
+
themeMigration_2026_05_26_dropOverlayExtraStops,
|
|
82
|
+
componentMigration_2026_05_26_dropOverlayExtraStops,
|
|
77
83
|
];
|
|
78
84
|
|
|
79
85
|
function countFor(kind: 'theme' | 'component-config'): number {
|
|
@@ -1,28 +1,28 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Overlays slice — overlay + hover
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Overlays slice — overlay + hover stops, each stored as `{alias, opacity}`.
|
|
3
|
+
* Emits as `color-mix(in srgb, var(<alias>) <pct>%, transparent)` so the
|
|
4
|
+
* overlay tints automatically follow the aliased source token (a brand-color
|
|
5
|
+
* shift propagates without re-editing every overlay).
|
|
6
|
+
*
|
|
7
|
+
* Defaults diverge from tokens.css by design: the editor starts from a
|
|
8
|
+
* neutral alias and tokens.css continues to win until first edit (see
|
|
9
|
+
* `overlaysEqualsDefault`).
|
|
6
10
|
*/
|
|
7
11
|
import type { EditorState, OverlayToken } from '../../store/editorTypes';
|
|
8
12
|
|
|
9
13
|
export function makeDefaultOverlayTokens(): OverlayToken[] {
|
|
10
14
|
return [
|
|
11
|
-
{ variable: '--overlay-
|
|
12
|
-
{ variable: '--overlay
|
|
13
|
-
{ variable: '--overlay-
|
|
14
|
-
{ variable: '--overlay', label: 'Base', r: 0, g: 0, b: 0, opacity: 0.3 },
|
|
15
|
-
{ variable: '--overlay-high', label: 'High', r: 0, g: 0, b: 0, opacity: 0.5 },
|
|
16
|
-
{ variable: '--overlay-higher', label: 'Higher', r: 0, g: 0, b: 0, opacity: 0.7 },
|
|
17
|
-
{ variable: '--overlay-highest',label: 'Highest',r: 0, g: 0, b: 0, opacity: 0.95 },
|
|
15
|
+
{ variable: '--overlay-low', label: 'Low', alias: '--surface-neutral-lowest', opacity: 0.38 },
|
|
16
|
+
{ variable: '--overlay', label: 'Base', alias: '--surface-neutral-lowest', opacity: 0.51 },
|
|
17
|
+
{ variable: '--overlay-high', label: 'High', alias: '--surface-neutral-lowest', opacity: 0.64 },
|
|
18
18
|
];
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
export function makeDefaultHoverTokens(): OverlayToken[] {
|
|
22
22
|
return [
|
|
23
|
-
{ variable: '--hover-low', label: 'Low',
|
|
24
|
-
{ variable: '--hover', label: 'Base',
|
|
25
|
-
{ variable: '--hover-high', label: 'High',
|
|
23
|
+
{ variable: '--hover-low', label: 'Low', alias: '--text-primary', opacity: 0.05 },
|
|
24
|
+
{ variable: '--hover', label: 'Base', alias: '--text-primary', opacity: 0.1 },
|
|
25
|
+
{ variable: '--hover-high', label: 'High', alias: '--text-primary', opacity: 0.15 },
|
|
26
26
|
];
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -30,88 +30,52 @@ export function makeDefaultOverlaysState(): EditorState['overlays'] {
|
|
|
30
30
|
return {
|
|
31
31
|
tokens: makeDefaultOverlayTokens(),
|
|
32
32
|
hoverTokens: makeDefaultHoverTokens(),
|
|
33
|
-
globals: {
|
|
34
|
-
overlay: { hue: 0, saturation: 0, lightness: 0, opacityMin: 0.05, opacityMax: 0.95 },
|
|
35
|
-
hover: { hue: 0, saturation: 0, lightness: 100, opacityMin: 0.05, opacityMax: 0.15 },
|
|
36
|
-
},
|
|
37
33
|
};
|
|
38
34
|
}
|
|
39
35
|
|
|
40
36
|
export const OVERLAY_VAR_NAMES = [
|
|
41
|
-
'--overlay-
|
|
42
|
-
'--overlay-high', '--overlay-higher', '--overlay-highest',
|
|
37
|
+
'--overlay-low', '--overlay', '--overlay-high',
|
|
43
38
|
'--hover-low', '--hover', '--hover-high',
|
|
44
39
|
] as const;
|
|
45
40
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
41
|
+
export function overlayTokenToCss(t: OverlayToken): string {
|
|
42
|
+
const pct = Math.round(t.opacity * 100);
|
|
43
|
+
if (pct >= 100) return `var(${t.alias})`;
|
|
44
|
+
return `color-mix(in srgb, var(${t.alias}) ${pct}%, transparent)`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const COLOR_MIX_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+)%,\s*transparent\)$/i;
|
|
48
|
+
const PLAIN_VAR_RE = /^var\((--[a-z0-9-]+)\)$/i;
|
|
50
49
|
|
|
51
|
-
export function
|
|
50
|
+
export function parseOverlayCss(raw: string): { alias: string; opacity: number } | null {
|
|
52
51
|
const s = raw.trim();
|
|
53
|
-
const
|
|
54
|
-
if (
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const a = m[4] !== undefined ? parseFloat(m[4]) : 1;
|
|
59
|
-
if (![r, g, b].every((n) => Number.isFinite(n) && n >= 0 && n <= 255)) return null;
|
|
60
|
-
return { r, g, b, opacity: Number.isFinite(a) ? a : 1 };
|
|
61
|
-
}
|
|
62
|
-
const h = s.match(HEX_RE);
|
|
63
|
-
if (h) {
|
|
64
|
-
const hex = h[1];
|
|
65
|
-
const alpha = h[2] !== undefined ? parseInt(h[2], 16) / 255 : 1;
|
|
66
|
-
return {
|
|
67
|
-
r: parseInt(hex.slice(0, 2), 16),
|
|
68
|
-
g: parseInt(hex.slice(2, 4), 16),
|
|
69
|
-
b: parseInt(hex.slice(4, 6), 16),
|
|
70
|
-
opacity: Math.round(alpha * 100) / 100,
|
|
71
|
-
};
|
|
52
|
+
const mix = s.match(COLOR_MIX_RE);
|
|
53
|
+
if (mix) {
|
|
54
|
+
const pct = parseInt(mix[2], 10);
|
|
55
|
+
if (!Number.isFinite(pct)) return null;
|
|
56
|
+
return { alias: mix[1], opacity: Math.max(0, Math.min(100, pct)) / 100 };
|
|
72
57
|
}
|
|
58
|
+
const plain = s.match(PLAIN_VAR_RE);
|
|
59
|
+
if (plain) return { alias: plain[1], opacity: 1 };
|
|
73
60
|
return null;
|
|
74
61
|
}
|
|
75
62
|
|
|
76
|
-
export function overlayTokenToRgba(t: OverlayToken): string {
|
|
77
|
-
return `rgba(${t.r}, ${t.g}, ${t.b}, ${t.opacity})`;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
63
|
export function overlaysToVars(o: EditorState['overlays']): Record<string, string> {
|
|
81
64
|
const out: Record<string, string> = {};
|
|
82
|
-
for (const t of o.tokens) out[t.variable] =
|
|
83
|
-
for (const t of o.hoverTokens) out[t.variable] =
|
|
65
|
+
for (const t of o.tokens) out[t.variable] = overlayTokenToCss(t);
|
|
66
|
+
for (const t of o.hoverTokens) out[t.variable] = overlayTokenToCss(t);
|
|
84
67
|
return out;
|
|
85
68
|
}
|
|
86
69
|
|
|
87
|
-
function tokensEqualDefault(tokens: OverlayToken[], defaults: OverlayToken[]): boolean {
|
|
88
|
-
if (tokens.length !== defaults.length) return false;
|
|
89
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
90
|
-
const a = tokens[i]; const b = defaults[i];
|
|
91
|
-
if (a.variable !== b.variable || a.r !== b.r || a.g !== b.g || a.b !== b.b || a.opacity !== b.opacity) return false;
|
|
92
|
-
}
|
|
93
|
-
return true;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
/**
|
|
97
|
-
* Same pattern as columns: only emit overlay CSS vars once state diverges
|
|
98
|
-
* from the editor defaults. tokens.css owns the rgba values until the
|
|
99
|
-
* user touches any overlay control (or loads a theme that already
|
|
100
|
-
* contains overrides).
|
|
101
|
-
*/
|
|
102
|
-
export function overlaysEqualsDefault(o: EditorState['overlays']): boolean {
|
|
103
|
-
return tokensEqualDefault(o.tokens, makeDefaultOverlayTokens())
|
|
104
|
-
&& tokensEqualDefault(o.hoverTokens, makeDefaultHoverTokens());
|
|
105
|
-
}
|
|
106
|
-
|
|
107
70
|
export function applyOverlayVarsToState(overlays: EditorState['overlays'], vars: Record<string, string>): void {
|
|
108
71
|
const applyTo = (list: OverlayToken[]) => {
|
|
109
72
|
for (const t of list) {
|
|
110
73
|
const raw = vars[t.variable];
|
|
111
74
|
if (!raw) continue;
|
|
112
|
-
const parsed =
|
|
75
|
+
const parsed = parseOverlayCss(raw);
|
|
113
76
|
if (!parsed) continue;
|
|
114
|
-
t.
|
|
77
|
+
t.alias = parsed.alias;
|
|
78
|
+
t.opacity = parsed.opacity;
|
|
115
79
|
}
|
|
116
80
|
};
|
|
117
81
|
applyTo(overlays.tokens);
|
|
@@ -120,13 +84,18 @@ export function applyOverlayVarsToState(overlays: EditorState['overlays'], vars:
|
|
|
120
84
|
|
|
121
85
|
/**
|
|
122
86
|
* Loader: route overlay/hover entries from a freshly-loaded theme's vars
|
|
123
|
-
* bag into `next.overlays` and remove them from the bag
|
|
124
|
-
*
|
|
87
|
+
* bag into `next.overlays` and remove them from the bag — but only when the
|
|
88
|
+
* value parses as the new format (color-mix / plain var). Legacy rgba values
|
|
89
|
+
* pass through to the cssVars bag so the DOM still paints them; the user's
|
|
90
|
+
* next edit in the picker promotes them to the typed slice.
|
|
125
91
|
*/
|
|
126
92
|
export function loadOverlaysFromVars(
|
|
127
93
|
next: EditorState,
|
|
128
94
|
rawVars: Record<string, string>,
|
|
129
95
|
): void {
|
|
130
96
|
applyOverlayVarsToState(next.overlays, rawVars);
|
|
131
|
-
for (const name of OVERLAY_VAR_NAMES)
|
|
97
|
+
for (const name of OVERLAY_VAR_NAMES) {
|
|
98
|
+
const raw = rawVars[name];
|
|
99
|
+
if (raw && parseOverlayCss(raw) !== null) delete rawVars[name];
|
|
100
|
+
}
|
|
132
101
|
}
|
|
@@ -31,13 +31,9 @@
|
|
|
31
31
|
{
|
|
32
32
|
title: 'Overlays',
|
|
33
33
|
swatches: [
|
|
34
|
-
{ name: '
|
|
35
|
-
{ name: '
|
|
36
|
-
{ name: '
|
|
37
|
-
{ name: 'Overlay', variable: '--overlay', description: 'Standard overlay' },
|
|
38
|
-
{ name: 'High', variable: '--overlay-high', description: 'Heavy overlay' },
|
|
39
|
-
{ name: 'Higher', variable: '--overlay-higher', description: 'Modal backdrop' },
|
|
40
|
-
{ name: 'Highest', variable: '--overlay-highest', description: 'Nearly opaque' }
|
|
34
|
+
{ name: 'Low', variable: '--overlay-low', description: 'Light backdrop / pressed wash' },
|
|
35
|
+
{ name: 'Overlay', variable: '--overlay', description: 'Standard backdrop' },
|
|
36
|
+
{ name: 'High', variable: '--overlay-high', description: 'Strong backdrop' }
|
|
41
37
|
]
|
|
42
38
|
},
|
|
43
39
|
{
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
unlinkComponentProperty,
|
|
19
19
|
relinkComponentProperty,
|
|
20
20
|
} from '../core/store/editorStore';
|
|
21
|
-
import
|
|
21
|
+
import UIRelinkConfirmDialog from './UIRelinkConfirmDialog.svelte';
|
|
22
22
|
import UILinkToggle from './UILinkToggle.svelte';
|
|
23
23
|
|
|
24
24
|
interface Props {
|
|
@@ -383,7 +383,7 @@
|
|
|
383
383
|
<div class="link-toggle-wrap">
|
|
384
384
|
<UILinkToggle linked={isLinkedParent} ontoggle={toggleLinkPaddingGroup} />
|
|
385
385
|
{#if relinkOpen && component}
|
|
386
|
-
<
|
|
386
|
+
<UIRelinkConfirmDialog
|
|
387
387
|
candidates={relinkCandidates}
|
|
388
388
|
initialVariable={variable}
|
|
389
389
|
prefixToStrip={`--${component}-`}
|