@motion-proto/live-tokens 0.84.0 → 0.85.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/.claude/skills/live-tokens-create-component/references/token-naming.md +0 -1
- package/CHANGELOG.md +41 -0
- package/dist-plugin/{chunk-V2OVSC5Z.js → chunk-GWMEYBZ2.js} +22 -1
- package/dist-plugin/{chunk-FD4FVDOC.js → chunk-ONYAIXCZ.js} +74 -1
- package/dist-plugin/{chunk-M6BWSS6C.js → chunk-W5VQV32K.js} +1 -1
- package/dist-plugin/index.cjs +101 -7
- package/dist-plugin/index.js +3 -3
- package/dist-plugin/migrateData/index.cjs +78 -5
- package/dist-plugin/migrateData/index.js +2 -2
- package/dist-plugin/setColors/index.cjs +74 -1
- package/dist-plugin/setColors/index.js +1 -1
- package/dist-plugin/setGeometry/index.cjs +74 -4
- package/dist-plugin/setGeometry/index.d.cts +1 -1
- package/dist-plugin/setGeometry/index.d.ts +1 -1
- package/dist-plugin/setGeometry/index.js +1 -4
- package/dist-plugin/tokensCssMigrations/index.cjs +22 -1
- package/dist-plugin/tokensCssMigrations/index.js +1 -1
- package/package.json +1 -1
- package/src/editor/component-editor/DialogEditor.svelte +1 -2
- package/src/editor/component-editor/ImageLightboxEditor.svelte +1 -2
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +1 -4
- package/src/editor/core/components/aliasKinds.ts +0 -4
- package/src/editor/core/sketch/sketchLayer.ts +1 -1
- package/src/editor/core/store/editorPersistence.ts +46 -23
- package/src/editor/core/store/editorStore.ts +1 -5
- package/src/editor/core/store/editorTypes.ts +11 -7
- package/src/editor/core/themes/migrations/2026-09-21-scrim-surface.ts +56 -0
- package/src/editor/core/themes/migrations/2026-09-21-wash-color-and-opacity.ts +51 -0
- package/src/editor/core/themes/migrations/index.ts +4 -0
- package/src/editor/core/themes/slices/washes.ts +59 -77
- package/src/editor/skill-atlas/skillSources.generated.ts +1 -1
- package/src/editor/ui/UIPaletteSelector.svelte +5 -1
- package/src/editor/ui/UIVariantSelector.svelte +1 -1
- package/src/editor/ui/sections/WashesSection.svelte +100 -99
- package/src/editor/ui/variantScales.ts +0 -11
- package/src/live-tokens/data/colors-and-type/autumn.json +9 -7
- package/src/live-tokens/data/colors-and-type/default.json +9 -7
- package/src/live-tokens/data/colors-and-type/halloween.json +9 -7
- package/src/live-tokens/data/colors-and-type/midnight-study.json +9 -7
- package/src/live-tokens/data/colors-and-type/ocean.json +9 -7
- package/src/live-tokens/data/colors-and-type/royal-velvet.json +9 -7
- package/src/live-tokens/data/colors-and-type/sketchy.json +9 -7
- package/src/live-tokens/data/colors-and-type/spring-meadow.json +9 -7
- package/src/live-tokens/data/colors-and-type/sunset.json +9 -7
- package/src/live-tokens/data/themes/autumn.json +12 -12
- package/src/live-tokens/data/themes/halloween.json +12 -12
- package/src/live-tokens/data/themes/midnight-study.json +12 -12
- package/src/live-tokens/data/themes/ocean.json +12 -12
- package/src/live-tokens/data/themes/royal-velvet.json +12 -12
- package/src/live-tokens/data/themes/sketchy.json +12 -12
- package/src/live-tokens/data/themes/spring-meadow.json +12 -12
- package/src/live-tokens/data/themes/sunset.json +12 -12
- package/src/live-tokens/data/tokens.generated.css +8 -6
- package/src/system/components/Dialog.svelte +2 -7
- package/src/system/components/ImageLightbox.svelte +7 -18
- package/src/system/styles/tokens.css +13 -8
- package/src/testing-js/{chunk-WQJ6QB6Y.js → chunk-RDHCBQ6I.js} +4 -8
- package/src/testing-js/chunk-RDHCBQ6I.js.map +1 -0
- package/src/testing-js/{chunk-XXABJNUU.js → chunk-XV5CYADO.js} +2 -2
- package/src/testing-js/component-behavior.contract.js +1 -1
- package/src/testing-js/component-editor.contract.js +1 -1
- package/src/testing-js/component-render.contract.js +1 -1
- package/src/testing-js/index.js +2 -2
- package/src/testing-js/page-compliance.contract.js +1 -1
- package/src/testing-js/vitest.js +2 -2
- package/src/testing-js/chunk-WQJ6QB6Y.js.map +0 -1
- /package/src/testing-js/{chunk-XXABJNUU.js.map → chunk-XV5CYADO.js.map} +0 -0
|
@@ -74,9 +74,6 @@ var KIND_RULES = [
|
|
|
74
74
|
{ kind: "dot-size", suffix: ["-dot-size"] },
|
|
75
75
|
{ kind: "blur", suffix: ["-blur"], prefix: ["--blur-"] },
|
|
76
76
|
{ kind: "scale", suffix: ["-scale"], prefix: ["--scale-"] },
|
|
77
|
-
// A strength from 0 to 1 that the component mixes into a colour where it
|
|
78
|
-
// paints. A palette picker here writes a colour into a number.
|
|
79
|
-
{ kind: "opacity", suffix: ["-opacity"], prefix: ["--scrim-opacity"] },
|
|
80
77
|
{ kind: "shadow", suffix: ["-shadow"], prefix: ["--shadow-"] },
|
|
81
78
|
{ kind: "padding", suffix: ["-padding", "-margin"] },
|
|
82
79
|
{ kind: "gap", suffix: ["-gap"] },
|
|
@@ -1569,6 +1566,77 @@ var componentMigration_2026_09_20_scrimColorAndOpacity = {
|
|
|
1569
1566
|
}
|
|
1570
1567
|
};
|
|
1571
1568
|
|
|
1569
|
+
// src/editor/core/themes/migrations/2026-09-21-wash-color-and-opacity.ts
|
|
1570
|
+
var FAMILIES = ["scrim", "tint"];
|
|
1571
|
+
var COLOR_MIX_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+(?:\.\d+)?)%,\s*transparent\)$/i;
|
|
1572
|
+
var PLAIN_VAR_RE = /^var\((--[a-z0-9-]+)\)$/i;
|
|
1573
|
+
function parseFill(raw) {
|
|
1574
|
+
const s = raw.trim();
|
|
1575
|
+
const mix = s.match(COLOR_MIX_RE);
|
|
1576
|
+
if (mix) return { color: mix[1], opacity: Math.min(100, Number(mix[2])) / 100 };
|
|
1577
|
+
const plain = s.match(PLAIN_VAR_RE);
|
|
1578
|
+
return plain ? { color: plain[1], opacity: 1 } : null;
|
|
1579
|
+
}
|
|
1580
|
+
var colorsAndTypeMigration_2026_09_21_washColorAndOpacity = {
|
|
1581
|
+
id: "2026-09-21-wash-color-and-opacity",
|
|
1582
|
+
fromVersion: 8,
|
|
1583
|
+
toVersion: 9,
|
|
1584
|
+
appliesTo: "colors-and-type",
|
|
1585
|
+
apply(rawVars) {
|
|
1586
|
+
const out = { ...rawVars };
|
|
1587
|
+
for (const family of FAMILIES) {
|
|
1588
|
+
let color = null;
|
|
1589
|
+
for (const suffix of ["", "-low", "-high"]) {
|
|
1590
|
+
const key = `--${family}${suffix}`;
|
|
1591
|
+
const parsed = out[key] === void 0 ? null : parseFill(out[key]);
|
|
1592
|
+
if (!parsed) continue;
|
|
1593
|
+
color ??= parsed.color;
|
|
1594
|
+
out[`--${family}-opacity${suffix}`] = String(parsed.opacity);
|
|
1595
|
+
delete out[key];
|
|
1596
|
+
}
|
|
1597
|
+
if (color) out[`--${family}-color`] = `var(${color})`;
|
|
1598
|
+
}
|
|
1599
|
+
return out;
|
|
1600
|
+
}
|
|
1601
|
+
};
|
|
1602
|
+
|
|
1603
|
+
// src/editor/core/themes/migrations/2026-09-21-scrim-surface.ts
|
|
1604
|
+
var STOPS2 = {
|
|
1605
|
+
"--scrim-opacity-low": "--scrim-low",
|
|
1606
|
+
"--scrim-opacity": "--scrim",
|
|
1607
|
+
"--scrim-opacity-high": "--scrim-high"
|
|
1608
|
+
};
|
|
1609
|
+
var PAIR = /^--(imagelightbox|dialog)-scrim-(color|opacity)$/;
|
|
1610
|
+
var ref = (value) => value.startsWith("--") ? `var(${value})` : value;
|
|
1611
|
+
function fill(color, opacity) {
|
|
1612
|
+
if (color === "--scrim-color" && STOPS2[opacity]) return STOPS2[opacity];
|
|
1613
|
+
if (opacity === "1") return color;
|
|
1614
|
+
const pct = Number.isFinite(Number(opacity)) ? `${Number(opacity) * 100}%` : `calc(${ref(opacity)} * 100%)`;
|
|
1615
|
+
return `color-mix(in srgb, ${ref(color)} ${pct}, transparent)`;
|
|
1616
|
+
}
|
|
1617
|
+
var componentMigration_2026_09_21_scrimSurface = {
|
|
1618
|
+
id: "2026-09-21-scrim-surface",
|
|
1619
|
+
fromVersion: 37,
|
|
1620
|
+
toVersion: 38,
|
|
1621
|
+
appliesTo: "component-config",
|
|
1622
|
+
apply(rawVars) {
|
|
1623
|
+
const out = {};
|
|
1624
|
+
const pairs = {};
|
|
1625
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
1626
|
+
const match = PAIR.exec(key);
|
|
1627
|
+
if (!match) {
|
|
1628
|
+
out[key] = value;
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
(pairs[match[1]] ??= {})[match[2]] = value;
|
|
1632
|
+
}
|
|
1633
|
+
for (const [id, { color = "--scrim-color", opacity = "--scrim-opacity-high" }] of Object.entries(pairs)) {
|
|
1634
|
+
out[`--${id}-scrim-surface`] = fill(color, opacity);
|
|
1635
|
+
}
|
|
1636
|
+
return out;
|
|
1637
|
+
}
|
|
1638
|
+
};
|
|
1639
|
+
|
|
1572
1640
|
// src/editor/core/themes/migrations/2026-09-13-cornerbadge-prefix.ts
|
|
1573
1641
|
var RENAMES8 = {
|
|
1574
1642
|
cornerbadge: {
|
|
@@ -1765,7 +1833,9 @@ var MIGRATIONS = [
|
|
|
1765
1833
|
componentMigration_2026_09_13_collapsiblesectionOpen,
|
|
1766
1834
|
componentMigration_2026_09_13_badgeBrand,
|
|
1767
1835
|
componentMigration_2026_09_20_imagelightboxScrim,
|
|
1768
|
-
componentMigration_2026_09_20_scrimColorAndOpacity
|
|
1836
|
+
componentMigration_2026_09_20_scrimColorAndOpacity,
|
|
1837
|
+
colorsAndTypeMigration_2026_09_21_washColorAndOpacity,
|
|
1838
|
+
componentMigration_2026_09_21_scrimSurface
|
|
1769
1839
|
];
|
|
1770
1840
|
function countFor(kind) {
|
|
1771
1841
|
return MIGRATIONS.filter((m) => m.appliesTo === kind).length;
|
|
@@ -37,7 +37,7 @@ interface AdjustResult {
|
|
|
37
37
|
}
|
|
38
38
|
declare function adjustAliases(configs: Record<string, ComponentConfig>, ops: readonly AdjustOp[], now: string): AdjustResult;
|
|
39
39
|
|
|
40
|
-
type TokenKind = 'surface' | 'border' | 'border-width' | 'hairline-width' | 'indicator-width' | 'radius' | 'track-height' | 'hairline-inset' | 'dot-size' | 'length' | 'blur' | 'scale' | '
|
|
40
|
+
type TokenKind = 'surface' | 'border' | 'border-width' | 'hairline-width' | 'indicator-width' | 'radius' | 'track-height' | 'hairline-inset' | 'dot-size' | 'length' | 'blur' | 'scale' | 'shadow' | 'font-family' | 'font-weight' | 'font-size' | 'line-height' | 'letter-spacing' | 'padding' | 'gap' | 'duration' | 'easing' | 'text-color';
|
|
41
41
|
/** Side-aware kind test. Per-side paddings (`--card-default-body-padding-top`)
|
|
42
42
|
are written by UIPaddingSelector and never declared as editor tokens, so
|
|
43
43
|
`rawKind` never meets one; the CLI does, and they belong with their parent. */
|
|
@@ -37,7 +37,7 @@ interface AdjustResult {
|
|
|
37
37
|
}
|
|
38
38
|
declare function adjustAliases(configs: Record<string, ComponentConfig>, ops: readonly AdjustOp[], now: string): AdjustResult;
|
|
39
39
|
|
|
40
|
-
type TokenKind = 'surface' | 'border' | 'border-width' | 'hairline-width' | 'indicator-width' | 'radius' | 'track-height' | 'hairline-inset' | 'dot-size' | 'length' | 'blur' | 'scale' | '
|
|
40
|
+
type TokenKind = 'surface' | 'border' | 'border-width' | 'hairline-width' | 'indicator-width' | 'radius' | 'track-height' | 'hairline-inset' | 'dot-size' | 'length' | 'blur' | 'scale' | 'shadow' | 'font-family' | 'font-weight' | 'font-size' | 'line-height' | 'letter-spacing' | 'padding' | 'gap' | 'duration' | 'easing' | 'text-color';
|
|
41
41
|
/** Side-aware kind test. Per-side paddings (`--card-default-body-padding-top`)
|
|
42
42
|
are written by UIPaddingSelector and never declared as editor tokens, so
|
|
43
43
|
`rawKind` never meets one; the CLI does, and they belong with their parent. */
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CURRENT_COMPONENT_SCHEMA_VERSION
|
|
3
|
-
} from "../chunk-
|
|
3
|
+
} from "../chunk-ONYAIXCZ.js";
|
|
4
4
|
import {
|
|
5
5
|
readLiveTokensConfig,
|
|
6
6
|
resolveDataDirs
|
|
@@ -42,9 +42,6 @@ var KIND_RULES = [
|
|
|
42
42
|
{ kind: "dot-size", suffix: ["-dot-size"] },
|
|
43
43
|
{ kind: "blur", suffix: ["-blur"], prefix: ["--blur-"] },
|
|
44
44
|
{ kind: "scale", suffix: ["-scale"], prefix: ["--scale-"] },
|
|
45
|
-
// A strength from 0 to 1 that the component mixes into a colour where it
|
|
46
|
-
// paints. A palette picker here writes a colour into a number.
|
|
47
|
-
{ kind: "opacity", suffix: ["-opacity"], prefix: ["--scrim-opacity"] },
|
|
48
45
|
{ kind: "shadow", suffix: ["-shadow"], prefix: ["--shadow-"] },
|
|
49
46
|
{ kind: "padding", suffix: ["-padding", "-margin"] },
|
|
50
47
|
{ kind: "gap", suffix: ["-gap"] },
|
|
@@ -659,6 +659,26 @@ var tokensCssMigration_2026_09_20_scrimColorAndOpacity = {
|
|
|
659
659
|
}
|
|
660
660
|
};
|
|
661
661
|
|
|
662
|
+
// vite-plugin/tokensCssMigrations/migrations/2026-09-21-tint-color-and-opacity.ts
|
|
663
|
+
var ENTRIES3 = [
|
|
664
|
+
{ name: "--tint-color", value: "rgb(255, 255, 255)" },
|
|
665
|
+
{ name: "--tint-opacity-low", value: "0.05" },
|
|
666
|
+
{ name: "--tint-opacity", value: "0.1" },
|
|
667
|
+
{ name: "--tint-opacity-high", value: "0.15" }
|
|
668
|
+
];
|
|
669
|
+
var tokensCssMigration_2026_09_21_tintColorAndOpacity = {
|
|
670
|
+
id: "2026-09-21-tint-color-and-opacity",
|
|
671
|
+
kind: "additive",
|
|
672
|
+
description: "Add --tint-color and --tint-opacity-*; a tint's colour and strength are separate tokens",
|
|
673
|
+
apply(css) {
|
|
674
|
+
return ensureScale(css, {
|
|
675
|
+
entries: ENTRIES3,
|
|
676
|
+
sectionComment: "Tint colour and strength, which the --tint-* stops compose",
|
|
677
|
+
anchorPrefixes: ["--tint"]
|
|
678
|
+
});
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
|
|
662
682
|
// vite-plugin/files/dataPaths.ts
|
|
663
683
|
var import_fs = __toESM(require("fs"), 1);
|
|
664
684
|
var import_path = __toESM(require("path"), 1);
|
|
@@ -712,7 +732,8 @@ var TOKENS_CSS_MIGRATIONS = [
|
|
|
712
732
|
tokensCssMigration_2026_08_27_editorialLargeSteps,
|
|
713
733
|
tokensCssMigration_2026_09_01_scrimRename,
|
|
714
734
|
tokensCssMigration_2026_09_01_tintScale,
|
|
715
|
-
tokensCssMigration_2026_09_20_scrimColorAndOpacity
|
|
735
|
+
tokensCssMigration_2026_09_20_scrimColorAndOpacity,
|
|
736
|
+
tokensCssMigration_2026_09_21_tintColorAndOpacity
|
|
716
737
|
];
|
|
717
738
|
function runTokensCssMigrations(css) {
|
|
718
739
|
return foldMigrations(css, () => true);
|
package/package.json
CHANGED
|
@@ -16,8 +16,7 @@
|
|
|
16
16
|
// Button styling lives in Button.svelte — the dialog only owns its own chrome.
|
|
17
17
|
const frameStates: Record<string, Token[]> = {
|
|
18
18
|
scrim: [
|
|
19
|
-
{ label: 'scrim color', groupKey: 'surface', variable: '--dialog-scrim-
|
|
20
|
-
{ label: 'scrim opacity', variable: '--dialog-scrim-opacity' },
|
|
19
|
+
{ label: 'scrim color', groupKey: 'surface', variable: '--dialog-scrim-surface' },
|
|
21
20
|
],
|
|
22
21
|
dialog: [
|
|
23
22
|
{ label: 'surface color', variable: '--dialog-surface' },
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
{ label: 'tile shadow', variable: '--imagelightbox-tile-shadow' },
|
|
16
16
|
],
|
|
17
17
|
overlay: [
|
|
18
|
-
{ label: 'scrim color', groupKey: 'surface', variable: '--imagelightbox-scrim-
|
|
19
|
-
{ label: 'scrim opacity', variable: '--imagelightbox-scrim-opacity' },
|
|
18
|
+
{ label: 'scrim color', groupKey: 'surface', variable: '--imagelightbox-scrim-surface' },
|
|
20
19
|
],
|
|
21
20
|
chrome: [
|
|
22
21
|
{ label: 'surface color', groupKey: 'surface', variable: '--imagelightbox-chrome-surface' },
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
import UIPaddingSelector from '../../ui/UIPaddingSelector.svelte';
|
|
10
10
|
import UILetterSpacingSelector from '../../ui/UILetterSpacingSelector.svelte';
|
|
11
11
|
import UIEasingSelector from '../../ui/UIEasingSelector.svelte';
|
|
12
|
-
import { BLUR, BORDER_WIDTH, DOT_SIZE, DURATION, RADIUS, SCALE,
|
|
12
|
+
import { BLUR, BORDER_WIDTH, DOT_SIZE, DURATION, RADIUS, SCALE, SHADOW, TRACK_HEIGHT, HAIRLINE_INSET } from '../../ui/variantScales';
|
|
13
13
|
import {
|
|
14
14
|
editorState,
|
|
15
15
|
getComponentPropertySiblings,
|
|
@@ -80,7 +80,6 @@
|
|
|
80
80
|
'easing',
|
|
81
81
|
'blur',
|
|
82
82
|
'scale',
|
|
83
|
-
'opacity',
|
|
84
83
|
'shadow',
|
|
85
84
|
'text-color',
|
|
86
85
|
'surface',
|
|
@@ -161,7 +160,6 @@
|
|
|
161
160
|
'easing': { component: UIEasingSelector },
|
|
162
161
|
'blur': { component: UIVariantSelector, extra: () => ({ ...BLUR }) },
|
|
163
162
|
'scale': { component: UIVariantSelector, extra: () => ({ ...SCALE }) },
|
|
164
|
-
'opacity': { component: UIVariantSelector, extra: () => ({ ...SCRIM_OPACITY }) },
|
|
165
163
|
'shadow': { component: UIVariantSelector, extra: () => ({ ...SHADOW }) },
|
|
166
164
|
'surface': { component: UIPaletteSelector, extra: paletteExtra },
|
|
167
165
|
'border': { component: UIPaletteSelector, extra: paletteExtra },
|
|
@@ -192,7 +190,6 @@
|
|
|
192
190
|
'easing',
|
|
193
191
|
'blur',
|
|
194
192
|
'scale',
|
|
195
|
-
'opacity',
|
|
196
193
|
'shadow',
|
|
197
194
|
'surface',
|
|
198
195
|
'border-width',
|
|
@@ -11,7 +11,6 @@ export type TokenKind =
|
|
|
11
11
|
| 'length'
|
|
12
12
|
| 'blur'
|
|
13
13
|
| 'scale'
|
|
14
|
-
| 'opacity'
|
|
15
14
|
| 'shadow'
|
|
16
15
|
| 'font-family'
|
|
17
16
|
| 'font-weight'
|
|
@@ -60,9 +59,6 @@ export const KIND_RULES: ReadonlyArray<{
|
|
|
60
59
|
{ kind: 'dot-size', suffix: ['-dot-size'] },
|
|
61
60
|
{ kind: 'blur', suffix: ['-blur'], prefix: ['--blur-'] },
|
|
62
61
|
{ kind: 'scale', suffix: ['-scale'], prefix: ['--scale-'] },
|
|
63
|
-
// A strength from 0 to 1 that the component mixes into a colour where it
|
|
64
|
-
// paints. A palette picker here writes a colour into a number.
|
|
65
|
-
{ kind: 'opacity', suffix: ['-opacity'], prefix: ['--scrim-opacity'] },
|
|
66
62
|
{ kind: 'shadow', suffix: ['-shadow'], prefix: ['--shadow-'] },
|
|
67
63
|
{ kind: 'padding', suffix: ['-padding', '-margin'] },
|
|
68
64
|
{ kind: 'gap', suffix: ['-gap'] },
|
|
@@ -186,7 +186,7 @@ const PART_SPECS: readonly PartSpec[] = [
|
|
|
186
186
|
{ sel: '.image-lightbox-thumb', stem: 'imagelightbox-tile', positioned: true, clips: true },
|
|
187
187
|
// A fixed full-viewport scrim behind the open modal; no border of its own.
|
|
188
188
|
{
|
|
189
|
-
sel: '.image-lightbox-overlay', fill: '
|
|
189
|
+
sel: '.image-lightbox-overlay', fill: 'var(--imagelightbox-scrim-surface)',
|
|
190
190
|
stroke: 'transparent', positioned: true, unmasked: true,
|
|
191
191
|
},
|
|
192
192
|
// Close and the two nav chevrons all carry this class; each is fixed and
|
|
@@ -14,13 +14,13 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { get } from 'svelte/store';
|
|
17
|
-
import type { EditorState } from './editorTypes';
|
|
17
|
+
import type { EditorState, WashScale } from './editorTypes';
|
|
18
18
|
import { storageKey } from './editorConfig';
|
|
19
19
|
import { store } from './editorCore';
|
|
20
20
|
import { quietGet, quietSet } from '../storage/storage';
|
|
21
21
|
import { isGradientSlot, makeDefaultGradients } from '../themes/slices/gradients';
|
|
22
22
|
import { seedShadowsFromDom } from '../themes/slices/shadows';
|
|
23
|
-
import {
|
|
23
|
+
import { makeDefaultWashScale, type WashFamily } from '../themes/slices/washes';
|
|
24
24
|
import { sanitizeHarmonyAxes } from '../palettes/colorHarmony';
|
|
25
25
|
import {
|
|
26
26
|
renameBackgroundPaletteKey,
|
|
@@ -78,34 +78,57 @@ function migrateGradients(state: EditorState): EditorState {
|
|
|
78
78
|
// calls `Object.entries(slice.config)` unconditionally, so backfill the
|
|
79
79
|
// required fields and drop any non-object slice before the state reaches the
|
|
80
80
|
// renderer. Spread preserves optional fields like `unlinked`.
|
|
81
|
-
//
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
// `
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
'--overlay-
|
|
90
|
-
'--
|
|
91
|
-
'--
|
|
92
|
-
'--
|
|
81
|
+
// A persisted washes slice can predate two reshapes. Before the scrim and tint
|
|
82
|
+
// renames it was `overlays: { tokens, hoverTokens }`; until 0.85.0 each family
|
|
83
|
+
// was a list of `{variable, alias, opacity}` stops, each carrying its own
|
|
84
|
+
// colour. `hydrate` shallow-merges, so an older build's `washes` arrives whole
|
|
85
|
+
// in that shape, and `washesToVars` reads `color` and `stops` unconditionally.
|
|
86
|
+
// Reshape here: the family takes the base stop's colour, and each stop its
|
|
87
|
+
// opacity under the new variable.
|
|
88
|
+
const LEGACY_STOP_OPACITY_VARS: Record<string, string> = {
|
|
89
|
+
'--overlay-low': '--scrim-opacity-low',
|
|
90
|
+
'--overlay': '--scrim-opacity',
|
|
91
|
+
'--overlay-high': '--scrim-opacity-high',
|
|
92
|
+
'--scrim-low': '--scrim-opacity-low',
|
|
93
|
+
'--scrim': '--scrim-opacity',
|
|
94
|
+
'--scrim-high': '--scrim-opacity-high',
|
|
95
|
+
'--hover-low': '--tint-opacity-low',
|
|
96
|
+
'--hover': '--tint-opacity',
|
|
97
|
+
'--hover-high': '--tint-opacity-high',
|
|
98
|
+
'--tint-low': '--tint-opacity-low',
|
|
99
|
+
'--tint': '--tint-opacity',
|
|
100
|
+
'--tint-high': '--tint-opacity-high',
|
|
93
101
|
};
|
|
94
102
|
|
|
95
103
|
type LegacyWashes = {
|
|
104
|
+
scrim?: unknown;
|
|
105
|
+
tint?: unknown;
|
|
96
106
|
scrims?: unknown;
|
|
97
107
|
tints?: unknown;
|
|
98
108
|
hoverTokens?: unknown;
|
|
99
109
|
tokens?: unknown;
|
|
100
110
|
};
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
.
|
|
108
|
-
|
|
112
|
+
type LegacyStop = { variable: string; alias?: unknown; opacity?: unknown };
|
|
113
|
+
|
|
114
|
+
function isScale(raw: unknown): raw is WashScale {
|
|
115
|
+
return !!raw && typeof raw === 'object'
|
|
116
|
+
&& typeof (raw as WashScale).color === 'string'
|
|
117
|
+
&& Array.isArray((raw as WashScale).stops);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function washScale(current: unknown, legacyList: unknown, family: WashFamily): WashScale {
|
|
121
|
+
if (isScale(current)) return current;
|
|
122
|
+
const scale = makeDefaultWashScale(family);
|
|
123
|
+
if (!Array.isArray(legacyList)) return scale;
|
|
124
|
+
const stops = legacyList.filter((t): t is LegacyStop => !!t && typeof t === 'object' && 'variable' in t);
|
|
125
|
+
const base = stops.find((t) => LEGACY_STOP_OPACITY_VARS[t.variable] === `--${family}-opacity`) ?? stops[0];
|
|
126
|
+
if (typeof base?.alias === 'string') scale.color = base.alias;
|
|
127
|
+
for (const t of stops) {
|
|
128
|
+
const stop = scale.stops.find((s) => s.variable === LEGACY_STOP_OPACITY_VARS[t.variable]);
|
|
129
|
+
if (stop && typeof t.opacity === 'number') stop.opacity = t.opacity;
|
|
130
|
+
}
|
|
131
|
+
return scale;
|
|
109
132
|
}
|
|
110
133
|
|
|
111
134
|
export function normalizeWashes(state: EditorState): EditorState {
|
|
@@ -113,8 +136,8 @@ export function normalizeWashes(state: EditorState): EditorState {
|
|
|
113
136
|
const legacy = ((state as unknown as { overlays?: LegacyWashes }).overlays ?? {}) as LegacyWashes;
|
|
114
137
|
const next = { ...state } as EditorState & { overlays?: unknown };
|
|
115
138
|
next.washes = {
|
|
116
|
-
|
|
117
|
-
|
|
139
|
+
scrim: washScale(washes.scrim, washes.scrims ?? legacy.tokens, 'scrim'),
|
|
140
|
+
tint: washScale(washes.tint, washes.tints ?? washes.hoverTokens ?? legacy.hoverTokens, 'tint'),
|
|
118
141
|
};
|
|
119
142
|
delete next.overlays;
|
|
120
143
|
return next;
|
|
@@ -152,12 +152,8 @@ export {
|
|
|
152
152
|
export {
|
|
153
153
|
washesToVars,
|
|
154
154
|
WASH_VAR_NAMES,
|
|
155
|
-
applyWashVarsToState,
|
|
156
155
|
makeDefaultWashesState,
|
|
157
|
-
|
|
158
|
-
makeDefaultTintTokens,
|
|
159
|
-
washTokenToCss,
|
|
160
|
-
parseWashCss,
|
|
156
|
+
makeDefaultWashScale,
|
|
161
157
|
} from '../themes/slices/washes';
|
|
162
158
|
|
|
163
159
|
export {
|
|
@@ -23,16 +23,20 @@ export interface ShadowOverrideFlags {
|
|
|
23
23
|
distance: boolean; blur: boolean; size: boolean;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/**
|
|
27
|
-
|
|
28
|
-
* is behind them; tints shade the surface they sit on. */
|
|
29
|
-
export interface WashToken {
|
|
26
|
+
/** One opacity stop of a wash family, emitted as a bare number. */
|
|
27
|
+
export interface WashStop {
|
|
30
28
|
variable: string;
|
|
31
29
|
label: string;
|
|
32
|
-
alias: string;
|
|
33
30
|
opacity: number;
|
|
34
31
|
}
|
|
35
32
|
|
|
33
|
+
/** A wash family: one aliased colour and its opacity stops. Scrims dim what is
|
|
34
|
+
* behind them; tints shade the surface they sit on. */
|
|
35
|
+
export interface WashScale {
|
|
36
|
+
color: string;
|
|
37
|
+
stops: WashStop[];
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
export interface ColumnsState {
|
|
37
41
|
count: number;
|
|
38
42
|
maxWidth: number;
|
|
@@ -113,8 +117,8 @@ export interface EditorState {
|
|
|
113
117
|
overrides: Record<string, ShadowOverrideFlags>;
|
|
114
118
|
};
|
|
115
119
|
washes: {
|
|
116
|
-
|
|
117
|
-
|
|
120
|
+
scrim: WashScale;
|
|
121
|
+
tint: WashScale;
|
|
118
122
|
};
|
|
119
123
|
columns: ColumnsState;
|
|
120
124
|
components: Record<string, ComponentSlice>;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { Migration } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A screen's scrim is one fill again (2026-09-21).
|
|
5
|
+
*
|
|
6
|
+
* From 0.84.0 Dialog and ImageLightbox read `-scrim-color` and
|
|
7
|
+
* `-scrim-opacity` and mixed them where they painted. The theme now composes
|
|
8
|
+
* `--scrim-low`, `--scrim`, and `--scrim-high` from its own colour and
|
|
9
|
+
* strengths, so a screen picks one of those stops as `-scrim-surface`, the way
|
|
10
|
+
* a hover picks a `--tint-*` stop.
|
|
11
|
+
*
|
|
12
|
+
* The scale's colour at one of its strengths becomes that stop. Any other pair
|
|
13
|
+
* becomes the `color-mix()` the screen used to paint, so the page looks the
|
|
14
|
+
* same.
|
|
15
|
+
*/
|
|
16
|
+
const STOPS: Record<string, string> = {
|
|
17
|
+
'--scrim-opacity-low': '--scrim-low',
|
|
18
|
+
'--scrim-opacity': '--scrim',
|
|
19
|
+
'--scrim-opacity-high': '--scrim-high',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const PAIR = /^--(imagelightbox|dialog)-scrim-(color|opacity)$/;
|
|
23
|
+
|
|
24
|
+
const ref = (value: string) => (value.startsWith('--') ? `var(${value})` : value);
|
|
25
|
+
|
|
26
|
+
function fill(color: string, opacity: string): string {
|
|
27
|
+
if (color === '--scrim-color' && STOPS[opacity]) return STOPS[opacity];
|
|
28
|
+
if (opacity === '1') return color;
|
|
29
|
+
const pct = Number.isFinite(Number(opacity))
|
|
30
|
+
? `${Number(opacity) * 100}%`
|
|
31
|
+
: `calc(${ref(opacity)} * 100%)`;
|
|
32
|
+
return `color-mix(in srgb, ${ref(color)} ${pct}, transparent)`;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const componentMigration_2026_09_21_scrimSurface: Migration = {
|
|
36
|
+
id: '2026-09-21-scrim-surface',
|
|
37
|
+
fromVersion: 37,
|
|
38
|
+
toVersion: 38,
|
|
39
|
+
appliesTo: 'component-config',
|
|
40
|
+
apply(rawVars) {
|
|
41
|
+
const out: Record<string, string> = {};
|
|
42
|
+
const pairs: Record<string, { color?: string; opacity?: string }> = {};
|
|
43
|
+
for (const [key, value] of Object.entries(rawVars)) {
|
|
44
|
+
const match = PAIR.exec(key);
|
|
45
|
+
if (!match) {
|
|
46
|
+
out[key] = value;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
(pairs[match[1]] ??= {})[match[2] as 'color' | 'opacity'] = value;
|
|
50
|
+
}
|
|
51
|
+
for (const [id, { color = '--scrim-color', opacity = '--scrim-opacity-high' }] of Object.entries(pairs)) {
|
|
52
|
+
out[`--${id}-scrim-surface`] = fill(color, opacity);
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { Migration } from './index';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A wash family's colour and its strengths, apart (2026-09-21).
|
|
5
|
+
*
|
|
6
|
+
* A theme stored each `--scrim-*` and `--tint-*` stop as a composed fill,
|
|
7
|
+
* `color-mix(in srgb, var(<colour>) N%, transparent)`, so the colour was
|
|
8
|
+
* restated three times and the fill overrode the one tokens.css composes from
|
|
9
|
+
* `--scrim-color` and `--scrim-opacity-*`. That left Dialog and ImageLightbox,
|
|
10
|
+
* which read the pair, deaf to every theme's scrim.
|
|
11
|
+
*
|
|
12
|
+
* Each family now stores `--<family>-color` and `--<family>-opacity-*`. The
|
|
13
|
+
* base stop gives the colour, falling back to the first stop that parses. A
|
|
14
|
+
* stop that does not parse is a hand-written fill: it stays, so the page keeps
|
|
15
|
+
* painting what the theme's author chose.
|
|
16
|
+
*/
|
|
17
|
+
const FAMILIES = ['scrim', 'tint'] as const;
|
|
18
|
+
|
|
19
|
+
const COLOR_MIX_RE = /^color-mix\(in srgb,\s*var\((--[a-z0-9-]+)\)\s+(\d+(?:\.\d+)?)%,\s*transparent\)$/i;
|
|
20
|
+
const PLAIN_VAR_RE = /^var\((--[a-z0-9-]+)\)$/i;
|
|
21
|
+
|
|
22
|
+
function parseFill(raw: string): { color: string; opacity: number } | null {
|
|
23
|
+
const s = raw.trim();
|
|
24
|
+
const mix = s.match(COLOR_MIX_RE);
|
|
25
|
+
if (mix) return { color: mix[1], opacity: Math.min(100, Number(mix[2])) / 100 };
|
|
26
|
+
const plain = s.match(PLAIN_VAR_RE);
|
|
27
|
+
return plain ? { color: plain[1], opacity: 1 } : null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const colorsAndTypeMigration_2026_09_21_washColorAndOpacity: Migration = {
|
|
31
|
+
id: '2026-09-21-wash-color-and-opacity',
|
|
32
|
+
fromVersion: 8,
|
|
33
|
+
toVersion: 9,
|
|
34
|
+
appliesTo: 'colors-and-type',
|
|
35
|
+
apply(rawVars) {
|
|
36
|
+
const out = { ...rawVars };
|
|
37
|
+
for (const family of FAMILIES) {
|
|
38
|
+
let color: string | null = null;
|
|
39
|
+
for (const suffix of ['', '-low', '-high']) {
|
|
40
|
+
const key = `--${family}${suffix}`;
|
|
41
|
+
const parsed = out[key] === undefined ? null : parseFill(out[key]);
|
|
42
|
+
if (!parsed) continue;
|
|
43
|
+
color ??= parsed.color;
|
|
44
|
+
out[`--${family}-opacity${suffix}`] = String(parsed.opacity);
|
|
45
|
+
delete out[key];
|
|
46
|
+
}
|
|
47
|
+
if (color) out[`--${family}-color`] = `var(${color})`;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -81,6 +81,8 @@ import { componentMigration_2026_09_13_indicator } from './2026-09-13-indicator'
|
|
|
81
81
|
import { componentMigration_2026_09_13_sectiondividerSurface } from './2026-09-13-sectiondivider-surface';
|
|
82
82
|
import { componentMigration_2026_09_20_imagelightboxScrim } from './2026-09-20-imagelightbox-scrim';
|
|
83
83
|
import { componentMigration_2026_09_20_scrimColorAndOpacity } from './2026-09-20-scrim-color-and-opacity';
|
|
84
|
+
import { colorsAndTypeMigration_2026_09_21_washColorAndOpacity } from './2026-09-21-wash-color-and-opacity';
|
|
85
|
+
import { componentMigration_2026_09_21_scrimSurface } from './2026-09-21-scrim-surface';
|
|
84
86
|
import { componentMigration_2026_09_13_cornerbadgePrefix } from './2026-09-13-cornerbadge-prefix';
|
|
85
87
|
import { componentMigration_2026_09_13_toggleLabel } from './2026-09-13-toggle-label';
|
|
86
88
|
import { componentMigration_2026_09_13_collapsiblesectionOpen } from './2026-09-13-collapsiblesection-open';
|
|
@@ -136,6 +138,8 @@ export const MIGRATIONS: Migration[] = [
|
|
|
136
138
|
componentMigration_2026_09_13_badgeBrand,
|
|
137
139
|
componentMigration_2026_09_20_imagelightboxScrim,
|
|
138
140
|
componentMigration_2026_09_20_scrimColorAndOpacity,
|
|
141
|
+
colorsAndTypeMigration_2026_09_21_washColorAndOpacity,
|
|
142
|
+
componentMigration_2026_09_21_scrimSurface,
|
|
139
143
|
];
|
|
140
144
|
|
|
141
145
|
function countFor(kind: 'colors-and-type' | 'component-config'): number {
|