@motion-proto/live-tokens 0.7.1 → 0.8.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/dist-plugin/index.cjs +707 -90
- package/dist-plugin/index.d.cts +1 -0
- package/dist-plugin/index.d.ts +1 -0
- package/dist-plugin/index.js +707 -90
- package/package.json +2 -1
- package/src/app/site.css +1 -1
- package/src/editor/component-editor/CollapsibleSectionEditor.svelte +34 -27
- package/src/editor/component-editor/DialogEditor.svelte +4 -4
- package/src/editor/component-editor/NotificationEditor.svelte +3 -1
- package/src/editor/component-editor/SectionDividerEditor.svelte +439 -112
- package/src/editor/component-editor/StandardButtonsEditor.svelte +13 -1
- package/src/editor/component-editor/editors.d.ts +10 -0
- package/src/editor/component-editor/scaffolding/AngleDial.svelte +52 -13
- package/src/editor/component-editor/scaffolding/ComponentFileManager.svelte +10 -11
- package/src/editor/component-editor/scaffolding/LinkedBlock.svelte +0 -1
- package/src/editor/component-editor/scaffolding/RadialShapePad.svelte +483 -0
- package/src/editor/component-editor/scaffolding/ShadowBackdrop.svelte +15 -2
- package/src/editor/component-editor/scaffolding/StateBlock.svelte +103 -15
- package/src/editor/component-editor/scaffolding/TokenLayout.svelte +9 -6
- package/src/editor/component-editor/scaffolding/TypeEditor.svelte +13 -1
- package/src/editor/component-editor/scaffolding/VariantGroup.svelte +239 -25
- package/src/editor/component-editor/scaffolding/buildTypeGroupTokens.ts +1 -0
- package/src/editor/component-editor/scaffolding/types.ts +11 -0
- package/src/editor/core/components/componentConfigKeys.ts +8 -0
- package/src/editor/core/components/componentConfigService.ts +2 -2
- package/src/editor/core/components/componentPersist.ts +7 -5
- package/src/editor/core/manifests/manifestService.ts +58 -3
- package/src/editor/core/palettes/familySwap.ts +99 -0
- package/src/editor/core/palettes/paletteDerivation.ts +69 -0
- package/src/editor/core/palettes/tokenRegistry.ts +4 -1
- package/src/editor/core/store/editorStore.ts +206 -12
- package/src/editor/core/store/editorTypes.ts +55 -12
- package/src/editor/core/store/gradientSource.ts +192 -0
- package/src/editor/core/themes/migrations/2026-05-19-collapsiblesection-drop-frame-surface.ts +28 -0
- package/src/editor/core/themes/migrations/2026-05-19-sectiondivider-rich-gradient.ts +35 -0
- package/src/editor/core/themes/migrations/2026-05-20-sectiondivider-slim-variants.ts +82 -0
- package/src/editor/core/themes/migrations/2026-05-21-sectiondivider-spacing-to-padding.ts +24 -0
- package/src/editor/core/themes/migrations/2026-05-22-sectiondivider-intrinsics-to-css.ts +81 -0
- package/src/editor/core/themes/migrations/index.ts +10 -0
- package/src/editor/core/themes/slices/components.ts +18 -4
- package/src/editor/core/themes/slices/gradients.ts +88 -13
- package/src/editor/core/themes/themeInit.ts +2 -2
- package/src/editor/core/themes/themeTypes.ts +56 -1
- package/src/editor/overlay/ColumnsOverlay.svelte +0 -1
- package/src/editor/overlay/LiveEditorOverlay.svelte +1 -4
- package/src/editor/styles/ui-editor.css +1 -0
- package/src/editor/styles/ui-form-controls.css +19 -20
- package/src/editor/ui/BezierCurveEditor.svelte +114 -63
- package/src/editor/ui/EditorViewSwitcher.svelte +0 -1
- package/src/editor/ui/FileLoadList.svelte +22 -5
- package/src/editor/ui/FontStackEditor.svelte +214 -76
- package/src/editor/ui/GradientEditor.svelte +435 -215
- package/src/editor/ui/GradientStopPicker.svelte +11 -3
- package/src/editor/ui/ManifestFileManager.svelte +71 -4
- package/src/editor/ui/PaletteEditor.svelte +52 -79
- package/src/editor/ui/ProjectFontsSection.svelte +328 -293
- package/src/editor/ui/ThemeFileManager.svelte +0 -4
- package/src/editor/ui/UIFontFamilySelector.svelte +0 -1
- package/src/editor/ui/UIFontSizeSelector.svelte +3 -0
- package/src/editor/ui/UIInfoPopover.svelte +0 -1
- package/src/editor/ui/UILetterSpacingSelector.svelte +65 -0
- package/src/editor/ui/UIPaletteSelector.svelte +31 -4
- package/src/editor/ui/UIPillButton.svelte +33 -3
- package/src/editor/ui/UISegmentedControl.svelte +114 -0
- package/src/editor/ui/UITokenSelector.svelte +4 -1
- package/src/editor/ui/VariablesTab.svelte +41 -35
- package/src/editor/ui/palette/OverridesPanel.svelte +14 -37
- package/src/editor/ui/palette/PaletteBase.svelte +3 -3
- package/src/editor/ui/sections/ColumnsSection.svelte +1 -2
- package/src/editor/ui/sections/GradientsSection.svelte +1 -1
- package/src/editor/ui/sections/OverlaysSection.svelte +1 -1
- package/src/editor/ui/sections/ShadowsSection.svelte +1 -1
- package/src/system/components/Button.svelte +2 -2
- package/src/system/components/Card.svelte +29 -1
- package/src/system/components/CollapsibleSection.svelte +25 -2
- package/src/system/components/FloatingTokenTags.css +43 -24
- package/src/system/components/FloatingTokenTags.svelte +88 -137
- package/src/system/components/Notification.svelte +8 -1
- package/src/system/components/SectionDivider.svelte +456 -379
- package/src/system/styles/CONVENTIONS.md +1 -1
- package/src/system/styles/fonts.css +3 -16
- package/src/system/styles/tokens.css +356 -1199
- package/src/system/styles/tokens.generated.css +544 -0
- package/src/editor/component-editor/scaffolding/DividerEditor.svelte +0 -94
- package/src/editor/component-editor/scaffolding/GradientCard.svelte +0 -296
package/dist-plugin/index.js
CHANGED
|
@@ -18,6 +18,410 @@ function sanitizeFileName(name) {
|
|
|
18
18
|
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
+
// src/editor/core/palettes/oklch.ts
|
|
22
|
+
function srgbToLinear(c) {
|
|
23
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
24
|
+
}
|
|
25
|
+
function linearToSrgb(c) {
|
|
26
|
+
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
27
|
+
}
|
|
28
|
+
function hexToLinearRgb(hex) {
|
|
29
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
30
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
31
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
32
|
+
return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
|
|
33
|
+
}
|
|
34
|
+
function linearRgbToHex(r, g, b) {
|
|
35
|
+
const toHex = (c) => {
|
|
36
|
+
const v = Math.round(Math.max(0, Math.min(1, linearToSrgb(c))) * 255);
|
|
37
|
+
return v.toString(16).padStart(2, "0");
|
|
38
|
+
};
|
|
39
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
40
|
+
}
|
|
41
|
+
function linearRgbToOklab(r, g, b) {
|
|
42
|
+
const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
43
|
+
const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
44
|
+
const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
45
|
+
const l = Math.cbrt(l_);
|
|
46
|
+
const m = Math.cbrt(m_);
|
|
47
|
+
const s = Math.cbrt(s_);
|
|
48
|
+
return [
|
|
49
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
50
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
51
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
52
|
+
];
|
|
53
|
+
}
|
|
54
|
+
function oklabToLinearRgb(L, a, b) {
|
|
55
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
56
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
57
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
58
|
+
const l = l_ * l_ * l_;
|
|
59
|
+
const m = m_ * m_ * m_;
|
|
60
|
+
const s = s_ * s_ * s_;
|
|
61
|
+
return [
|
|
62
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
63
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
64
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
function oklabToOklch(L, a, b) {
|
|
68
|
+
const c = Math.sqrt(a * a + b * b);
|
|
69
|
+
let h = Math.atan2(b, a) * 180 / Math.PI;
|
|
70
|
+
if (h < 0) h += 360;
|
|
71
|
+
return { l: L, c, h };
|
|
72
|
+
}
|
|
73
|
+
function oklchToOklab(l, c, h) {
|
|
74
|
+
const hRad = h * Math.PI / 180;
|
|
75
|
+
return [l, c * Math.cos(hRad), c * Math.sin(hRad)];
|
|
76
|
+
}
|
|
77
|
+
function hexToOklch(hex) {
|
|
78
|
+
const [r, g, b] = hexToLinearRgb(hex);
|
|
79
|
+
const [L, a, bVal] = linearRgbToOklab(r, g, b);
|
|
80
|
+
return oklabToOklch(L, a, bVal);
|
|
81
|
+
}
|
|
82
|
+
function oklchToHex(l, c, h) {
|
|
83
|
+
const [L, a, b] = oklchToOklab(l, c, h);
|
|
84
|
+
const [r, g, bVal] = oklabToLinearRgb(L, a, b);
|
|
85
|
+
return linearRgbToHex(r, g, bVal);
|
|
86
|
+
}
|
|
87
|
+
function isInGamut(r, g, b) {
|
|
88
|
+
const eps = 1e-4;
|
|
89
|
+
return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;
|
|
90
|
+
}
|
|
91
|
+
function gamutClamp(l, c, h) {
|
|
92
|
+
if (l <= 0) return { l: 0, c: 0, h };
|
|
93
|
+
if (l >= 1) return { l: 1, c: 0, h };
|
|
94
|
+
const [L, a, b] = oklchToOklab(l, c, h);
|
|
95
|
+
const [r, g, bVal] = oklabToLinearRgb(L, a, b);
|
|
96
|
+
if (isInGamut(r, g, bVal)) return { l, c, h };
|
|
97
|
+
let lo = 0;
|
|
98
|
+
let hi = c;
|
|
99
|
+
for (let i = 0; i < 20; i++) {
|
|
100
|
+
const mid = (lo + hi) / 2;
|
|
101
|
+
const [La, aa, ba] = oklchToOklab(l, mid, h);
|
|
102
|
+
const [rr, gg, bb] = oklabToLinearRgb(La, aa, ba);
|
|
103
|
+
if (isInGamut(rr, gg, bb)) {
|
|
104
|
+
lo = mid;
|
|
105
|
+
} else {
|
|
106
|
+
hi = mid;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return { l, c: lo, h };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// src/editor/ui/curveEngine.ts
|
|
113
|
+
function makeAnchor(x, y, tangentLen = 15) {
|
|
114
|
+
return { x, y, inDx: -tangentLen, inDy: 0, outDx: tangentLen, outDy: 0 };
|
|
115
|
+
}
|
|
116
|
+
function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
|
|
117
|
+
const u = 1 - t, u2 = u * u, u3 = u2 * u;
|
|
118
|
+
const t2 = t * t, t3 = t2 * t;
|
|
119
|
+
return {
|
|
120
|
+
x: u3 * p0x + 3 * u2 * t * c0x + 3 * u * t2 * c1x + t3 * p1x,
|
|
121
|
+
y: u3 * p0y + 3 * u2 * t * c0y + 3 * u * t2 * c1y + t3 * p1y
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
function sampleCurve(anchors, xPos) {
|
|
125
|
+
if (anchors.length === 0) return 0;
|
|
126
|
+
if (anchors.length === 1) return anchors[0].y;
|
|
127
|
+
if (xPos <= anchors[0].x) return anchors[0].y;
|
|
128
|
+
if (xPos >= anchors[anchors.length - 1].x) return anchors[anchors.length - 1].y;
|
|
129
|
+
let seg = 0;
|
|
130
|
+
while (seg < anchors.length - 2 && anchors[seg + 1].x < xPos) seg++;
|
|
131
|
+
const a0 = anchors[seg], a1 = anchors[seg + 1];
|
|
132
|
+
const p0x = a0.x, p0y = a0.y;
|
|
133
|
+
const c0x = a0.x + a0.outDx, c0y = a0.y + a0.outDy;
|
|
134
|
+
const c1x = a1.x + a1.inDx, c1y = a1.y + a1.inDy;
|
|
135
|
+
const p1x = a1.x, p1y = a1.y;
|
|
136
|
+
let lo = 0, hi = 1;
|
|
137
|
+
for (let i = 0; i < 20; i++) {
|
|
138
|
+
const mid = (lo + hi) / 2;
|
|
139
|
+
const pt = evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, mid);
|
|
140
|
+
if (pt.x < xPos) lo = mid;
|
|
141
|
+
else hi = mid;
|
|
142
|
+
}
|
|
143
|
+
return evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, (lo + hi) / 2).y;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// src/editor/core/palettes/paletteDerivation.ts
|
|
147
|
+
var PALETTE_SPECS = [
|
|
148
|
+
{ label: "Neutral", cssNamespace: "neutral", mode: "gray", initialColor: "#808080" },
|
|
149
|
+
{ label: "Alternate", cssNamespace: "alternate", mode: "gray", initialColor: "#808080" },
|
|
150
|
+
{ label: "Background", cssNamespace: "canvas", mode: "chromatic", emptySelector: true, initialColor: "#1a1a2e" },
|
|
151
|
+
{ label: "Brand", cssNamespace: "brand", mode: "chromatic", initialColor: "#c93636" },
|
|
152
|
+
{ label: "Accent", cssNamespace: "accent", mode: "chromatic", initialColor: "#f49e0b" },
|
|
153
|
+
{ label: "Special", cssNamespace: "special", mode: "chromatic", initialColor: "#8b5cf6" },
|
|
154
|
+
{ label: "Info", cssNamespace: "info", mode: "chromatic", initialColor: "#3077e8" },
|
|
155
|
+
{ label: "Success", cssNamespace: "success", mode: "chromatic", initialColor: "#21c45d" },
|
|
156
|
+
{ label: "Warning", cssNamespace: "warning", mode: "chromatic", initialColor: "#e66e1a" },
|
|
157
|
+
{ label: "Danger", cssNamespace: "danger", mode: "chromatic", initialColor: "#e8304f" }
|
|
158
|
+
];
|
|
159
|
+
var PALETTE_STEPS = [
|
|
160
|
+
{ label: "100" },
|
|
161
|
+
{ label: "200" },
|
|
162
|
+
{ label: "300" },
|
|
163
|
+
{ label: "400" },
|
|
164
|
+
{ label: "500" },
|
|
165
|
+
{ label: "600" },
|
|
166
|
+
{ label: "700" },
|
|
167
|
+
{ label: "800" },
|
|
168
|
+
{ label: "850" },
|
|
169
|
+
{ label: "900" },
|
|
170
|
+
{ label: "950" }
|
|
171
|
+
];
|
|
172
|
+
var GRAY_STEPS = [
|
|
173
|
+
{ label: "100" },
|
|
174
|
+
{ label: "200" },
|
|
175
|
+
{ label: "300" },
|
|
176
|
+
{ label: "400" },
|
|
177
|
+
{ label: "500" },
|
|
178
|
+
{ label: "600" },
|
|
179
|
+
{ label: "700" },
|
|
180
|
+
{ label: "800" },
|
|
181
|
+
{ label: "850" },
|
|
182
|
+
{ label: "900" },
|
|
183
|
+
{ label: "950" }
|
|
184
|
+
];
|
|
185
|
+
var SCALES = [
|
|
186
|
+
{
|
|
187
|
+
title: "Surfaces",
|
|
188
|
+
isText: false,
|
|
189
|
+
steps: [
|
|
190
|
+
{ name: "lowest", position: -1 },
|
|
191
|
+
{ name: "lower", position: -2 / 3 },
|
|
192
|
+
{ name: "low", position: -1 / 3 },
|
|
193
|
+
{ name: "default", position: 0 },
|
|
194
|
+
{ name: "high", position: 1 / 3 },
|
|
195
|
+
{ name: "higher", position: 2 / 3 },
|
|
196
|
+
{ name: "highest", position: 1 }
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
title: "Borders",
|
|
201
|
+
isText: false,
|
|
202
|
+
steps: [
|
|
203
|
+
{ name: "faint", position: -1 },
|
|
204
|
+
{ name: "subtle", position: -0.5 },
|
|
205
|
+
{ name: "default", position: 0 },
|
|
206
|
+
{ name: "medium", position: 0.5 },
|
|
207
|
+
{ name: "strong", position: 1 }
|
|
208
|
+
]
|
|
209
|
+
},
|
|
210
|
+
{
|
|
211
|
+
title: "Text",
|
|
212
|
+
isText: true,
|
|
213
|
+
steps: [
|
|
214
|
+
{ name: "primary", position: 0 },
|
|
215
|
+
{ name: "secondary", position: 0 },
|
|
216
|
+
{ name: "tertiary", position: 0 },
|
|
217
|
+
{ name: "muted", position: 0 },
|
|
218
|
+
{ name: "disabled", position: 0 }
|
|
219
|
+
]
|
|
220
|
+
}
|
|
221
|
+
];
|
|
222
|
+
var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
|
|
223
|
+
var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
|
|
224
|
+
var DEFAULT_GRAY_LIGHTNESS = () => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
|
|
225
|
+
var DEFAULT_GRAY_SATURATION = () => [makeAnchor(0, 20, 30), makeAnchor(100, 20, 30)];
|
|
226
|
+
var DEFAULT_TINT_CHROMA = 0.04;
|
|
227
|
+
var defaultScaleCurves = {
|
|
228
|
+
Surfaces: {
|
|
229
|
+
lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
|
|
230
|
+
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
231
|
+
},
|
|
232
|
+
Borders: {
|
|
233
|
+
lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
|
|
234
|
+
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
235
|
+
},
|
|
236
|
+
Text: {
|
|
237
|
+
lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
|
|
238
|
+
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
function paletteStepKey(label) {
|
|
242
|
+
return `Palette-${label}`;
|
|
243
|
+
}
|
|
244
|
+
function grayStepKey(label) {
|
|
245
|
+
return `gray-${label}`;
|
|
246
|
+
}
|
|
247
|
+
function stepKey(scaleTitle, stepName) {
|
|
248
|
+
return `${scaleTitle}-${stepName}`;
|
|
249
|
+
}
|
|
250
|
+
function stepIndexToX(index, total) {
|
|
251
|
+
return total > 1 ? index / (total - 1) * 100 : 50;
|
|
252
|
+
}
|
|
253
|
+
function computePaletteColor(index, base, lightnessCurve, saturationCurve, curveOffset) {
|
|
254
|
+
const { c: baseC, h } = hexToOklch(base);
|
|
255
|
+
const xPos = stepIndexToX(index, PALETTE_STEPS.length);
|
|
256
|
+
const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
|
|
257
|
+
const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
|
|
258
|
+
const targetC = baseC * satMul;
|
|
259
|
+
const clamped = gamutClamp(targetL, targetC, h);
|
|
260
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
261
|
+
}
|
|
262
|
+
function computeGrayColor(index, hue, chroma, grayLightnessCurve, graySaturationCurve, curveOffset) {
|
|
263
|
+
const xPos = stepIndexToX(index, GRAY_STEPS.length);
|
|
264
|
+
const lOff = curveOffset["gray-lightness"] ?? 0;
|
|
265
|
+
const sOff = curveOffset["gray-saturation"] ?? 0;
|
|
266
|
+
const targetL = Math.max(0, Math.min(100, sampleCurve(grayLightnessCurve, xPos) + lOff)) / 100;
|
|
267
|
+
const satMul = Math.max(0, Math.min(2, (sampleCurve(graySaturationCurve, xPos) + sOff) / 100));
|
|
268
|
+
const targetC = chroma * satMul;
|
|
269
|
+
const clamped = gamutClamp(targetL, targetC, hue);
|
|
270
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
271
|
+
}
|
|
272
|
+
function computeDerivedColor(step, base, scaleTitle, scaleCurves, curveOffset) {
|
|
273
|
+
const scale = SCALES.find((s) => s.title === scaleTitle);
|
|
274
|
+
const idx = scale.steps.indexOf(step);
|
|
275
|
+
const xPos = stepIndexToX(idx, scale.steps.length);
|
|
276
|
+
const defs = defaultScaleCurves[scaleTitle];
|
|
277
|
+
const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
|
|
278
|
+
const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
|
|
279
|
+
const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
|
|
280
|
+
const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
|
|
281
|
+
const { l: baseL, c: baseC, h: baseH } = hexToOklch(base);
|
|
282
|
+
let targetL;
|
|
283
|
+
if (scale.isText) {
|
|
284
|
+
const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
|
|
285
|
+
targetL = Math.max(0, Math.min(1, baseL * lMul));
|
|
286
|
+
} else {
|
|
287
|
+
targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
|
|
288
|
+
}
|
|
289
|
+
const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
|
|
290
|
+
const targetC = baseC * satMul;
|
|
291
|
+
const clamped = gamutClamp(targetL, targetC, baseH);
|
|
292
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
293
|
+
}
|
|
294
|
+
function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
|
|
295
|
+
if (cssNamespace === null) return null;
|
|
296
|
+
if (scaleTitle === "Surfaces") {
|
|
297
|
+
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
298
|
+
return cssNamespace === "neutral" ? `--surface-neutral${suffix}` : `--surface-${cssNamespace}${suffix}`;
|
|
299
|
+
}
|
|
300
|
+
if (scaleTitle === "Borders") {
|
|
301
|
+
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
302
|
+
return cssNamespace === "neutral" ? `--border-neutral${suffix}` : `--border-${cssNamespace}${suffix}`;
|
|
303
|
+
}
|
|
304
|
+
if (scaleTitle === "Text") {
|
|
305
|
+
if (cssNamespace === "neutral") return `--text-${stepName}`;
|
|
306
|
+
return stepName === "primary" ? `--text-${cssNamespace}` : `--text-${cssNamespace}-${stepName}`;
|
|
307
|
+
}
|
|
308
|
+
return null;
|
|
309
|
+
}
|
|
310
|
+
function derivePaletteVars(spec, config) {
|
|
311
|
+
const out = {};
|
|
312
|
+
if (!config) return out;
|
|
313
|
+
const baseColor = config.baseColor ?? spec.initialColor;
|
|
314
|
+
const overrides = config.overrides ?? {};
|
|
315
|
+
const curveOffset = config.curveOffset ?? {};
|
|
316
|
+
const scaleCurves = config.scaleCurves ?? {};
|
|
317
|
+
let baseForScales;
|
|
318
|
+
if (spec.mode === "gray") {
|
|
319
|
+
const grayLightnessCurve = config.grayLightnessCurve ?? DEFAULT_GRAY_LIGHTNESS();
|
|
320
|
+
const graySaturationCurve = config.graySaturationCurve ?? DEFAULT_GRAY_SATURATION();
|
|
321
|
+
const tintHue = config.tintHue ?? 240;
|
|
322
|
+
const tintChroma = config.tintChroma ?? DEFAULT_TINT_CHROMA;
|
|
323
|
+
let gray500 = "#808080";
|
|
324
|
+
GRAY_STEPS.forEach((step, index) => {
|
|
325
|
+
const k = grayStepKey(step.label);
|
|
326
|
+
const hex = computeGrayColor(index, tintHue, tintChroma, grayLightnessCurve, graySaturationCurve, curveOffset);
|
|
327
|
+
const effective = k in overrides ? overrides[k] : hex;
|
|
328
|
+
out[`--color-${spec.cssNamespace}-${step.label}`] = effective;
|
|
329
|
+
if (step.label === "500") gray500 = hex;
|
|
330
|
+
});
|
|
331
|
+
baseForScales = gray500;
|
|
332
|
+
} else {
|
|
333
|
+
const lightnessCurve = config.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS();
|
|
334
|
+
const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
|
|
335
|
+
PALETTE_STEPS.forEach((ps, index) => {
|
|
336
|
+
const k = paletteStepKey(ps.label);
|
|
337
|
+
const hex = computePaletteColor(index, baseColor, lightnessCurve, saturationCurve, curveOffset);
|
|
338
|
+
const effective = k in overrides ? overrides[k] : hex;
|
|
339
|
+
out[`--color-${spec.cssNamespace}-${ps.label}`] = effective;
|
|
340
|
+
});
|
|
341
|
+
baseForScales = baseColor;
|
|
342
|
+
}
|
|
343
|
+
for (const scale of SCALES) {
|
|
344
|
+
for (const step of scale.steps) {
|
|
345
|
+
const k = stepKey(scale.title, step.name);
|
|
346
|
+
const hex = k in overrides ? overrides[k] : computeDerivedColor(step, baseForScales, scale.title, scaleCurves, curveOffset);
|
|
347
|
+
const varName = scaleToCssVar(scale.title, step.name, spec.cssNamespace);
|
|
348
|
+
if (varName) out[varName] = hex;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (spec.emptySelector) {
|
|
352
|
+
const emptyMode = config.emptyMode ?? "solid";
|
|
353
|
+
const emptyStep = config.emptyStep ?? "850";
|
|
354
|
+
const gradientStyle = config.gradientStyle ?? "linear";
|
|
355
|
+
const gradientAngle = config.gradientAngle ?? 180;
|
|
356
|
+
const gradientReverse = config.gradientReverse ?? false;
|
|
357
|
+
const gradientSize = config.gradientSize ?? "page";
|
|
358
|
+
const gradientStops = config.gradientStops ?? [
|
|
359
|
+
{ position: 0, paletteLabel: "800" },
|
|
360
|
+
{ position: 100, paletteLabel: "950" }
|
|
361
|
+
];
|
|
362
|
+
if (emptyMode === "solid") {
|
|
363
|
+
const stepHex = out[`--color-${spec.cssNamespace}-${emptyStep}`];
|
|
364
|
+
if (stepHex) out["--page-bg"] = stepHex;
|
|
365
|
+
out["--page-bg-attachment"] = "scroll";
|
|
366
|
+
} else {
|
|
367
|
+
const sortedStops = [...gradientStops].sort(
|
|
368
|
+
(a, b) => gradientReverse ? b.position - a.position : a.position - b.position
|
|
369
|
+
);
|
|
370
|
+
const stopsCss = sortedStops.map((s) => `${out[`--color-${spec.cssNamespace}-${s.paletteLabel}`] ?? "#000000"} ${s.position}%`).join(", ");
|
|
371
|
+
let gradient;
|
|
372
|
+
switch (gradientStyle) {
|
|
373
|
+
case "radial":
|
|
374
|
+
gradient = `radial-gradient(circle, ${stopsCss})`;
|
|
375
|
+
break;
|
|
376
|
+
case "conic":
|
|
377
|
+
gradient = `conic-gradient(from ${gradientAngle}deg, ${stopsCss})`;
|
|
378
|
+
break;
|
|
379
|
+
default:
|
|
380
|
+
gradient = `linear-gradient(${gradientAngle}deg, ${stopsCss})`;
|
|
381
|
+
}
|
|
382
|
+
out["--page-bg"] = gradient;
|
|
383
|
+
out["--page-bg-attachment"] = gradientSize === "window" ? "fixed" : "scroll";
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
return out;
|
|
387
|
+
}
|
|
388
|
+
function palettesToVars(palettes) {
|
|
389
|
+
const out = {};
|
|
390
|
+
for (const spec of PALETTE_SPECS) {
|
|
391
|
+
Object.assign(out, derivePaletteVars(spec, palettes[spec.label]));
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|
|
395
|
+
var HEX_RE = /^#[0-9a-f]{6}$/i;
|
|
396
|
+
function reconcilePalettesFromCssVars(palettes, cssVars) {
|
|
397
|
+
const next = structuredClone(palettes);
|
|
398
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
399
|
+
const snapped = /* @__PURE__ */ new Set();
|
|
400
|
+
for (const spec of PALETTE_SPECS) {
|
|
401
|
+
const current = next[spec.label];
|
|
402
|
+
if (current === void 0) continue;
|
|
403
|
+
if (current._imported === true) {
|
|
404
|
+
const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
|
|
405
|
+
if (anchorHex && HEX_RE.test(anchorHex.trim())) {
|
|
406
|
+
const hex = anchorHex.trim();
|
|
407
|
+
if (spec.mode === "gray") {
|
|
408
|
+
const { c, h } = hexToOklch(hex);
|
|
409
|
+
next[spec.label] = { ...current, tintHue: h, tintChroma: c, _imported: false };
|
|
410
|
+
} else {
|
|
411
|
+
next[spec.label] = { ...current, baseColor: hex, _imported: false };
|
|
412
|
+
}
|
|
413
|
+
snapped.add(spec.label);
|
|
414
|
+
} else {
|
|
415
|
+
next[spec.label] = { ...current, _imported: false };
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
|
|
419
|
+
consumed.add(k);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return { palettes: next, consumed, snapped };
|
|
423
|
+
}
|
|
424
|
+
|
|
21
425
|
// vite-plugin/files/versionedFileResourceServer.ts
|
|
22
426
|
import fs from "fs";
|
|
23
427
|
import path from "path";
|
|
@@ -106,6 +510,16 @@ async function dispatch(req, res, routes) {
|
|
|
106
510
|
return false;
|
|
107
511
|
}
|
|
108
512
|
|
|
513
|
+
// vite-plugin/files/nameAllocator.ts
|
|
514
|
+
function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
|
|
515
|
+
if (!exists(baseName)) return baseName;
|
|
516
|
+
for (let i = 2; i < maxAttempts; i++) {
|
|
517
|
+
const candidate = `${baseName}-${i}`;
|
|
518
|
+
if (!exists(candidate)) return candidate;
|
|
519
|
+
}
|
|
520
|
+
throw new Error(`Could not allocate a non-colliding name for "${baseName}"`);
|
|
521
|
+
}
|
|
522
|
+
|
|
109
523
|
// vite-plugin/themeFileApi.ts
|
|
110
524
|
import { fileURLToPath } from "url";
|
|
111
525
|
var PKG_VERSION = (() => {
|
|
@@ -128,6 +542,7 @@ var PKG_VERSION = (() => {
|
|
|
128
542
|
function themeFileApi(opts) {
|
|
129
543
|
const THEMES_DIR = path2.resolve(opts.themesDir);
|
|
130
544
|
const CSS_PATH = path2.resolve(opts.tokensCssPath);
|
|
545
|
+
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ? path2.resolve(opts.tokensGeneratedCssPath) : path2.join(path2.dirname(CSS_PATH), "tokens.generated.css");
|
|
131
546
|
const FONTS_CSS_PATH = opts.fontsCssPath ? path2.resolve(opts.fontsCssPath) : path2.join(path2.dirname(CSS_PATH), "fonts.css");
|
|
132
547
|
const API_BASE = opts.apiBase ?? "/api";
|
|
133
548
|
const COMPONENT_CONFIGS_DIR = opts.componentConfigsDir ? path2.resolve(opts.componentConfigsDir) : path2.resolve("component-configs");
|
|
@@ -174,6 +589,17 @@ function themeFileApi(opts) {
|
|
|
174
589
|
res.setHeader("Content-Type", "application/json");
|
|
175
590
|
res.end(JSON.stringify(data));
|
|
176
591
|
}
|
|
592
|
+
function normalizeTheme(theme) {
|
|
593
|
+
if (!theme || typeof theme !== "object") return theme;
|
|
594
|
+
const palettes = theme.editorConfigs ?? {};
|
|
595
|
+
const cssVars = theme.cssVariables ?? {};
|
|
596
|
+
const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
|
|
597
|
+
const nextCssVars = {};
|
|
598
|
+
for (const [k, v] of Object.entries(cssVars)) {
|
|
599
|
+
if (!consumed.has(k)) nextCssVars[k] = v;
|
|
600
|
+
}
|
|
601
|
+
return { ...theme, editorConfigs: nextPalettes, cssVariables: nextCssVars };
|
|
602
|
+
}
|
|
177
603
|
const SYSTEM_CASCADES_SSR = {
|
|
178
604
|
"system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
179
605
|
"system-ui-serif": 'Georgia, "Times New Roman", serif',
|
|
@@ -205,42 +631,78 @@ function themeFileApi(opts) {
|
|
|
205
631
|
}
|
|
206
632
|
return out;
|
|
207
633
|
}
|
|
208
|
-
function
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
634
|
+
function regenerateTokensCss() {
|
|
635
|
+
const lines = [];
|
|
636
|
+
lines.push("/* Generated by themeFileApi from the production theme and component configs. Do not edit. */");
|
|
637
|
+
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
638
|
+
lines.push("");
|
|
639
|
+
const productionThemeName = themesResource.getProductionName();
|
|
640
|
+
const themePath = path2.join(THEMES_DIR, `${productionThemeName}.json`);
|
|
641
|
+
let themeVarCount = 0;
|
|
642
|
+
if (fs2.existsSync(themePath)) {
|
|
643
|
+
const themeData = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
|
|
644
|
+
const cssVars = { ...themeData.cssVariables || {} };
|
|
645
|
+
Object.assign(cssVars, palettesToVars(themeData.editorConfigs ?? {}));
|
|
646
|
+
const resolvedFontVars = resolveFontStacks(themeData);
|
|
647
|
+
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
648
|
+
cssVars[name] = value;
|
|
649
|
+
}
|
|
650
|
+
themeVarCount = Object.keys(cssVars).length;
|
|
651
|
+
if (themeVarCount > 0) {
|
|
652
|
+
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
653
|
+
lines.push(":root:root {");
|
|
654
|
+
for (const [name, value] of Object.entries(cssVars)) {
|
|
655
|
+
lines.push(` ${name}: ${value};`);
|
|
226
656
|
}
|
|
227
|
-
|
|
657
|
+
lines.push("}");
|
|
658
|
+
lines.push("");
|
|
228
659
|
}
|
|
229
|
-
);
|
|
230
|
-
let finalContent = updatedContent;
|
|
231
|
-
if (remaining.size > 0) {
|
|
232
|
-
const newVars = [...remaining].map((name) => ` ${name}: ${cssVars[name]};`).join("\n");
|
|
233
|
-
finalContent = finalContent.replace(
|
|
234
|
-
/\n\}(\s*)$/,
|
|
235
|
-
`
|
|
236
|
-
|
|
237
|
-
/* Token additions */
|
|
238
|
-
${newVars}
|
|
239
|
-
}$1`
|
|
240
|
-
);
|
|
241
660
|
}
|
|
242
|
-
|
|
243
|
-
|
|
661
|
+
let componentOverrideCount = 0;
|
|
662
|
+
if (fs2.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
663
|
+
const blocks = [];
|
|
664
|
+
for (const comp of listComponentNames()) {
|
|
665
|
+
const prod = componentResource(comp).getProductionName();
|
|
666
|
+
if (prod === "default") continue;
|
|
667
|
+
const prodCfg = readComponentConfig(comp, prod);
|
|
668
|
+
const defaultCfg = readComponentConfig(comp, "default");
|
|
669
|
+
if (!prodCfg || !defaultCfg) continue;
|
|
670
|
+
const overrides = [];
|
|
671
|
+
const defaultAliases = defaultCfg.aliases ?? {};
|
|
672
|
+
for (const [varName, semanticValue] of Object.entries(prodCfg.aliases ?? {})) {
|
|
673
|
+
if (!aliasValuesEqual(defaultAliases[varName], semanticValue)) {
|
|
674
|
+
overrides.push([varName, semanticValue]);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
if (overrides.length === 0) continue;
|
|
678
|
+
const block = [` /* ${comp} (${prod}) */`];
|
|
679
|
+
for (const [varName, semanticValue] of overrides) {
|
|
680
|
+
block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
|
|
681
|
+
}
|
|
682
|
+
blocks.push(block);
|
|
683
|
+
componentOverrideCount += overrides.length;
|
|
684
|
+
}
|
|
685
|
+
if (blocks.length > 0) {
|
|
686
|
+
lines.push("/* Component aliases (production configs differing from defaults) */");
|
|
687
|
+
lines.push(":root:root {");
|
|
688
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
689
|
+
if (i > 0) lines.push("");
|
|
690
|
+
lines.push(...blocks[i]);
|
|
691
|
+
}
|
|
692
|
+
lines.push("}");
|
|
693
|
+
lines.push("");
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
if (!fs2.existsSync(path2.dirname(GENERATED_CSS_PATH))) {
|
|
697
|
+
fs2.mkdirSync(path2.dirname(GENERATED_CSS_PATH), { recursive: true });
|
|
698
|
+
}
|
|
699
|
+
fs2.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
|
|
700
|
+
console.log(
|
|
701
|
+
`[regenerateTokensCss] Wrote ${path2.basename(GENERATED_CSS_PATH)} (${themeVarCount} theme vars, ${componentOverrideCount} component overrides)`
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
function syncTokensToCss(_fileName) {
|
|
705
|
+
regenerateTokensCss();
|
|
244
706
|
}
|
|
245
707
|
function syncFontsToCss(fileName) {
|
|
246
708
|
const themePath = path2.join(THEMES_DIR, `${fileName}.json`);
|
|
@@ -252,15 +714,20 @@ ${newVars}
|
|
|
252
714
|
lines.push("/* Generated from the production theme by syncFontsToCss. Do not edit. */");
|
|
253
715
|
lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
|
|
254
716
|
lines.push("");
|
|
255
|
-
|
|
717
|
+
const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
|
|
718
|
+
const faceSources = sources.filter((s) => s.kind === "font-face" && s.cssText);
|
|
719
|
+
for (const source of urlSources) {
|
|
256
720
|
const familyNames = source.families.map((f) => f.name).join(", ");
|
|
257
721
|
const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
|
|
258
722
|
lines.push(`/* ${label} */`);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
723
|
+
lines.push(`@import url('${source.url}');`);
|
|
724
|
+
lines.push("");
|
|
725
|
+
}
|
|
726
|
+
for (const source of faceSources) {
|
|
727
|
+
const familyNames = source.families.map((f) => f.name).join(", ");
|
|
728
|
+
const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
|
|
729
|
+
lines.push(`/* ${label} */`);
|
|
730
|
+
lines.push(source.cssText);
|
|
264
731
|
lines.push("");
|
|
265
732
|
}
|
|
266
733
|
const content = lines.join("\n");
|
|
@@ -400,6 +867,33 @@ ${newVars}
|
|
|
400
867
|
fs2.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
401
868
|
return true;
|
|
402
869
|
}
|
|
870
|
+
function formatAliasGradient(v) {
|
|
871
|
+
const stopColor = (s) => {
|
|
872
|
+
const base = s.color.startsWith("--") ? `var(${s.color})` : s.color;
|
|
873
|
+
const opacity = s.opacity ?? 100;
|
|
874
|
+
return opacity >= 100 ? base : `color-mix(in srgb, ${base} ${opacity}%, transparent)`;
|
|
875
|
+
};
|
|
876
|
+
if (v.type === "none") return "transparent";
|
|
877
|
+
if (v.type === "solid") {
|
|
878
|
+
const first = v.stops[0];
|
|
879
|
+
if (!first) return "transparent";
|
|
880
|
+
return stopColor(first);
|
|
881
|
+
}
|
|
882
|
+
const stops = v.stops.map((s) => `${stopColor(s)} ${s.position}%`).join(", ");
|
|
883
|
+
if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
|
|
884
|
+
const radial = v.radius && v.radius > 0 ? `circle ${v.radius}px at center` : "circle";
|
|
885
|
+
return `radial-gradient(${radial}, ${stops})`;
|
|
886
|
+
}
|
|
887
|
+
function aliasValueToCss(v) {
|
|
888
|
+
if (typeof v === "string") return v.startsWith("--") ? `var(${v})` : v;
|
|
889
|
+
return formatAliasGradient(v.value);
|
|
890
|
+
}
|
|
891
|
+
function aliasValuesEqual(a, b) {
|
|
892
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
893
|
+
if (typeof a === "string" && typeof b === "string") return a === b;
|
|
894
|
+
if (typeof a !== typeof b) return false;
|
|
895
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
896
|
+
}
|
|
403
897
|
function readComponentConfig(comp, name) {
|
|
404
898
|
const filePath = componentResource(comp).filePath(name);
|
|
405
899
|
if (!fs2.existsSync(filePath)) return null;
|
|
@@ -409,54 +903,8 @@ ${newVars}
|
|
|
409
903
|
return null;
|
|
410
904
|
}
|
|
411
905
|
}
|
|
412
|
-
const COMPONENT_OVERRIDES_START = "/* component-aliases:start */";
|
|
413
|
-
const COMPONENT_OVERRIDES_END = "/* component-aliases:end */";
|
|
414
906
|
function syncComponentsToCss() {
|
|
415
|
-
|
|
416
|
-
if (!fs2.existsSync(COMPONENT_CONFIGS_DIR)) return;
|
|
417
|
-
const lines = [];
|
|
418
|
-
const components = listComponentNames();
|
|
419
|
-
for (const comp of components) {
|
|
420
|
-
const prod = componentResource(comp).getProductionName();
|
|
421
|
-
if (prod === "default") continue;
|
|
422
|
-
const prodCfg = readComponentConfig(comp, prod);
|
|
423
|
-
const defaultCfg = readComponentConfig(comp, "default");
|
|
424
|
-
if (!prodCfg || !defaultCfg) continue;
|
|
425
|
-
const overrides = [];
|
|
426
|
-
for (const [varName, semanticName] of Object.entries(prodCfg.aliases ?? {})) {
|
|
427
|
-
if ((defaultCfg.aliases ?? {})[varName] !== semanticName) {
|
|
428
|
-
overrides.push([varName, semanticName]);
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
if (overrides.length === 0) continue;
|
|
432
|
-
lines.push(` /* ${comp} (${prod}) */`);
|
|
433
|
-
for (const [varName, semanticName] of overrides) {
|
|
434
|
-
lines.push(` ${varName}: var(${semanticName});`);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
const block = lines.length > 0 ? `
|
|
438
|
-
|
|
439
|
-
${COMPONENT_OVERRIDES_START}
|
|
440
|
-
:root:root {
|
|
441
|
-
${lines.join("\n")}
|
|
442
|
-
}
|
|
443
|
-
${COMPONENT_OVERRIDES_END}
|
|
444
|
-
` : "";
|
|
445
|
-
let cssContent = fs2.readFileSync(CSS_PATH, "utf-8");
|
|
446
|
-
const startIdx = cssContent.indexOf(COMPONENT_OVERRIDES_START);
|
|
447
|
-
const endIdx = cssContent.indexOf(COMPONENT_OVERRIDES_END);
|
|
448
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
449
|
-
let stripStart = startIdx;
|
|
450
|
-
while (stripStart > 0 && cssContent[stripStart - 1] === "\n") stripStart--;
|
|
451
|
-
const after = cssContent.slice(endIdx + COMPONENT_OVERRIDES_END.length);
|
|
452
|
-
cssContent = cssContent.slice(0, stripStart) + (block || "\n") + after.replace(/^\n+/, "");
|
|
453
|
-
} else if (block) {
|
|
454
|
-
cssContent = cssContent.replace(/\n*$/, "") + block;
|
|
455
|
-
}
|
|
456
|
-
fs2.writeFileSync(CSS_PATH, cssContent);
|
|
457
|
-
console.log(
|
|
458
|
-
`[syncComponentsToCss] Wrote ${lines.filter((l) => !l.trim().startsWith("/*")).length} alias override(s) to tokens.css`
|
|
459
|
-
);
|
|
907
|
+
regenerateTokensCss();
|
|
460
908
|
}
|
|
461
909
|
const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
462
910
|
const THEMES_ROUTE = `${API_BASE}/themes`;
|
|
@@ -471,6 +919,8 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
471
919
|
const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
|
|
472
920
|
const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
|
|
473
921
|
const MANIFEST_APPLY_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/apply$`);
|
|
922
|
+
const MANIFEST_EXPORT_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/export$`);
|
|
923
|
+
const MANIFEST_IMPORT_ROUTE = `${API_BASE}/manifests/import`;
|
|
474
924
|
const MANIFEST_BY_NAME_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)$`);
|
|
475
925
|
async function handleListThemes(_ctx) {
|
|
476
926
|
const activeFile = themesResource.getActiveName();
|
|
@@ -494,7 +944,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
494
944
|
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
495
945
|
return;
|
|
496
946
|
}
|
|
497
|
-
const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
947
|
+
const data = normalizeTheme(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
|
|
498
948
|
data._fileName = activeFile;
|
|
499
949
|
jsonResponse(res, 200, data);
|
|
500
950
|
}
|
|
@@ -515,7 +965,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
515
965
|
jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
|
|
516
966
|
return;
|
|
517
967
|
}
|
|
518
|
-
const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
968
|
+
const data = normalizeTheme(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
|
|
519
969
|
jsonResponse(res, 200, {
|
|
520
970
|
fileName: prodFile,
|
|
521
971
|
name: data.name || prodFile,
|
|
@@ -558,7 +1008,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
558
1008
|
jsonResponse(res, 404, { error: "Not found" });
|
|
559
1009
|
return;
|
|
560
1010
|
}
|
|
561
|
-
const data = JSON.parse(fs2.readFileSync(filePath, "utf-8"));
|
|
1011
|
+
const data = normalizeTheme(JSON.parse(fs2.readFileSync(filePath, "utf-8")));
|
|
562
1012
|
data._fileName = fileName;
|
|
563
1013
|
jsonResponse(res, 200, data);
|
|
564
1014
|
return;
|
|
@@ -878,11 +1328,15 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
878
1328
|
apply.push([comp, sanitized]);
|
|
879
1329
|
}
|
|
880
1330
|
themesResource.setActiveName(themeName);
|
|
881
|
-
|
|
1331
|
+
themesResource.setProductionName(themeName);
|
|
1332
|
+
syncTokensToCss(themeName);
|
|
1333
|
+
syncFontsToCss(themeName);
|
|
1334
|
+
const themeData = normalizeTheme(JSON.parse(fs2.readFileSync(themePath, "utf-8")));
|
|
882
1335
|
themeData._fileName = themeName;
|
|
883
1336
|
for (const [comp, configFile] of apply) {
|
|
884
1337
|
const r = componentResource(comp);
|
|
885
1338
|
r.setActiveName(configFile);
|
|
1339
|
+
r.setProductionName(configFile);
|
|
886
1340
|
const cfg = readComponentConfig(comp, configFile);
|
|
887
1341
|
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
|
|
888
1342
|
}
|
|
@@ -892,6 +1346,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
892
1346
|
const cfg = readComponentConfig(comp, activeName);
|
|
893
1347
|
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
|
|
894
1348
|
}
|
|
1349
|
+
syncComponentsToCss();
|
|
895
1350
|
manifestsResource.setActiveName(fileName);
|
|
896
1351
|
jsonResponse(res, 200, {
|
|
897
1352
|
ok: true,
|
|
@@ -900,6 +1355,157 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
900
1355
|
componentConfigs: resolvedConfigs
|
|
901
1356
|
});
|
|
902
1357
|
}
|
|
1358
|
+
async function handleExportManifest({ params, res }) {
|
|
1359
|
+
const [fileName] = params;
|
|
1360
|
+
const manifestPath = manifestsResource.filePath(fileName);
|
|
1361
|
+
if (!fs2.existsSync(manifestPath)) {
|
|
1362
|
+
jsonResponse(res, 404, { error: "Manifest not found" });
|
|
1363
|
+
return;
|
|
1364
|
+
}
|
|
1365
|
+
const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
|
|
1366
|
+
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
1367
|
+
const themePath = themesResource.filePath(themeName);
|
|
1368
|
+
if (!fs2.existsSync(themePath)) {
|
|
1369
|
+
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
1370
|
+
return;
|
|
1371
|
+
}
|
|
1372
|
+
const theme = JSON.parse(fs2.readFileSync(themePath, "utf-8"));
|
|
1373
|
+
const knownComponents = new Set(listComponentNames());
|
|
1374
|
+
const componentConfigs = {};
|
|
1375
|
+
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
1376
|
+
if (!knownComponents.has(comp)) continue;
|
|
1377
|
+
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1378
|
+
if (sanitized === "default") continue;
|
|
1379
|
+
const cfgPath = componentResource(comp).filePath(sanitized);
|
|
1380
|
+
if (!fs2.existsSync(cfgPath)) {
|
|
1381
|
+
jsonResponse(res, 422, {
|
|
1382
|
+
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1383
|
+
});
|
|
1384
|
+
return;
|
|
1385
|
+
}
|
|
1386
|
+
const cfg = JSON.parse(fs2.readFileSync(cfgPath, "utf-8"));
|
|
1387
|
+
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
1388
|
+
}
|
|
1389
|
+
const bundle = {
|
|
1390
|
+
kind: "manifest-bundle",
|
|
1391
|
+
schemaVersion: 1,
|
|
1392
|
+
liveTokensVersion: PKG_VERSION,
|
|
1393
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1394
|
+
manifest,
|
|
1395
|
+
theme,
|
|
1396
|
+
componentConfigs
|
|
1397
|
+
};
|
|
1398
|
+
res.statusCode = 200;
|
|
1399
|
+
res.setHeader("Content-Type", "application/json");
|
|
1400
|
+
res.setHeader(
|
|
1401
|
+
"Content-Disposition",
|
|
1402
|
+
`attachment; filename="${fileName}.bundle.json"`
|
|
1403
|
+
);
|
|
1404
|
+
res.end(JSON.stringify(bundle, null, 2));
|
|
1405
|
+
}
|
|
1406
|
+
function nextAvailableName2(resourceFilePath, baseName) {
|
|
1407
|
+
return nextAvailableName(
|
|
1408
|
+
(n) => fs2.existsSync(resourceFilePath(n)),
|
|
1409
|
+
sanitizeFileName(baseName)
|
|
1410
|
+
);
|
|
1411
|
+
}
|
|
1412
|
+
async function handleImportManifest({ req, res }) {
|
|
1413
|
+
let bundle;
|
|
1414
|
+
try {
|
|
1415
|
+
bundle = JSON.parse(await readBody(req));
|
|
1416
|
+
} catch {
|
|
1417
|
+
jsonResponse(res, 400, { error: "Body is not valid JSON" });
|
|
1418
|
+
return;
|
|
1419
|
+
}
|
|
1420
|
+
if (!bundle || bundle.kind !== "manifest-bundle") {
|
|
1421
|
+
jsonResponse(res, 400, {
|
|
1422
|
+
error: "Not a manifest bundle (kind discriminator missing or wrong)"
|
|
1423
|
+
});
|
|
1424
|
+
return;
|
|
1425
|
+
}
|
|
1426
|
+
if (bundle.schemaVersion !== 1) {
|
|
1427
|
+
jsonResponse(res, 400, {
|
|
1428
|
+
error: `Unsupported bundle schemaVersion: ${bundle.schemaVersion}`
|
|
1429
|
+
});
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
if (!bundle.manifest || !bundle.theme || !bundle.componentConfigs) {
|
|
1433
|
+
jsonResponse(res, 400, { error: "Bundle missing manifest / theme / componentConfigs" });
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
const renames = {};
|
|
1437
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1438
|
+
const originalThemeName = sanitizeFileName(bundle.manifest.theme || "default");
|
|
1439
|
+
const finalThemeName = nextAvailableName2(
|
|
1440
|
+
(n) => themesResource.filePath(n),
|
|
1441
|
+
originalThemeName
|
|
1442
|
+
);
|
|
1443
|
+
if (finalThemeName !== originalThemeName) {
|
|
1444
|
+
renames[`theme:${originalThemeName}`] = finalThemeName;
|
|
1445
|
+
}
|
|
1446
|
+
const themeBody = { ...bundle.theme };
|
|
1447
|
+
themeBody.updatedAt = now;
|
|
1448
|
+
if (!themeBody.createdAt) themeBody.createdAt = now;
|
|
1449
|
+
themesResource.ensureDir();
|
|
1450
|
+
fs2.writeFileSync(themesResource.filePath(finalThemeName), JSON.stringify(themeBody, null, 2));
|
|
1451
|
+
const knownComponents = new Set(listComponentNames());
|
|
1452
|
+
const componentRenames = {};
|
|
1453
|
+
for (const [key, cfgValue] of Object.entries(bundle.componentConfigs)) {
|
|
1454
|
+
const [comp, originalName] = key.split("/");
|
|
1455
|
+
if (!comp || !originalName) continue;
|
|
1456
|
+
if (!knownComponents.has(comp)) continue;
|
|
1457
|
+
const r = componentResource(comp);
|
|
1458
|
+
const finalName = nextAvailableName2(
|
|
1459
|
+
(n) => r.filePath(n),
|
|
1460
|
+
originalName
|
|
1461
|
+
);
|
|
1462
|
+
if (finalName !== originalName) {
|
|
1463
|
+
renames[`componentConfig:${comp}/${originalName}`] = finalName;
|
|
1464
|
+
}
|
|
1465
|
+
componentRenames[`${comp}/${originalName}`] = finalName;
|
|
1466
|
+
const cfgBody = { ...cfgValue };
|
|
1467
|
+
cfgBody.component = comp;
|
|
1468
|
+
cfgBody.name = finalName;
|
|
1469
|
+
cfgBody.updatedAt = now;
|
|
1470
|
+
if (!cfgBody.createdAt) cfgBody.createdAt = now;
|
|
1471
|
+
r.ensureDir();
|
|
1472
|
+
fs2.writeFileSync(r.filePath(finalName), JSON.stringify(cfgBody, null, 2));
|
|
1473
|
+
}
|
|
1474
|
+
const rewrittenManifest = {
|
|
1475
|
+
...bundle.manifest,
|
|
1476
|
+
theme: finalThemeName,
|
|
1477
|
+
componentConfigs: {}
|
|
1478
|
+
};
|
|
1479
|
+
for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
|
|
1480
|
+
const original = String(configName);
|
|
1481
|
+
if (original === "default") {
|
|
1482
|
+
rewrittenManifest.componentConfigs[comp] = "default";
|
|
1483
|
+
continue;
|
|
1484
|
+
}
|
|
1485
|
+
const finalName = componentRenames[`${comp}/${original}`] ?? original;
|
|
1486
|
+
rewrittenManifest.componentConfigs[comp] = finalName;
|
|
1487
|
+
}
|
|
1488
|
+
rewrittenManifest.updatedAt = now;
|
|
1489
|
+
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
1490
|
+
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
1491
|
+
const finalManifestName = nextAvailableName2(
|
|
1492
|
+
(n) => manifestsResource.filePath(n),
|
|
1493
|
+
originalManifestName
|
|
1494
|
+
);
|
|
1495
|
+
if (finalManifestName !== originalManifestName) {
|
|
1496
|
+
renames[`manifest:${originalManifestName}`] = finalManifestName;
|
|
1497
|
+
}
|
|
1498
|
+
manifestsResource.ensureDir();
|
|
1499
|
+
fs2.writeFileSync(
|
|
1500
|
+
manifestsResource.filePath(finalManifestName),
|
|
1501
|
+
JSON.stringify(rewrittenManifest, null, 2)
|
|
1502
|
+
);
|
|
1503
|
+
jsonResponse(res, 200, {
|
|
1504
|
+
ok: true,
|
|
1505
|
+
manifest: finalManifestName,
|
|
1506
|
+
renames
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
903
1509
|
function methodNotAllowed({ res }) {
|
|
904
1510
|
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
905
1511
|
}
|
|
@@ -937,11 +1543,21 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
937
1543
|
{ method: "GET", pattern: MANIFESTS_ROUTE, handler: handleListManifests },
|
|
938
1544
|
{ method: "GET", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleGetActiveManifest },
|
|
939
1545
|
{ method: "PUT", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleSetActiveManifest },
|
|
1546
|
+
// Manifests — exact import route runs before :name regexes
|
|
1547
|
+
{ method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
|
|
1548
|
+
{ method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1549
|
+
{ method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1550
|
+
{ method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
940
1551
|
// Manifests — :name/apply (more specific than :name)
|
|
941
1552
|
{ method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
|
|
942
1553
|
{ method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
943
1554
|
{ method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
944
1555
|
{ method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1556
|
+
// Manifests — :name/export (more specific than :name)
|
|
1557
|
+
{ method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
|
|
1558
|
+
{ method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1559
|
+
{ method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1560
|
+
{ method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
945
1561
|
// Manifests — :name CRUD (broadest manifest route, runs last)
|
|
946
1562
|
{ method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
947
1563
|
{ method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
@@ -961,6 +1577,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
961
1577
|
ensureThemesDir();
|
|
962
1578
|
ensureComponentConfigsDir();
|
|
963
1579
|
ensureManifestsDir();
|
|
1580
|
+
regenerateTokensCss();
|
|
964
1581
|
server.middlewares.use(async (req, res, next) => {
|
|
965
1582
|
const handled = await dispatch(req, res, routes);
|
|
966
1583
|
if (!handled) next();
|