@motion-proto/live-tokens 0.44.0 → 0.46.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist-plugin/index.cjs +1 -1
  3. package/dist-plugin/index.js +1 -1
  4. package/package.json +1 -1
  5. package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +0 -1
  6. package/src/editor/component-editor/scaffolding/ComponentFileMenu.svelte +1 -1
  7. package/src/editor/core/palettes/colorHarmony.ts +35 -6
  8. package/src/editor/core/palettes/paletteDerivation.ts +2 -3
  9. package/src/editor/core/store/editorTypes.ts +2 -2
  10. package/src/editor/docs/CodeBlock.svelte +0 -2
  11. package/src/editor/docs/Docs.svelte +0 -3
  12. package/src/editor/pages/ComponentEditorPage.svelte +3 -5
  13. package/src/editor/pages/EditorShell.svelte +3 -5
  14. package/src/editor/ui/ColorEditPanel.svelte +19 -22
  15. package/src/editor/ui/EditorViewSwitcher.svelte +2 -3
  16. package/src/editor/ui/FileLoadList.svelte +7 -9
  17. package/src/editor/ui/ManifestFileManager.svelte +1 -3
  18. package/src/editor/ui/ProjectFontsSection.svelte +0 -4
  19. package/src/editor/ui/SurfacesTab.svelte +3 -3
  20. package/src/editor/ui/TextStylesSection.svelte +6 -8
  21. package/src/editor/ui/ThemeFileManager.svelte +1 -3
  22. package/src/editor/ui/UIEasingSelector.svelte +0 -2
  23. package/src/editor/ui/UIFontFamilySelector.svelte +9 -3
  24. package/src/editor/ui/UIMenuButton.svelte +230 -0
  25. package/src/editor/ui/UIPaletteSelector.svelte +0 -1
  26. package/src/editor/ui/VariablesTab.svelte +3 -4
  27. package/src/editor/ui/colors/AxisNumeral.svelte +70 -0
  28. package/src/editor/ui/colors/ColorReadouts.svelte +0 -2
  29. package/src/editor/ui/colors/ColorStory.svelte +24 -77
  30. package/src/editor/ui/colors/ColorWheel.svelte +48 -29
  31. package/src/editor/ui/colors/ColorsTab.svelte +361 -212
  32. package/src/editor/ui/colors/HarmonyAxesList.svelte +264 -141
  33. package/src/editor/ui/colors/LightnessBar.svelte +0 -2
  34. package/src/editor/ui/colors/harmonyDrag.ts +10 -0
  35. package/src/editor/ui/colors/harmonyModeIcons.ts +5 -0
  36. package/src/editor/ui/colors/paletteBaseColor.ts +25 -39
  37. package/src/live-tokens/data/tokens.generated.css +28 -28
  38. package/src/system/components/ImageLightbox.svelte +0 -2
  39. package/src/system/components/SectionDivider.svelte +4 -4
  40. package/src/editor/core/palettes/recommendText.ts +0 -216
  41. package/src/editor/core/palettes/solveTextContrast.ts +0 -254
@@ -1,254 +0,0 @@
1
- // "Derive accessible text": solve each family's Text lightness curve so the
2
- // derived text colors clear WCAG AA against the surfaces they sit on.
3
- //
4
- // The Text scale derives L as seedL × (curveSample/100), clamped to [0,2], so a
5
- // family can only reach L ∈ [0, min(1, 2·seedL)]. The solver works inside that
6
- // window (passed as `lMax`) and emits a 5-anchor lightness curve whose sample
7
- // at each step x reproduces the solved L; a companion saturation curve pins the
8
- // solved (possibly chroma-decayed) chroma so the round-trip is exact. Surface
9
- // and page colors come from the real `palettesToValues` derivation (numeric
10
- // OKLCH); the solver converts to hex only at its own sRGB (WCAG) boundary.
11
-
12
- import { oklchToHexClamped } from './oklch';
13
- import {
14
- palettesToValues,
15
- serializeDerivedValue,
16
- PALETTE_SPECS,
17
- scaleToCssVar,
18
- defaultScaleCurves,
19
- type DerivedValue,
20
- type SchemeDirection,
21
- } from './paletteDerivation';
22
- import { findLForContrast, contrastRatio } from './contrast';
23
- import { sampleCurve, makeAnchor, type CurveAnchor } from '../../ui/curveEngine';
24
- import type { PaletteConfig } from '../themes/themeTypes';
25
-
26
- export interface ContrastPairing {
27
- family: string;
28
- step: string;
29
- textVar: string;
30
- againstVar: string;
31
- targetRatio: number;
32
- achievedRatio: number;
33
- meets: boolean;
34
- /** True when this pairing carries an AA guarantee (vs. a deliberately graded,
35
- * non-AA step like `muted`/`disabled` on chromatic families). */
36
- guaranteed: boolean;
37
- }
38
-
39
- export interface TextSolveResult {
40
- patches: Record<string, Pick<PaletteConfig, 'scaleCurves'>>;
41
- report: ContrastPairing[];
42
- }
43
-
44
- const TEXT_STEPS: { name: string; x: number }[] = [
45
- { name: 'primary', x: 0 },
46
- { name: 'secondary', x: 25 },
47
- { name: 'tertiary', x: 50 },
48
- { name: 'muted', x: 75 },
49
- { name: 'disabled', x: 100 },
50
- ];
51
-
52
- export const NEUTRAL_BAND = [
53
- '--surface-neutral-lowest', '--surface-neutral-lower', '--surface-neutral-low',
54
- '--surface-neutral', '--surface-neutral-high', '--surface-neutral-higher',
55
- '--surface-neutral-highest',
56
- ];
57
-
58
- interface StepPlan {
59
- ratio: number;
60
- guaranteed: boolean;
61
- /** 'band' = adverse extreme of the neutral surface band; 'default' = surface
62
- * default; 'worst' = adverse of {surface default, page bg}. */
63
- target: 'band' | 'default' | 'worst';
64
- }
65
-
66
- // Neutral family sits on neutral surfaces; primary/secondary must clear the
67
- // adverse band extreme, tertiary/muted the default surface. Disabled is graded.
68
- //
69
- // Muted held a large-text 3.0 floor until 0.44.0. Nothing in the name marked it
70
- // large-only, so consumers reached for it as a quiet body colour and shipped
71
- // text below AA; disabled is now the sole sub-AA step, which WCAG exempts.
72
- const NEUTRAL_PLAN: Record<string, StepPlan> = {
73
- primary: { ratio: 7.0, guaranteed: true, target: 'band' },
74
- secondary: { ratio: 4.5, guaranteed: true, target: 'band' },
75
- tertiary: { ratio: 4.5, guaranteed: true, target: 'default' },
76
- muted: { ratio: 4.5, guaranteed: true, target: 'default' },
77
- disabled: { ratio: 2.0, guaranteed: false, target: 'default' },
78
- };
79
-
80
- // Chromatic families: primary must clear the worse of surface-default / page-bg
81
- // (chroma decay allowed), secondary the default surface; the rest grade toward
82
- // the surface (non-AA).
83
- const CHROMATIC_PLAN: Record<string, StepPlan> = {
84
- primary: { ratio: 4.5, guaranteed: true, target: 'worst' },
85
- secondary: { ratio: 4.5, guaranteed: true, target: 'default' },
86
- tertiary: { ratio: 3.0, guaranteed: false, target: 'default' },
87
- muted: { ratio: 2.2, guaranteed: false, target: 'default' },
88
- disabled: { ratio: 1.6, guaranteed: false, target: 'default' },
89
- };
90
-
91
- interface Surface {
92
- var: string;
93
- l: number;
94
- c: number;
95
- h: number;
96
- }
97
-
98
- function colorSurface(varName: string, value: DerivedValue | undefined): Surface | null {
99
- return value?.kind === 'color' ? { var: varName, l: value.l, c: value.c, h: value.h } : null;
100
- }
101
-
102
- // The solver terminates in sRGB (WCAG luminance is sRGB-defined); convert the
103
- // numeric surface to hex only at this boundary. Clamp so contrast math sees the
104
- // same chroma-reduced projection the CSS ships (unclamped override surfaces).
105
- function surfaceHex(s: Surface): string {
106
- return oklchToHexClamped(s.l, s.c, s.h);
107
- }
108
-
109
- // The hardest surface to clear: for lighter text the highest-luminance surface,
110
- // for darker text the lowest.
111
- function pickAdverse(surfaces: Surface[], direction: 'lighter' | 'darker'): Surface {
112
- let best = surfaces[0];
113
- let bestL = best.l;
114
- for (const s of surfaces.slice(1)) {
115
- if (direction === 'lighter' ? s.l > bestL : s.l < bestL) {
116
- best = s;
117
- bestL = s.l;
118
- }
119
- }
120
- return best;
121
- }
122
-
123
- export function solveTextCurves(
124
- palettes: Record<string, PaletteConfig>,
125
- scheme?: SchemeDirection,
126
- ): TextSolveResult {
127
- const values = palettesToValues(palettes);
128
- const surfaceDefault = colorSurface('--surface-neutral', values['--surface-neutral'])!;
129
- const rawPageBg = values['--page-bg'];
130
- const pageBg: Surface = rawPageBg?.kind === 'color'
131
- ? colorSurface('--page-bg', rawPageBg)!
132
- : surfaceDefault;
133
- const band: Surface[] = NEUTRAL_BAND
134
- .map((v) => colorSurface(v, values[v]))
135
- .filter((s): s is Surface => s !== null);
136
-
137
- const resolvedScheme: SchemeDirection =
138
- scheme ?? (pageBg.l >= 0.5 ? 'light' : 'dark');
139
- const direction: 'lighter' | 'darker' = resolvedScheme === 'light' ? 'darker' : 'lighter';
140
-
141
- const bandAdverse = pickAdverse(band, direction);
142
- const worstAdverse = pickAdverse([surfaceDefault, pageBg], direction);
143
-
144
- const patches: Record<string, Pick<PaletteConfig, 'scaleCurves'>> = {};
145
- const report: ContrastPairing[] = [];
146
-
147
- for (const spec of PALETTE_SPECS) {
148
- const config = palettes[spec.label];
149
- if (!config) continue;
150
-
151
- const seed = config.baseColor;
152
- const seedL = Math.max(seed.l, 1e-4);
153
- const baseC = Math.max(seed.c, 1e-9);
154
- const baseH = seed.h;
155
- const lMax = Math.min(1, 2 * seedL);
156
-
157
- const curveOffset = config.curveOffset ?? {};
158
- const lOff = curveOffset['Text-lightness'] ?? 0;
159
- const sOff = curveOffset['Text-saturation'] ?? 0;
160
- const satCurve = config.scaleCurves?.Text?.saturation ?? defaultScaleCurves.Text.saturation();
161
-
162
- const isNeutral = spec.label === 'Neutral';
163
- const plan = isNeutral ? NEUTRAL_PLAN : CHROMATIC_PLAN;
164
-
165
- const lightAnchors: CurveAnchor[] = [];
166
- const satAnchors: CurveAnchor[] = [];
167
-
168
- for (const step of TEXT_STEPS) {
169
- const stepPlan = plan[step.name];
170
- const against =
171
- stepPlan.target === 'band' ? bandAdverse :
172
- stepPlan.target === 'worst' ? worstAdverse : surfaceDefault;
173
-
174
- const origSatMul = Math.max(0, Math.min(2, (sampleCurve(satCurve, step.x) + sOff) / 100));
175
- const stepChroma = baseC * origSatMul;
176
-
177
- const solved = findLForContrast({
178
- against: surfaceHex(against),
179
- ratio: stepPlan.ratio,
180
- direction,
181
- c: stepChroma,
182
- h: baseH,
183
- lMin: 0,
184
- lMax,
185
- });
186
-
187
- const lightY = clamp(0, 200, (100 * solved.l) / seedL - lOff);
188
- const satY = clamp(0, 200, (100 * solved.c) / baseC - sOff);
189
- lightAnchors.push(makeAnchor(step.x, lightY));
190
- satAnchors.push(makeAnchor(step.x, satY));
191
- }
192
-
193
- patches[spec.label] = {
194
- scaleCurves: { Text: { lightness: lightAnchors, saturation: satAnchors } },
195
- };
196
- }
197
-
198
- // Report against the actually-derived text hexes (post-round-trip verification
199
- // at the solver level, mirroring Global invariant 8).
200
- const derivedValues = palettesToValues(applyPatches(palettes, patches));
201
- for (const spec of PALETTE_SPECS) {
202
- if (!patches[spec.label]) continue;
203
- const isNeutral = spec.label === 'Neutral';
204
- const plan = isNeutral ? NEUTRAL_PLAN : CHROMATIC_PLAN;
205
- for (const step of TEXT_STEPS) {
206
- const stepPlan = plan[step.name];
207
- const textVar = scaleToCssVar('Text', step.name, spec.cssNamespace);
208
- if (!textVar) continue;
209
- const against =
210
- stepPlan.target === 'band' ? bandAdverse :
211
- stepPlan.target === 'worst' ? worstAdverse : surfaceDefault;
212
- const textValue = derivedValues[textVar];
213
- const againstValue = derivedValues[against.var];
214
- const againstHex = againstValue?.kind === 'color' ? serializeDerivedValue(againstValue) : surfaceHex(against);
215
- const achieved = textValue?.kind === 'color' ? contrastRatio(serializeDerivedValue(textValue), againstHex) : 1;
216
- report.push({
217
- family: spec.label,
218
- step: step.name,
219
- textVar,
220
- againstVar: against.var,
221
- targetRatio: stepPlan.ratio,
222
- achievedRatio: achieved,
223
- meets: achieved >= minGuarantee(stepPlan),
224
- guaranteed: stepPlan.guaranteed,
225
- });
226
- }
227
- }
228
-
229
- return { patches, report };
230
- }
231
-
232
- // Every guaranteed step clears the body-text AA floor. Graded steps floor at 1
233
- // (informational).
234
- function minGuarantee(plan: StepPlan): number {
235
- return plan.guaranteed ? 4.5 : 1;
236
- }
237
-
238
- function clamp(lo: number, hi: number, v: number): number {
239
- return Math.max(lo, Math.min(hi, v));
240
- }
241
-
242
- function applyPatches(
243
- palettes: Record<string, PaletteConfig>,
244
- patches: Record<string, Pick<PaletteConfig, 'scaleCurves'>>,
245
- ): Record<string, PaletteConfig> {
246
- const out: Record<string, PaletteConfig> = {};
247
- for (const [label, config] of Object.entries(palettes)) {
248
- const patch = patches[label];
249
- out[label] = patch
250
- ? { ...config, scaleCurves: { ...config.scaleCurves, ...patch.scaleCurves } }
251
- : config;
252
- }
253
- return out;
254
- }