@motion-proto/live-tokens 0.96.0 → 0.97.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +37 -0
- package/bin/set-colors.mjs +1 -0
- package/bin/setup-claude.mjs +2 -2
- package/dist-plugin/setColors/index.cjs +7 -1
- package/dist-plugin/setColors/index.d.cts +9 -4
- package/dist-plugin/setColors/index.d.ts +9 -4
- package/dist-plugin/setColors/index.js +7 -1
- package/dist-plugin/setGeometry/index.d.cts +1 -1
- package/dist-plugin/setGeometry/index.d.ts +1 -1
- package/dist-plugin/setType/index.d.cts +1 -1
- package/dist-plugin/setType/index.d.ts +1 -1
- package/dist-plugin/{themeTypes-B8_Idrp4.d.cts → themeTypes-BaTQUyac.d.cts} +10 -1
- package/dist-plugin/{themeTypes-B8_Idrp4.d.ts → themeTypes-BaTQUyac.d.ts} +10 -1
- package/package.json +4 -2
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +0 -7
- package/src/editor/core/store/editorPersistence.ts +9 -1
- package/src/editor/core/store/editorRenderer.ts +2 -0
- package/src/editor/core/store/editorStore.ts +25 -0
- package/src/editor/core/store/editorTypes.ts +9 -0
- package/src/editor/core/themes/buildColors.ts +16 -5
- package/src/editor/core/themes/slices/customColors.ts +226 -0
- package/src/editor/core/themes/themeTypes.ts +10 -0
- package/src/editor/docs/content/editing-tokens.md +28 -0
- package/src/editor/docs/content.generated.ts +1 -1
- package/src/editor/pages/EditorShell.svelte +1 -0
- package/src/editor/ui/CustomColorReplaceStep.svelte +118 -0
- package/src/editor/ui/UIPaletteSelector.svelte +338 -15
- package/src/editor/ui/UITokenSelector.svelte +7 -12
- package/src/editor/ui/VariablesTab.svelte +3 -0
- package/src/editor/ui/anchorToTrigger.ts +63 -0
- package/src/editor/ui/sections/CustomColorsSection.svelte +254 -0
- package/src/system/components/ImageLightbox.svelte +8 -1
- package/src/testing-js/page-compliance.contract.js +31 -5
- package/src/testing-js/page-compliance.contract.js.map +1 -1
- package/src/editor/ui/keepInViewport.ts +0 -84
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,42 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.97.0 — Custom colors
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **Every color picker offers custom colors.** A **Custom** row after Danger
|
|
8
|
+
lists the open theme's own colors, `--color-custom-1`, `--color-custom-2`
|
|
9
|
+
and so on, each holding one OKLCH value for light and dark. The row's
|
|
10
|
+
plus swatch starts a new color from the slot's current color and opens
|
|
11
|
+
the OKLCH editor; confirm assigns it to the slot in one undo step, and
|
|
12
|
+
cancel removes it. A slot picks an existing custom color as it picks a
|
|
13
|
+
palette step, opacity included. The pencil on a custom swatch edits it in
|
|
14
|
+
place, and the edit moves every slot that names it. The Tokens view gains a
|
|
15
|
+
**Custom Colours** section that lists, edits and removes them. Removing a
|
|
16
|
+
color that a component property, gradient stop or wash names first asks
|
|
17
|
+
for a replacement, preselects the nearest token, and rewrites each
|
|
18
|
+
reference with its opacity intact, all in one undo step.
|
|
19
|
+
- **Themes carry an optional `customColors` field.**
|
|
20
|
+
`ColorsAndType.customColors` sits beside `gradients` and holds
|
|
21
|
+
`{ variable, color }` entries. The field is additive: a theme without it
|
|
22
|
+
loads with no custom colors, `THEME_SCHEMA_VERSION` stays 5, and no
|
|
23
|
+
migration runs. Save, export and import keep the list, and Adopt writes
|
|
24
|
+
each custom color into `tokens.generated.css` with the theme's other
|
|
25
|
+
variables. `set-colors` and `save-theme` carry the field through
|
|
26
|
+
unchanged. `tokens.css` and the shipped themes declare no custom color, so
|
|
27
|
+
`check-page` reports `unknown-token` for page CSS that names
|
|
28
|
+
`var(--color-custom-1)`; reach a custom color through a component property.
|
|
29
|
+
|
|
30
|
+
## 0.96.1 — Vitest 5 peer range
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- **Installs alongside Vitest 5.** The optional `vitest` peer range now reads
|
|
35
|
+
`^4.0 || ^5.0`. With `^4.0` alone, npm stopped any install that re-resolved
|
|
36
|
+
the tree with `ERESOLVE` once Vitest 5 sat anywhere in it, including as an
|
|
37
|
+
uninstalled optional peer of Vite's devtools. The `testing` contract helpers
|
|
38
|
+
pass the full suite on Vitest 5.
|
|
39
|
+
|
|
3
40
|
## 0.96.0 — Lightbox zoom readout in natural pixels
|
|
4
41
|
|
|
5
42
|
### Changed
|
package/bin/set-colors.mjs
CHANGED
package/bin/setup-claude.mjs
CHANGED
|
@@ -19,7 +19,7 @@ import { join } from 'node:path';
|
|
|
19
19
|
|
|
20
20
|
// The package owns this prefix in a project's skills directory. A skill without
|
|
21
21
|
// it is the project's own and is never touched, whatever the flags say.
|
|
22
|
-
const SKILL_PREFIX = 'live-tokens-';
|
|
22
|
+
export const SKILL_PREFIX = 'live-tokens-';
|
|
23
23
|
|
|
24
24
|
const SAMPLE_PROMPTS = {
|
|
25
25
|
'live-tokens-create-page': 'build a pricing page using live-tokens components',
|
|
@@ -44,7 +44,7 @@ export function runSetupClaude({ pkgRoot, cwd, force = false }) {
|
|
|
44
44
|
throw new Error(`No bundled skills found at ${srcSkills}. Is the package installed correctly?`);
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
const shipped = directoriesIn(srcSkills);
|
|
47
|
+
const shipped = directoriesIn(srcSkills).filter((name) => name.startsWith(SKILL_PREFIX));
|
|
48
48
|
if (shipped.length === 0) {
|
|
49
49
|
throw new Error('No bundled skills to install.');
|
|
50
50
|
}
|
|
@@ -1098,7 +1098,13 @@ function buildColors(input, carry = {}) {
|
|
|
1098
1098
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
1099
1099
|
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
|
|
1100
1100
|
return {
|
|
1101
|
-
colors: {
|
|
1101
|
+
colors: {
|
|
1102
|
+
editorConfigs: configs,
|
|
1103
|
+
harmonyAxes,
|
|
1104
|
+
gradients,
|
|
1105
|
+
...carry.customColors ? { customColors: carry.customColors } : {},
|
|
1106
|
+
cssVariables
|
|
1107
|
+
},
|
|
1102
1108
|
report: {
|
|
1103
1109
|
...report,
|
|
1104
1110
|
gradients: gradients === carry.gradients ? "carried" : "recipes",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, O as Oklch,
|
|
1
|
+
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, b as CustomColorDiskToken, O as Oklch, e as HarmonyMode } from '../themeTypes-BaTQUyac.cjs';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
|
|
4
4
|
|
|
@@ -44,12 +44,14 @@ interface ColorsInput {
|
|
|
44
44
|
canvasGradient?: boolean;
|
|
45
45
|
}
|
|
46
46
|
/** The live colors and type this pass reads before it replaces the color
|
|
47
|
-
* state: the override bag, so every name no palette owns rides through,
|
|
48
|
-
*
|
|
49
|
-
* derived, not carried — see
|
|
47
|
+
* state: the override bag, so every name no palette owns rides through, the
|
|
48
|
+
* swatch gradients, so user-tuned ones survive, and the custom colours, which
|
|
49
|
+
* ride through untouched. Shadow opacity is derived, not carried — see
|
|
50
|
+
* `shadowVars`. */
|
|
50
51
|
interface CarryForward {
|
|
51
52
|
cssVariables?: Record<string, string>;
|
|
52
53
|
gradients?: GradientDiskToken[];
|
|
54
|
+
customColors?: CustomColorDiskToken[];
|
|
53
55
|
}
|
|
54
56
|
interface ContrastCheck {
|
|
55
57
|
textVar: string;
|
|
@@ -80,6 +82,9 @@ interface ColorsState {
|
|
|
80
82
|
editorConfigs: Record<string, PaletteConfig>;
|
|
81
83
|
harmonyAxes: HarmonyAxis[];
|
|
82
84
|
gradients: GradientDiskToken[];
|
|
85
|
+
/** Present only when the carried colours and type held the field, so a
|
|
86
|
+
* recolor adds no key to a theme that never had one. */
|
|
87
|
+
customColors?: CustomColorDiskToken[];
|
|
83
88
|
cssVariables: Record<string, string>;
|
|
84
89
|
}
|
|
85
90
|
interface BuildColorsResult {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, O as Oklch,
|
|
1
|
+
import { P as PaletteConfig, H as HarmonyAxis, G as GradientDiskToken, b as CustomColorDiskToken, O as Oklch, e as HarmonyMode } from '../themeTypes-BaTQUyac.js';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
|
|
4
4
|
|
|
@@ -44,12 +44,14 @@ interface ColorsInput {
|
|
|
44
44
|
canvasGradient?: boolean;
|
|
45
45
|
}
|
|
46
46
|
/** The live colors and type this pass reads before it replaces the color
|
|
47
|
-
* state: the override bag, so every name no palette owns rides through,
|
|
48
|
-
*
|
|
49
|
-
* derived, not carried — see
|
|
47
|
+
* state: the override bag, so every name no palette owns rides through, the
|
|
48
|
+
* swatch gradients, so user-tuned ones survive, and the custom colours, which
|
|
49
|
+
* ride through untouched. Shadow opacity is derived, not carried — see
|
|
50
|
+
* `shadowVars`. */
|
|
50
51
|
interface CarryForward {
|
|
51
52
|
cssVariables?: Record<string, string>;
|
|
52
53
|
gradients?: GradientDiskToken[];
|
|
54
|
+
customColors?: CustomColorDiskToken[];
|
|
53
55
|
}
|
|
54
56
|
interface ContrastCheck {
|
|
55
57
|
textVar: string;
|
|
@@ -80,6 +82,9 @@ interface ColorsState {
|
|
|
80
82
|
editorConfigs: Record<string, PaletteConfig>;
|
|
81
83
|
harmonyAxes: HarmonyAxis[];
|
|
82
84
|
gradients: GradientDiskToken[];
|
|
85
|
+
/** Present only when the carried colours and type held the field, so a
|
|
86
|
+
* recolor adds no key to a theme that never had one. */
|
|
87
|
+
customColors?: CustomColorDiskToken[];
|
|
83
88
|
cssVariables: Record<string, string>;
|
|
84
89
|
}
|
|
85
90
|
interface BuildColorsResult {
|
|
@@ -483,7 +483,13 @@ function buildColors(input, carry = {}) {
|
|
|
483
483
|
for (const t of gradients) cssVariables[t.variable] = formatGradientValue(t);
|
|
484
484
|
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
|
|
485
485
|
return {
|
|
486
|
-
colors: {
|
|
486
|
+
colors: {
|
|
487
|
+
editorConfigs: configs,
|
|
488
|
+
harmonyAxes,
|
|
489
|
+
gradients,
|
|
490
|
+
...carry.customColors ? { customColors: carry.customColors } : {},
|
|
491
|
+
cssVariables
|
|
492
|
+
},
|
|
487
493
|
report: {
|
|
488
494
|
...report,
|
|
489
495
|
gradients: gradients === carry.gradients ? "carried" : "recipes",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-
|
|
1
|
+
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-BaTQUyac.cjs';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.cjs';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-
|
|
1
|
+
import { A as AliasDiskValue, a as ComponentConfig } from '../themeTypes-BaTQUyac.js';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
|
|
3
3
|
export { C as CURRENT_COMPONENT_SCHEMA_VERSION } from '../index-4N-Orzzi.js';
|
|
4
4
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as FontStackVariable, C as ColorsAndType, c as FontStack, F as FontSource } from '../themeTypes-BaTQUyac.cjs';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.cjs';
|
|
3
3
|
|
|
4
4
|
type PairingSlot = 'display' | 'body' | 'serif' | 'mono' | 'editorial';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { d as FontStackVariable, C as ColorsAndType, c as FontStack, F as FontSource } from '../themeTypes-BaTQUyac.js';
|
|
2
2
|
export { r as readLiveTokensConfig, a as resolveDataDirs } from '../dataPaths-BpIK_Obx.js';
|
|
3
3
|
|
|
4
4
|
type PairingSlot = 'display' | 'body' | 'serif' | 'mono' | 'editorial';
|
|
@@ -181,6 +181,12 @@ interface GradientDiskToken {
|
|
|
181
181
|
opacity?: number;
|
|
182
182
|
}[];
|
|
183
183
|
}
|
|
184
|
+
/** On-disk shape of one `--color-custom-N` token: a theme-owned colour that
|
|
185
|
+
* holds its own OKLCH value, one value for light and dark alike. */
|
|
186
|
+
interface CustomColorDiskToken {
|
|
187
|
+
variable: string;
|
|
188
|
+
color: Oklch;
|
|
189
|
+
}
|
|
184
190
|
/**
|
|
185
191
|
* Where a live component config resolved from: the unsaved `_working`
|
|
186
192
|
* buffer, or the open theme's embedded copy. Every theme carries every
|
|
@@ -208,6 +214,9 @@ interface ColorsAndType {
|
|
|
208
214
|
/** Absent on files saved before gradients round-tripped; the loader keeps
|
|
209
215
|
* the stock defaults then, which match what those files rendered. */
|
|
210
216
|
gradients?: GradientDiskToken[];
|
|
217
|
+
/** Absent when the theme holds no custom colour; the loader reads that as
|
|
218
|
+
* an empty list. */
|
|
219
|
+
customColors?: CustomColorDiskToken[];
|
|
211
220
|
/** Four fixed harmony axes, each owning a hue with an optional bound family. */
|
|
212
221
|
harmonyAxes?: HarmonyAxis[];
|
|
213
222
|
/**
|
|
@@ -257,4 +266,4 @@ interface ComponentConfig {
|
|
|
257
266
|
schemaVersion?: number;
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a,
|
|
269
|
+
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, CustomColorDiskToken as b, FontStack as c, FontStackVariable as d, HarmonyMode as e };
|
|
@@ -181,6 +181,12 @@ interface GradientDiskToken {
|
|
|
181
181
|
opacity?: number;
|
|
182
182
|
}[];
|
|
183
183
|
}
|
|
184
|
+
/** On-disk shape of one `--color-custom-N` token: a theme-owned colour that
|
|
185
|
+
* holds its own OKLCH value, one value for light and dark alike. */
|
|
186
|
+
interface CustomColorDiskToken {
|
|
187
|
+
variable: string;
|
|
188
|
+
color: Oklch;
|
|
189
|
+
}
|
|
184
190
|
/**
|
|
185
191
|
* Where a live component config resolved from: the unsaved `_working`
|
|
186
192
|
* buffer, or the open theme's embedded copy. Every theme carries every
|
|
@@ -208,6 +214,9 @@ interface ColorsAndType {
|
|
|
208
214
|
/** Absent on files saved before gradients round-tripped; the loader keeps
|
|
209
215
|
* the stock defaults then, which match what those files rendered. */
|
|
210
216
|
gradients?: GradientDiskToken[];
|
|
217
|
+
/** Absent when the theme holds no custom colour; the loader reads that as
|
|
218
|
+
* an empty list. */
|
|
219
|
+
customColors?: CustomColorDiskToken[];
|
|
211
220
|
/** Four fixed harmony axes, each owning a hue with an optional bound family. */
|
|
212
221
|
harmonyAxes?: HarmonyAxis[];
|
|
213
222
|
/**
|
|
@@ -257,4 +266,4 @@ interface ComponentConfig {
|
|
|
257
266
|
schemaVersion?: number;
|
|
258
267
|
}
|
|
259
268
|
|
|
260
|
-
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a,
|
|
269
|
+
export type { AliasDiskValue as A, ColorsAndType as C, FontSource as F, GradientDiskToken as G, HarmonyAxis as H, Oklch as O, PaletteConfig as P, ComponentConfig as a, CustomColorDiskToken as b, FontStack as c, FontStackVariable as d, HarmonyMode as e };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@motion-proto/live-tokens",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.97.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"sideEffects": [
|
|
6
6
|
"**/*.css"
|
|
@@ -56,6 +56,7 @@
|
|
|
56
56
|
"dist-plugin",
|
|
57
57
|
"src/testing-js",
|
|
58
58
|
".claude/skills",
|
|
59
|
+
"!.claude/skills/evaluate",
|
|
59
60
|
"!.claude/skills/**/skill-alt.md",
|
|
60
61
|
"bin",
|
|
61
62
|
"template",
|
|
@@ -204,6 +205,7 @@
|
|
|
204
205
|
"check:smoke-install": "bash scripts/smoke-install.sh",
|
|
205
206
|
"check:smoke-create": "bash scripts/smoke-create.sh",
|
|
206
207
|
"eval": "bash scripts/eval.sh",
|
|
208
|
+
"eval:billing": "bash scripts/eval-billing.sh",
|
|
207
209
|
"build:eval-project": "bash scripts/build-eval-project.sh",
|
|
208
210
|
"check:smoke-component-tests": "bash scripts/smoke-component-tests.sh",
|
|
209
211
|
"check:smoke-page-tests": "bash scripts/smoke-page-tests.sh",
|
|
@@ -216,7 +218,7 @@
|
|
|
216
218
|
"sass": "^1.0",
|
|
217
219
|
"svelte": "^5",
|
|
218
220
|
"vite": "^8",
|
|
219
|
-
"vitest": "^4.0"
|
|
221
|
+
"vitest": "^4.0 || ^5.0"
|
|
220
222
|
},
|
|
221
223
|
"peerDependenciesMeta": {
|
|
222
224
|
"@playwright/test": {
|
|
@@ -818,13 +818,6 @@
|
|
|
818
818
|
color: var(--ui-text-tertiary);
|
|
819
819
|
}
|
|
820
820
|
|
|
821
|
-
/* Anchor dropdown to the trigger's right edge; the toolbar sits flush against
|
|
822
|
-
the editor's right gutter, so a left-anchored dropdown would clip. */
|
|
823
|
-
.canvas-toolbar :global(.ui-ts-dropdown) {
|
|
824
|
-
left: auto;
|
|
825
|
-
right: 0;
|
|
826
|
-
}
|
|
827
|
-
|
|
828
821
|
.variant-tabs {
|
|
829
822
|
display: inline-flex;
|
|
830
823
|
flex-wrap: wrap;
|
|
@@ -19,6 +19,7 @@ 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
|
+
import { isCustomColorList } from '../themes/slices/customColors';
|
|
22
23
|
import { seedShadowsFromDom } from '../themes/slices/shadows';
|
|
23
24
|
import { makeDefaultWashScale, type WashFamily } from '../themes/slices/washes';
|
|
24
25
|
import { normalizeColumnsState } from '../themes/slices/columns';
|
|
@@ -73,6 +74,13 @@ function migrateGradients(state: EditorState): EditorState {
|
|
|
73
74
|
return { ...state, gradients: { tokens: makeDefaultGradients() } };
|
|
74
75
|
}
|
|
75
76
|
|
|
77
|
+
// Persisted state from before custom colours gets the empty default from the
|
|
78
|
+
// shallow merge; anything else that is not a well-formed list loads as empty.
|
|
79
|
+
function migrateCustomColors(state: EditorState): EditorState {
|
|
80
|
+
if (isCustomColorList(state.customColors?.tokens)) return state;
|
|
81
|
+
return { ...state, customColors: { tokens: [] } };
|
|
82
|
+
}
|
|
83
|
+
|
|
76
84
|
// `hydrate` shallow-merges the persisted `components` bag over the default, so
|
|
77
85
|
// a slice serialized by an older build can lack fields added since (e.g.
|
|
78
86
|
// `config`, added with the two-field alias/config split). `componentsToVars`
|
|
@@ -205,7 +213,7 @@ export function hydrate(): void {
|
|
|
205
213
|
store.set(
|
|
206
214
|
normalizeHarmonyAxes(
|
|
207
215
|
normalizeBaseAnchors(
|
|
208
|
-
normalizePaletteBasis(normalizePaletteLabels(normalizeComponents(normalizeColumns(normalizeWashes(migrateGradients(merged)))))),
|
|
216
|
+
normalizePaletteBasis(normalizePaletteLabels(normalizeComponents(normalizeColumns(normalizeWashes(migrateCustomColors(migrateGradients(merged))))))),
|
|
209
217
|
),
|
|
210
218
|
),
|
|
211
219
|
);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
washesToVars,
|
|
22
22
|
shadowsToVars,
|
|
23
23
|
gradientsToVars,
|
|
24
|
+
customColorsToVars,
|
|
24
25
|
componentsToVars,
|
|
25
26
|
} from './editorStore';
|
|
26
27
|
|
|
@@ -34,6 +35,7 @@ export function deriveCssVars(state: EditorState): Record<string, string> {
|
|
|
34
35
|
if (state.gradients.tokens.length > 0) {
|
|
35
36
|
Object.assign(out, gradientsToVars(state.gradients));
|
|
36
37
|
}
|
|
38
|
+
Object.assign(out, customColorsToVars(state.customColors));
|
|
37
39
|
Object.assign(out, palettesToVars(state.palettes));
|
|
38
40
|
Object.assign(out, componentsToVars(state.components));
|
|
39
41
|
Object.assign(out, resolveFontStackValues(state.fonts.stacks, state.fonts.sources));
|
|
@@ -74,6 +74,10 @@ import {
|
|
|
74
74
|
loadGradientsFromFile,
|
|
75
75
|
makeDefaultGradients,
|
|
76
76
|
} from '../themes/slices/gradients';
|
|
77
|
+
import {
|
|
78
|
+
customColorsToVars,
|
|
79
|
+
loadCustomColorsFromFile,
|
|
80
|
+
} from '../themes/slices/customColors';
|
|
77
81
|
import {
|
|
78
82
|
componentBaseline,
|
|
79
83
|
getComponentOwnedVarNames,
|
|
@@ -102,6 +106,7 @@ function emptyState(): EditorState {
|
|
|
102
106
|
columns: defaultColumns(),
|
|
103
107
|
components: {},
|
|
104
108
|
gradients: { tokens: makeDefaultGradients() },
|
|
109
|
+
customColors: { tokens: [] },
|
|
105
110
|
harmonyAxes: defaultHarmonyAxes(),
|
|
106
111
|
cssVars: {},
|
|
107
112
|
};
|
|
@@ -179,6 +184,20 @@ export {
|
|
|
179
184
|
removeGradientToken,
|
|
180
185
|
} from '../themes/slices/gradients';
|
|
181
186
|
|
|
187
|
+
export {
|
|
188
|
+
customColorsToVars,
|
|
189
|
+
nextCustomColorSlot,
|
|
190
|
+
isCustomColorSlot,
|
|
191
|
+
addCustomColor,
|
|
192
|
+
setCustomColor,
|
|
193
|
+
removeCustomColor,
|
|
194
|
+
customColorUsages,
|
|
195
|
+
describeCustomColorUsages,
|
|
196
|
+
nearestColorToken,
|
|
197
|
+
replaceAndRemoveCustomColor,
|
|
198
|
+
} from '../themes/slices/customColors';
|
|
199
|
+
export type { CustomColorUsage } from '../themes/slices/customColors';
|
|
200
|
+
|
|
182
201
|
export {
|
|
183
202
|
componentsToVars,
|
|
184
203
|
getComponentOwnedVarNames,
|
|
@@ -404,6 +423,7 @@ export function colorsAndTypeToState(colorsAndType: ColorsAndType): EditorState
|
|
|
404
423
|
// begin with).
|
|
405
424
|
for (const load of Object.values(domainLoaders)) load(next, rawVars);
|
|
406
425
|
loadGradientsFromFile(next, colorsAndType.gradients, rawVars);
|
|
426
|
+
loadCustomColorsFromFile(next, colorsAndType.customColors, rawVars);
|
|
407
427
|
next.harmonyAxes = sanitizeHarmonyAxes(renameBackgroundHarmonyFamily(colorsAndType.harmonyAxes), next.palettes);
|
|
408
428
|
next.cssVars = rawVars;
|
|
409
429
|
return next;
|
|
@@ -438,12 +458,17 @@ function colorsAndTypeContent(state: EditorState): Omit<ColorsAndType, 'name' |
|
|
|
438
458
|
if (state.gradients.tokens.length > 0) {
|
|
439
459
|
Object.assign(cssVariables, gradientsToVars(state.gradients));
|
|
440
460
|
}
|
|
461
|
+
// An empty list writes no field, so a theme that never held a custom colour
|
|
462
|
+
// still compares equal to the buffer its load produced.
|
|
463
|
+
const hasCustomColors = state.customColors.tokens.length > 0;
|
|
464
|
+
if (hasCustomColors) Object.assign(cssVariables, customColorsToVars(state.customColors));
|
|
441
465
|
return {
|
|
442
466
|
editorConfigs: state.palettes,
|
|
443
467
|
cssVariables,
|
|
444
468
|
fontSources: state.fonts.sources,
|
|
445
469
|
fontStacks: state.fonts.stacks,
|
|
446
470
|
gradients: state.gradients.tokens,
|
|
471
|
+
...(hasCustomColors ? { customColors: state.customColors.tokens } : {}),
|
|
447
472
|
harmonyAxes: state.harmonyAxes,
|
|
448
473
|
schemaVersion: CURRENT_COLORS_AND_TYPE_SCHEMA_VERSION,
|
|
449
474
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PaletteConfig, FontSource, FontStack } from '../themes/themeTypes';
|
|
2
2
|
import type { GradientStop, GradientValue, LinearDirection } from '../themes/parsers/gradient';
|
|
3
3
|
import type { HarmonyAxis } from '../palettes/colorHarmony';
|
|
4
|
+
import type { Oklch } from '../palettes/oklch';
|
|
4
5
|
import type { ColumnViewport, ColumnViewportValues } from '../themes/slices/columns';
|
|
5
6
|
|
|
6
7
|
export interface ShadowGlobals {
|
|
@@ -80,6 +81,13 @@ export interface GradientToken {
|
|
|
80
81
|
stops: GradientTokenStop[];
|
|
81
82
|
}
|
|
82
83
|
|
|
84
|
+
/** A theme-owned `--color-custom-N` colour. Slots name it the way they name a
|
|
85
|
+
* palette step, so one edit moves every slot that names it. */
|
|
86
|
+
export interface CustomColorToken {
|
|
87
|
+
variable: string;
|
|
88
|
+
color: Oklch;
|
|
89
|
+
}
|
|
90
|
+
|
|
83
91
|
/** Structured gradient payload carried inline on a component alias.
|
|
84
92
|
* `GradientToken` minus `variable` (the alias key itself is the binding).
|
|
85
93
|
* Used when a component owns a per-instance gradient that doesn't share the
|
|
@@ -123,6 +131,7 @@ export interface EditorState {
|
|
|
123
131
|
columns: ColumnsState;
|
|
124
132
|
components: Record<string, ComponentSlice>;
|
|
125
133
|
gradients: { tokens: GradientToken[] };
|
|
134
|
+
customColors: { tokens: CustomColorToken[] };
|
|
126
135
|
/** Four fixed numbered axes (index 0 is the anchor); each owns a hue;
|
|
127
136
|
* family is the live-bound color or null. */
|
|
128
137
|
harmonyAxes: HarmonyAxis[];
|
|
@@ -26,7 +26,7 @@ import { SHADOW_VAR_NAMES, parseShadowCss, shadowTokenCss } from './parsers/shad
|
|
|
26
26
|
import { AA_BODY, AA_LARGE, contrastRatio, findLForContrast } from '../palettes/contrast';
|
|
27
27
|
import { type HarmonyAxis, type HarmonyMode } from '../palettes/colorHarmony';
|
|
28
28
|
import { defaultPaletteConfig } from '../../ui/palette/paletteMath';
|
|
29
|
-
import type { GradientDiskToken, PaletteConfig } from './themeTypes';
|
|
29
|
+
import type { CustomColorDiskToken, GradientDiskToken, PaletteConfig } from './themeTypes';
|
|
30
30
|
|
|
31
31
|
export interface ColorsInput {
|
|
32
32
|
scheme: SchemeDirection;
|
|
@@ -41,12 +41,14 @@ export interface ColorsInput {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/** The live colors and type this pass reads before it replaces the color
|
|
44
|
-
* state: the override bag, so every name no palette owns rides through,
|
|
45
|
-
*
|
|
46
|
-
* derived, not carried — see
|
|
44
|
+
* state: the override bag, so every name no palette owns rides through, the
|
|
45
|
+
* swatch gradients, so user-tuned ones survive, and the custom colours, which
|
|
46
|
+
* ride through untouched. Shadow opacity is derived, not carried — see
|
|
47
|
+
* `shadowVars`. */
|
|
47
48
|
export interface CarryForward {
|
|
48
49
|
cssVariables?: Record<string, string>;
|
|
49
50
|
gradients?: GradientDiskToken[];
|
|
51
|
+
customColors?: CustomColorDiskToken[];
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
export interface ContrastCheck {
|
|
@@ -80,6 +82,9 @@ export interface ColorsState {
|
|
|
80
82
|
editorConfigs: Record<string, PaletteConfig>;
|
|
81
83
|
harmonyAxes: HarmonyAxis[];
|
|
82
84
|
gradients: GradientDiskToken[];
|
|
85
|
+
/** Present only when the carried colours and type held the field, so a
|
|
86
|
+
* recolor adds no key to a theme that never had one. */
|
|
87
|
+
customColors?: CustomColorDiskToken[];
|
|
83
88
|
cssVariables: Record<string, string>;
|
|
84
89
|
}
|
|
85
90
|
|
|
@@ -467,7 +472,13 @@ export function buildColors(input: ColorsInput, carry: CarryForward = {}): Build
|
|
|
467
472
|
Object.assign(cssVariables, shadowVars(carry.cssVariables ?? {}, baseColors.Canvas));
|
|
468
473
|
|
|
469
474
|
return {
|
|
470
|
-
colors: {
|
|
475
|
+
colors: {
|
|
476
|
+
editorConfigs: configs,
|
|
477
|
+
harmonyAxes,
|
|
478
|
+
gradients,
|
|
479
|
+
...(carry.customColors ? { customColors: carry.customColors } : {}),
|
|
480
|
+
cssVariables,
|
|
481
|
+
},
|
|
471
482
|
report: {
|
|
472
483
|
...report,
|
|
473
484
|
gradients: gradients === carry.gradients ? 'carried' : 'recipes',
|