@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.cjs
CHANGED
|
@@ -54,6 +54,410 @@ function sanitizeFileName(name) {
|
|
|
54
54
|
return name.toLowerCase().trim().replace(/\s+/g, "-").replace(/[^a-z0-9\-_]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "") || "unnamed";
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// src/editor/core/palettes/oklch.ts
|
|
58
|
+
function srgbToLinear(c) {
|
|
59
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
60
|
+
}
|
|
61
|
+
function linearToSrgb(c) {
|
|
62
|
+
return c <= 31308e-7 ? 12.92 * c : 1.055 * Math.pow(c, 1 / 2.4) - 0.055;
|
|
63
|
+
}
|
|
64
|
+
function hexToLinearRgb(hex) {
|
|
65
|
+
const r = parseInt(hex.slice(1, 3), 16) / 255;
|
|
66
|
+
const g = parseInt(hex.slice(3, 5), 16) / 255;
|
|
67
|
+
const b = parseInt(hex.slice(5, 7), 16) / 255;
|
|
68
|
+
return [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)];
|
|
69
|
+
}
|
|
70
|
+
function linearRgbToHex(r, g, b) {
|
|
71
|
+
const toHex = (c) => {
|
|
72
|
+
const v = Math.round(Math.max(0, Math.min(1, linearToSrgb(c))) * 255);
|
|
73
|
+
return v.toString(16).padStart(2, "0");
|
|
74
|
+
};
|
|
75
|
+
return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
|
|
76
|
+
}
|
|
77
|
+
function linearRgbToOklab(r, g, b) {
|
|
78
|
+
const l_ = 0.4122214708 * r + 0.5363325363 * g + 0.0514459929 * b;
|
|
79
|
+
const m_ = 0.2119034982 * r + 0.6806995451 * g + 0.1073969566 * b;
|
|
80
|
+
const s_ = 0.0883024619 * r + 0.2817188376 * g + 0.6299787005 * b;
|
|
81
|
+
const l = Math.cbrt(l_);
|
|
82
|
+
const m = Math.cbrt(m_);
|
|
83
|
+
const s = Math.cbrt(s_);
|
|
84
|
+
return [
|
|
85
|
+
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
|
86
|
+
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
|
87
|
+
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s
|
|
88
|
+
];
|
|
89
|
+
}
|
|
90
|
+
function oklabToLinearRgb(L, a, b) {
|
|
91
|
+
const l_ = L + 0.3963377774 * a + 0.2158037573 * b;
|
|
92
|
+
const m_ = L - 0.1055613458 * a - 0.0638541728 * b;
|
|
93
|
+
const s_ = L - 0.0894841775 * a - 1.291485548 * b;
|
|
94
|
+
const l = l_ * l_ * l_;
|
|
95
|
+
const m = m_ * m_ * m_;
|
|
96
|
+
const s = s_ * s_ * s_;
|
|
97
|
+
return [
|
|
98
|
+
4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s,
|
|
99
|
+
-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s,
|
|
100
|
+
-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
function oklabToOklch(L, a, b) {
|
|
104
|
+
const c = Math.sqrt(a * a + b * b);
|
|
105
|
+
let h = Math.atan2(b, a) * 180 / Math.PI;
|
|
106
|
+
if (h < 0) h += 360;
|
|
107
|
+
return { l: L, c, h };
|
|
108
|
+
}
|
|
109
|
+
function oklchToOklab(l, c, h) {
|
|
110
|
+
const hRad = h * Math.PI / 180;
|
|
111
|
+
return [l, c * Math.cos(hRad), c * Math.sin(hRad)];
|
|
112
|
+
}
|
|
113
|
+
function hexToOklch(hex) {
|
|
114
|
+
const [r, g, b] = hexToLinearRgb(hex);
|
|
115
|
+
const [L, a, bVal] = linearRgbToOklab(r, g, b);
|
|
116
|
+
return oklabToOklch(L, a, bVal);
|
|
117
|
+
}
|
|
118
|
+
function oklchToHex(l, c, h) {
|
|
119
|
+
const [L, a, b] = oklchToOklab(l, c, h);
|
|
120
|
+
const [r, g, bVal] = oklabToLinearRgb(L, a, b);
|
|
121
|
+
return linearRgbToHex(r, g, bVal);
|
|
122
|
+
}
|
|
123
|
+
function isInGamut(r, g, b) {
|
|
124
|
+
const eps = 1e-4;
|
|
125
|
+
return r >= -eps && r <= 1 + eps && g >= -eps && g <= 1 + eps && b >= -eps && b <= 1 + eps;
|
|
126
|
+
}
|
|
127
|
+
function gamutClamp(l, c, h) {
|
|
128
|
+
if (l <= 0) return { l: 0, c: 0, h };
|
|
129
|
+
if (l >= 1) return { l: 1, c: 0, h };
|
|
130
|
+
const [L, a, b] = oklchToOklab(l, c, h);
|
|
131
|
+
const [r, g, bVal] = oklabToLinearRgb(L, a, b);
|
|
132
|
+
if (isInGamut(r, g, bVal)) return { l, c, h };
|
|
133
|
+
let lo = 0;
|
|
134
|
+
let hi = c;
|
|
135
|
+
for (let i = 0; i < 20; i++) {
|
|
136
|
+
const mid = (lo + hi) / 2;
|
|
137
|
+
const [La, aa, ba] = oklchToOklab(l, mid, h);
|
|
138
|
+
const [rr, gg, bb] = oklabToLinearRgb(La, aa, ba);
|
|
139
|
+
if (isInGamut(rr, gg, bb)) {
|
|
140
|
+
lo = mid;
|
|
141
|
+
} else {
|
|
142
|
+
hi = mid;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return { l, c: lo, h };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// src/editor/ui/curveEngine.ts
|
|
149
|
+
function makeAnchor(x, y, tangentLen = 15) {
|
|
150
|
+
return { x, y, inDx: -tangentLen, inDy: 0, outDx: tangentLen, outDy: 0 };
|
|
151
|
+
}
|
|
152
|
+
function evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, t) {
|
|
153
|
+
const u = 1 - t, u2 = u * u, u3 = u2 * u;
|
|
154
|
+
const t2 = t * t, t3 = t2 * t;
|
|
155
|
+
return {
|
|
156
|
+
x: u3 * p0x + 3 * u2 * t * c0x + 3 * u * t2 * c1x + t3 * p1x,
|
|
157
|
+
y: u3 * p0y + 3 * u2 * t * c0y + 3 * u * t2 * c1y + t3 * p1y
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
function sampleCurve(anchors, xPos) {
|
|
161
|
+
if (anchors.length === 0) return 0;
|
|
162
|
+
if (anchors.length === 1) return anchors[0].y;
|
|
163
|
+
if (xPos <= anchors[0].x) return anchors[0].y;
|
|
164
|
+
if (xPos >= anchors[anchors.length - 1].x) return anchors[anchors.length - 1].y;
|
|
165
|
+
let seg = 0;
|
|
166
|
+
while (seg < anchors.length - 2 && anchors[seg + 1].x < xPos) seg++;
|
|
167
|
+
const a0 = anchors[seg], a1 = anchors[seg + 1];
|
|
168
|
+
const p0x = a0.x, p0y = a0.y;
|
|
169
|
+
const c0x = a0.x + a0.outDx, c0y = a0.y + a0.outDy;
|
|
170
|
+
const c1x = a1.x + a1.inDx, c1y = a1.y + a1.inDy;
|
|
171
|
+
const p1x = a1.x, p1y = a1.y;
|
|
172
|
+
let lo = 0, hi = 1;
|
|
173
|
+
for (let i = 0; i < 20; i++) {
|
|
174
|
+
const mid = (lo + hi) / 2;
|
|
175
|
+
const pt = evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, mid);
|
|
176
|
+
if (pt.x < xPos) lo = mid;
|
|
177
|
+
else hi = mid;
|
|
178
|
+
}
|
|
179
|
+
return evalBezier(p0x, p0y, c0x, c0y, c1x, c1y, p1x, p1y, (lo + hi) / 2).y;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// src/editor/core/palettes/paletteDerivation.ts
|
|
183
|
+
var PALETTE_SPECS = [
|
|
184
|
+
{ label: "Neutral", cssNamespace: "neutral", mode: "gray", initialColor: "#808080" },
|
|
185
|
+
{ label: "Alternate", cssNamespace: "alternate", mode: "gray", initialColor: "#808080" },
|
|
186
|
+
{ label: "Background", cssNamespace: "canvas", mode: "chromatic", emptySelector: true, initialColor: "#1a1a2e" },
|
|
187
|
+
{ label: "Brand", cssNamespace: "brand", mode: "chromatic", initialColor: "#c93636" },
|
|
188
|
+
{ label: "Accent", cssNamespace: "accent", mode: "chromatic", initialColor: "#f49e0b" },
|
|
189
|
+
{ label: "Special", cssNamespace: "special", mode: "chromatic", initialColor: "#8b5cf6" },
|
|
190
|
+
{ label: "Info", cssNamespace: "info", mode: "chromatic", initialColor: "#3077e8" },
|
|
191
|
+
{ label: "Success", cssNamespace: "success", mode: "chromatic", initialColor: "#21c45d" },
|
|
192
|
+
{ label: "Warning", cssNamespace: "warning", mode: "chromatic", initialColor: "#e66e1a" },
|
|
193
|
+
{ label: "Danger", cssNamespace: "danger", mode: "chromatic", initialColor: "#e8304f" }
|
|
194
|
+
];
|
|
195
|
+
var PALETTE_STEPS = [
|
|
196
|
+
{ label: "100" },
|
|
197
|
+
{ label: "200" },
|
|
198
|
+
{ label: "300" },
|
|
199
|
+
{ label: "400" },
|
|
200
|
+
{ label: "500" },
|
|
201
|
+
{ label: "600" },
|
|
202
|
+
{ label: "700" },
|
|
203
|
+
{ label: "800" },
|
|
204
|
+
{ label: "850" },
|
|
205
|
+
{ label: "900" },
|
|
206
|
+
{ label: "950" }
|
|
207
|
+
];
|
|
208
|
+
var GRAY_STEPS = [
|
|
209
|
+
{ label: "100" },
|
|
210
|
+
{ label: "200" },
|
|
211
|
+
{ label: "300" },
|
|
212
|
+
{ label: "400" },
|
|
213
|
+
{ label: "500" },
|
|
214
|
+
{ label: "600" },
|
|
215
|
+
{ label: "700" },
|
|
216
|
+
{ label: "800" },
|
|
217
|
+
{ label: "850" },
|
|
218
|
+
{ label: "900" },
|
|
219
|
+
{ label: "950" }
|
|
220
|
+
];
|
|
221
|
+
var SCALES = [
|
|
222
|
+
{
|
|
223
|
+
title: "Surfaces",
|
|
224
|
+
isText: false,
|
|
225
|
+
steps: [
|
|
226
|
+
{ name: "lowest", position: -1 },
|
|
227
|
+
{ name: "lower", position: -2 / 3 },
|
|
228
|
+
{ name: "low", position: -1 / 3 },
|
|
229
|
+
{ name: "default", position: 0 },
|
|
230
|
+
{ name: "high", position: 1 / 3 },
|
|
231
|
+
{ name: "higher", position: 2 / 3 },
|
|
232
|
+
{ name: "highest", position: 1 }
|
|
233
|
+
]
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
title: "Borders",
|
|
237
|
+
isText: false,
|
|
238
|
+
steps: [
|
|
239
|
+
{ name: "faint", position: -1 },
|
|
240
|
+
{ name: "subtle", position: -0.5 },
|
|
241
|
+
{ name: "default", position: 0 },
|
|
242
|
+
{ name: "medium", position: 0.5 },
|
|
243
|
+
{ name: "strong", position: 1 }
|
|
244
|
+
]
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
title: "Text",
|
|
248
|
+
isText: true,
|
|
249
|
+
steps: [
|
|
250
|
+
{ name: "primary", position: 0 },
|
|
251
|
+
{ name: "secondary", position: 0 },
|
|
252
|
+
{ name: "tertiary", position: 0 },
|
|
253
|
+
{ name: "muted", position: 0 },
|
|
254
|
+
{ name: "disabled", position: 0 }
|
|
255
|
+
]
|
|
256
|
+
}
|
|
257
|
+
];
|
|
258
|
+
var DEFAULT_PALETTE_LIGHTNESS = () => [makeAnchor(0, 95, 5), makeAnchor(100, 8, 5)];
|
|
259
|
+
var DEFAULT_PALETTE_SATURATION = () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)];
|
|
260
|
+
var DEFAULT_GRAY_LIGHTNESS = () => [makeAnchor(0, 92, 5), makeAnchor(100, 3, 5)];
|
|
261
|
+
var DEFAULT_GRAY_SATURATION = () => [makeAnchor(0, 20, 30), makeAnchor(100, 20, 30)];
|
|
262
|
+
var DEFAULT_TINT_CHROMA = 0.04;
|
|
263
|
+
var defaultScaleCurves = {
|
|
264
|
+
Surfaces: {
|
|
265
|
+
lightness: () => [makeAnchor(0, 15, 5), makeAnchor(100, 47, 5)],
|
|
266
|
+
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
267
|
+
},
|
|
268
|
+
Borders: {
|
|
269
|
+
lightness: () => [makeAnchor(0, 25, 5), makeAnchor(100, 80, 5)],
|
|
270
|
+
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 100, 30)]
|
|
271
|
+
},
|
|
272
|
+
Text: {
|
|
273
|
+
lightness: () => [makeAnchor(0, 120, 30), makeAnchor(100, 55, 30)],
|
|
274
|
+
saturation: () => [makeAnchor(0, 100, 30), makeAnchor(100, 15, 30)]
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
function paletteStepKey(label) {
|
|
278
|
+
return `Palette-${label}`;
|
|
279
|
+
}
|
|
280
|
+
function grayStepKey(label) {
|
|
281
|
+
return `gray-${label}`;
|
|
282
|
+
}
|
|
283
|
+
function stepKey(scaleTitle, stepName) {
|
|
284
|
+
return `${scaleTitle}-${stepName}`;
|
|
285
|
+
}
|
|
286
|
+
function stepIndexToX(index, total) {
|
|
287
|
+
return total > 1 ? index / (total - 1) * 100 : 50;
|
|
288
|
+
}
|
|
289
|
+
function computePaletteColor(index, base, lightnessCurve, saturationCurve, curveOffset) {
|
|
290
|
+
const { c: baseC, h } = hexToOklch(base);
|
|
291
|
+
const xPos = stepIndexToX(index, PALETTE_STEPS.length);
|
|
292
|
+
const targetL = Math.max(0, Math.min(100, sampleCurve(lightnessCurve, xPos) + (curveOffset.lightness ?? 0))) / 100;
|
|
293
|
+
const satMul = Math.max(0, Math.min(2, (sampleCurve(saturationCurve, xPos) + (curveOffset.saturation ?? 0)) / 100));
|
|
294
|
+
const targetC = baseC * satMul;
|
|
295
|
+
const clamped = gamutClamp(targetL, targetC, h);
|
|
296
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
297
|
+
}
|
|
298
|
+
function computeGrayColor(index, hue, chroma, grayLightnessCurve, graySaturationCurve, curveOffset) {
|
|
299
|
+
const xPos = stepIndexToX(index, GRAY_STEPS.length);
|
|
300
|
+
const lOff = curveOffset["gray-lightness"] ?? 0;
|
|
301
|
+
const sOff = curveOffset["gray-saturation"] ?? 0;
|
|
302
|
+
const targetL = Math.max(0, Math.min(100, sampleCurve(grayLightnessCurve, xPos) + lOff)) / 100;
|
|
303
|
+
const satMul = Math.max(0, Math.min(2, (sampleCurve(graySaturationCurve, xPos) + sOff) / 100));
|
|
304
|
+
const targetC = chroma * satMul;
|
|
305
|
+
const clamped = gamutClamp(targetL, targetC, hue);
|
|
306
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
307
|
+
}
|
|
308
|
+
function computeDerivedColor(step, base, scaleTitle, scaleCurves, curveOffset) {
|
|
309
|
+
const scale = SCALES.find((s) => s.title === scaleTitle);
|
|
310
|
+
const idx = scale.steps.indexOf(step);
|
|
311
|
+
const xPos = stepIndexToX(idx, scale.steps.length);
|
|
312
|
+
const defs = defaultScaleCurves[scaleTitle];
|
|
313
|
+
const lCurve = scaleCurves[scaleTitle]?.lightness ?? defs.lightness();
|
|
314
|
+
const sCurve = scaleCurves[scaleTitle]?.saturation ?? defs.saturation();
|
|
315
|
+
const lOff = curveOffset[`${scaleTitle}-lightness`] ?? 0;
|
|
316
|
+
const sOff = curveOffset[`${scaleTitle}-saturation`] ?? 0;
|
|
317
|
+
const { l: baseL, c: baseC, h: baseH } = hexToOklch(base);
|
|
318
|
+
let targetL;
|
|
319
|
+
if (scale.isText) {
|
|
320
|
+
const lMul = Math.max(0, Math.min(2, (sampleCurve(lCurve, xPos) + lOff) / 100));
|
|
321
|
+
targetL = Math.max(0, Math.min(1, baseL * lMul));
|
|
322
|
+
} else {
|
|
323
|
+
targetL = Math.max(0, Math.min(100, sampleCurve(lCurve, xPos) + lOff)) / 100;
|
|
324
|
+
}
|
|
325
|
+
const satMul = Math.max(0, Math.min(2, (sampleCurve(sCurve, xPos) + sOff) / 100));
|
|
326
|
+
const targetC = baseC * satMul;
|
|
327
|
+
const clamped = gamutClamp(targetL, targetC, baseH);
|
|
328
|
+
return oklchToHex(clamped.l, clamped.c, clamped.h);
|
|
329
|
+
}
|
|
330
|
+
function scaleToCssVar(scaleTitle, stepName, cssNamespace) {
|
|
331
|
+
if (cssNamespace === null) return null;
|
|
332
|
+
if (scaleTitle === "Surfaces") {
|
|
333
|
+
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
334
|
+
return cssNamespace === "neutral" ? `--surface-neutral${suffix}` : `--surface-${cssNamespace}${suffix}`;
|
|
335
|
+
}
|
|
336
|
+
if (scaleTitle === "Borders") {
|
|
337
|
+
const suffix = stepName === "default" ? "" : `-${stepName}`;
|
|
338
|
+
return cssNamespace === "neutral" ? `--border-neutral${suffix}` : `--border-${cssNamespace}${suffix}`;
|
|
339
|
+
}
|
|
340
|
+
if (scaleTitle === "Text") {
|
|
341
|
+
if (cssNamespace === "neutral") return `--text-${stepName}`;
|
|
342
|
+
return stepName === "primary" ? `--text-${cssNamespace}` : `--text-${cssNamespace}-${stepName}`;
|
|
343
|
+
}
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
function derivePaletteVars(spec, config) {
|
|
347
|
+
const out = {};
|
|
348
|
+
if (!config) return out;
|
|
349
|
+
const baseColor = config.baseColor ?? spec.initialColor;
|
|
350
|
+
const overrides = config.overrides ?? {};
|
|
351
|
+
const curveOffset = config.curveOffset ?? {};
|
|
352
|
+
const scaleCurves = config.scaleCurves ?? {};
|
|
353
|
+
let baseForScales;
|
|
354
|
+
if (spec.mode === "gray") {
|
|
355
|
+
const grayLightnessCurve = config.grayLightnessCurve ?? DEFAULT_GRAY_LIGHTNESS();
|
|
356
|
+
const graySaturationCurve = config.graySaturationCurve ?? DEFAULT_GRAY_SATURATION();
|
|
357
|
+
const tintHue = config.tintHue ?? 240;
|
|
358
|
+
const tintChroma = config.tintChroma ?? DEFAULT_TINT_CHROMA;
|
|
359
|
+
let gray500 = "#808080";
|
|
360
|
+
GRAY_STEPS.forEach((step, index) => {
|
|
361
|
+
const k = grayStepKey(step.label);
|
|
362
|
+
const hex = computeGrayColor(index, tintHue, tintChroma, grayLightnessCurve, graySaturationCurve, curveOffset);
|
|
363
|
+
const effective = k in overrides ? overrides[k] : hex;
|
|
364
|
+
out[`--color-${spec.cssNamespace}-${step.label}`] = effective;
|
|
365
|
+
if (step.label === "500") gray500 = hex;
|
|
366
|
+
});
|
|
367
|
+
baseForScales = gray500;
|
|
368
|
+
} else {
|
|
369
|
+
const lightnessCurve = config.lightnessCurve ?? DEFAULT_PALETTE_LIGHTNESS();
|
|
370
|
+
const saturationCurve = config.saturationCurve ?? DEFAULT_PALETTE_SATURATION();
|
|
371
|
+
PALETTE_STEPS.forEach((ps, index) => {
|
|
372
|
+
const k = paletteStepKey(ps.label);
|
|
373
|
+
const hex = computePaletteColor(index, baseColor, lightnessCurve, saturationCurve, curveOffset);
|
|
374
|
+
const effective = k in overrides ? overrides[k] : hex;
|
|
375
|
+
out[`--color-${spec.cssNamespace}-${ps.label}`] = effective;
|
|
376
|
+
});
|
|
377
|
+
baseForScales = baseColor;
|
|
378
|
+
}
|
|
379
|
+
for (const scale of SCALES) {
|
|
380
|
+
for (const step of scale.steps) {
|
|
381
|
+
const k = stepKey(scale.title, step.name);
|
|
382
|
+
const hex = k in overrides ? overrides[k] : computeDerivedColor(step, baseForScales, scale.title, scaleCurves, curveOffset);
|
|
383
|
+
const varName = scaleToCssVar(scale.title, step.name, spec.cssNamespace);
|
|
384
|
+
if (varName) out[varName] = hex;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
if (spec.emptySelector) {
|
|
388
|
+
const emptyMode = config.emptyMode ?? "solid";
|
|
389
|
+
const emptyStep = config.emptyStep ?? "850";
|
|
390
|
+
const gradientStyle = config.gradientStyle ?? "linear";
|
|
391
|
+
const gradientAngle = config.gradientAngle ?? 180;
|
|
392
|
+
const gradientReverse = config.gradientReverse ?? false;
|
|
393
|
+
const gradientSize = config.gradientSize ?? "page";
|
|
394
|
+
const gradientStops = config.gradientStops ?? [
|
|
395
|
+
{ position: 0, paletteLabel: "800" },
|
|
396
|
+
{ position: 100, paletteLabel: "950" }
|
|
397
|
+
];
|
|
398
|
+
if (emptyMode === "solid") {
|
|
399
|
+
const stepHex = out[`--color-${spec.cssNamespace}-${emptyStep}`];
|
|
400
|
+
if (stepHex) out["--page-bg"] = stepHex;
|
|
401
|
+
out["--page-bg-attachment"] = "scroll";
|
|
402
|
+
} else {
|
|
403
|
+
const sortedStops = [...gradientStops].sort(
|
|
404
|
+
(a, b) => gradientReverse ? b.position - a.position : a.position - b.position
|
|
405
|
+
);
|
|
406
|
+
const stopsCss = sortedStops.map((s) => `${out[`--color-${spec.cssNamespace}-${s.paletteLabel}`] ?? "#000000"} ${s.position}%`).join(", ");
|
|
407
|
+
let gradient;
|
|
408
|
+
switch (gradientStyle) {
|
|
409
|
+
case "radial":
|
|
410
|
+
gradient = `radial-gradient(circle, ${stopsCss})`;
|
|
411
|
+
break;
|
|
412
|
+
case "conic":
|
|
413
|
+
gradient = `conic-gradient(from ${gradientAngle}deg, ${stopsCss})`;
|
|
414
|
+
break;
|
|
415
|
+
default:
|
|
416
|
+
gradient = `linear-gradient(${gradientAngle}deg, ${stopsCss})`;
|
|
417
|
+
}
|
|
418
|
+
out["--page-bg"] = gradient;
|
|
419
|
+
out["--page-bg-attachment"] = gradientSize === "window" ? "fixed" : "scroll";
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
return out;
|
|
423
|
+
}
|
|
424
|
+
function palettesToVars(palettes) {
|
|
425
|
+
const out = {};
|
|
426
|
+
for (const spec of PALETTE_SPECS) {
|
|
427
|
+
Object.assign(out, derivePaletteVars(spec, palettes[spec.label]));
|
|
428
|
+
}
|
|
429
|
+
return out;
|
|
430
|
+
}
|
|
431
|
+
var HEX_RE = /^#[0-9a-f]{6}$/i;
|
|
432
|
+
function reconcilePalettesFromCssVars(palettes, cssVars) {
|
|
433
|
+
const next = structuredClone(palettes);
|
|
434
|
+
const consumed = /* @__PURE__ */ new Set();
|
|
435
|
+
const snapped = /* @__PURE__ */ new Set();
|
|
436
|
+
for (const spec of PALETTE_SPECS) {
|
|
437
|
+
const current = next[spec.label];
|
|
438
|
+
if (current === void 0) continue;
|
|
439
|
+
if (current._imported === true) {
|
|
440
|
+
const anchorHex = cssVars[`--color-${spec.cssNamespace}-500`];
|
|
441
|
+
if (anchorHex && HEX_RE.test(anchorHex.trim())) {
|
|
442
|
+
const hex = anchorHex.trim();
|
|
443
|
+
if (spec.mode === "gray") {
|
|
444
|
+
const { c, h } = hexToOklch(hex);
|
|
445
|
+
next[spec.label] = { ...current, tintHue: h, tintChroma: c, _imported: false };
|
|
446
|
+
} else {
|
|
447
|
+
next[spec.label] = { ...current, baseColor: hex, _imported: false };
|
|
448
|
+
}
|
|
449
|
+
snapped.add(spec.label);
|
|
450
|
+
} else {
|
|
451
|
+
next[spec.label] = { ...current, _imported: false };
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
for (const k of Object.keys(derivePaletteVars(spec, next[spec.label]))) {
|
|
455
|
+
consumed.add(k);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
return { palettes: next, consumed, snapped };
|
|
459
|
+
}
|
|
460
|
+
|
|
57
461
|
// vite-plugin/files/versionedFileResourceServer.ts
|
|
58
462
|
var import_fs = __toESM(require("fs"), 1);
|
|
59
463
|
var import_path = __toESM(require("path"), 1);
|
|
@@ -142,6 +546,16 @@ async function dispatch(req, res, routes) {
|
|
|
142
546
|
return false;
|
|
143
547
|
}
|
|
144
548
|
|
|
549
|
+
// vite-plugin/files/nameAllocator.ts
|
|
550
|
+
function nextAvailableName(exists, baseName, maxAttempts = 1e3) {
|
|
551
|
+
if (!exists(baseName)) return baseName;
|
|
552
|
+
for (let i = 2; i < maxAttempts; i++) {
|
|
553
|
+
const candidate = `${baseName}-${i}`;
|
|
554
|
+
if (!exists(candidate)) return candidate;
|
|
555
|
+
}
|
|
556
|
+
throw new Error(`Could not allocate a non-colliding name for "${baseName}"`);
|
|
557
|
+
}
|
|
558
|
+
|
|
145
559
|
// vite-plugin/themeFileApi.ts
|
|
146
560
|
var import_node_url = require("url");
|
|
147
561
|
var import_meta = {};
|
|
@@ -165,6 +579,7 @@ var PKG_VERSION = (() => {
|
|
|
165
579
|
function themeFileApi(opts) {
|
|
166
580
|
const THEMES_DIR = import_path2.default.resolve(opts.themesDir);
|
|
167
581
|
const CSS_PATH = import_path2.default.resolve(opts.tokensCssPath);
|
|
582
|
+
const GENERATED_CSS_PATH = opts.tokensGeneratedCssPath ? import_path2.default.resolve(opts.tokensGeneratedCssPath) : import_path2.default.join(import_path2.default.dirname(CSS_PATH), "tokens.generated.css");
|
|
168
583
|
const FONTS_CSS_PATH = opts.fontsCssPath ? import_path2.default.resolve(opts.fontsCssPath) : import_path2.default.join(import_path2.default.dirname(CSS_PATH), "fonts.css");
|
|
169
584
|
const API_BASE = opts.apiBase ?? "/api";
|
|
170
585
|
const COMPONENT_CONFIGS_DIR = opts.componentConfigsDir ? import_path2.default.resolve(opts.componentConfigsDir) : import_path2.default.resolve("component-configs");
|
|
@@ -211,6 +626,17 @@ function themeFileApi(opts) {
|
|
|
211
626
|
res.setHeader("Content-Type", "application/json");
|
|
212
627
|
res.end(JSON.stringify(data));
|
|
213
628
|
}
|
|
629
|
+
function normalizeTheme(theme) {
|
|
630
|
+
if (!theme || typeof theme !== "object") return theme;
|
|
631
|
+
const palettes = theme.editorConfigs ?? {};
|
|
632
|
+
const cssVars = theme.cssVariables ?? {};
|
|
633
|
+
const { palettes: nextPalettes, consumed } = reconcilePalettesFromCssVars(palettes, cssVars);
|
|
634
|
+
const nextCssVars = {};
|
|
635
|
+
for (const [k, v] of Object.entries(cssVars)) {
|
|
636
|
+
if (!consumed.has(k)) nextCssVars[k] = v;
|
|
637
|
+
}
|
|
638
|
+
return { ...theme, editorConfigs: nextPalettes, cssVariables: nextCssVars };
|
|
639
|
+
}
|
|
214
640
|
const SYSTEM_CASCADES_SSR = {
|
|
215
641
|
"system-ui-sans": 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
|
216
642
|
"system-ui-serif": 'Georgia, "Times New Roman", serif',
|
|
@@ -242,42 +668,78 @@ function themeFileApi(opts) {
|
|
|
242
668
|
}
|
|
243
669
|
return out;
|
|
244
670
|
}
|
|
245
|
-
function
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
671
|
+
function regenerateTokensCss() {
|
|
672
|
+
const lines = [];
|
|
673
|
+
lines.push("/* Generated by themeFileApi from the production theme and component configs. Do not edit. */");
|
|
674
|
+
lines.push("/* tokens.css holds developer-authored defaults; this file holds editor overrides. */");
|
|
675
|
+
lines.push("");
|
|
676
|
+
const productionThemeName = themesResource.getProductionName();
|
|
677
|
+
const themePath = import_path2.default.join(THEMES_DIR, `${productionThemeName}.json`);
|
|
678
|
+
let themeVarCount = 0;
|
|
679
|
+
if (import_fs2.default.existsSync(themePath)) {
|
|
680
|
+
const themeData = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
|
|
681
|
+
const cssVars = { ...themeData.cssVariables || {} };
|
|
682
|
+
Object.assign(cssVars, palettesToVars(themeData.editorConfigs ?? {}));
|
|
683
|
+
const resolvedFontVars = resolveFontStacks(themeData);
|
|
684
|
+
for (const [name, value] of Object.entries(resolvedFontVars)) {
|
|
685
|
+
cssVars[name] = value;
|
|
686
|
+
}
|
|
687
|
+
themeVarCount = Object.keys(cssVars).length;
|
|
688
|
+
if (themeVarCount > 0) {
|
|
689
|
+
lines.push(`/* Production theme: ${productionThemeName} */`);
|
|
690
|
+
lines.push(":root:root {");
|
|
691
|
+
for (const [name, value] of Object.entries(cssVars)) {
|
|
692
|
+
lines.push(` ${name}: ${value};`);
|
|
263
693
|
}
|
|
264
|
-
|
|
694
|
+
lines.push("}");
|
|
695
|
+
lines.push("");
|
|
265
696
|
}
|
|
266
|
-
);
|
|
267
|
-
let finalContent = updatedContent;
|
|
268
|
-
if (remaining.size > 0) {
|
|
269
|
-
const newVars = [...remaining].map((name) => ` ${name}: ${cssVars[name]};`).join("\n");
|
|
270
|
-
finalContent = finalContent.replace(
|
|
271
|
-
/\n\}(\s*)$/,
|
|
272
|
-
`
|
|
273
|
-
|
|
274
|
-
/* Token additions */
|
|
275
|
-
${newVars}
|
|
276
|
-
}$1`
|
|
277
|
-
);
|
|
278
697
|
}
|
|
279
|
-
|
|
280
|
-
|
|
698
|
+
let componentOverrideCount = 0;
|
|
699
|
+
if (import_fs2.default.existsSync(COMPONENT_CONFIGS_DIR)) {
|
|
700
|
+
const blocks = [];
|
|
701
|
+
for (const comp of listComponentNames()) {
|
|
702
|
+
const prod = componentResource(comp).getProductionName();
|
|
703
|
+
if (prod === "default") continue;
|
|
704
|
+
const prodCfg = readComponentConfig(comp, prod);
|
|
705
|
+
const defaultCfg = readComponentConfig(comp, "default");
|
|
706
|
+
if (!prodCfg || !defaultCfg) continue;
|
|
707
|
+
const overrides = [];
|
|
708
|
+
const defaultAliases = defaultCfg.aliases ?? {};
|
|
709
|
+
for (const [varName, semanticValue] of Object.entries(prodCfg.aliases ?? {})) {
|
|
710
|
+
if (!aliasValuesEqual(defaultAliases[varName], semanticValue)) {
|
|
711
|
+
overrides.push([varName, semanticValue]);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (overrides.length === 0) continue;
|
|
715
|
+
const block = [` /* ${comp} (${prod}) */`];
|
|
716
|
+
for (const [varName, semanticValue] of overrides) {
|
|
717
|
+
block.push(` ${varName}: ${aliasValueToCss(semanticValue)};`);
|
|
718
|
+
}
|
|
719
|
+
blocks.push(block);
|
|
720
|
+
componentOverrideCount += overrides.length;
|
|
721
|
+
}
|
|
722
|
+
if (blocks.length > 0) {
|
|
723
|
+
lines.push("/* Component aliases (production configs differing from defaults) */");
|
|
724
|
+
lines.push(":root:root {");
|
|
725
|
+
for (let i = 0; i < blocks.length; i++) {
|
|
726
|
+
if (i > 0) lines.push("");
|
|
727
|
+
lines.push(...blocks[i]);
|
|
728
|
+
}
|
|
729
|
+
lines.push("}");
|
|
730
|
+
lines.push("");
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
if (!import_fs2.default.existsSync(import_path2.default.dirname(GENERATED_CSS_PATH))) {
|
|
734
|
+
import_fs2.default.mkdirSync(import_path2.default.dirname(GENERATED_CSS_PATH), { recursive: true });
|
|
735
|
+
}
|
|
736
|
+
import_fs2.default.writeFileSync(GENERATED_CSS_PATH, lines.join("\n"));
|
|
737
|
+
console.log(
|
|
738
|
+
`[regenerateTokensCss] Wrote ${import_path2.default.basename(GENERATED_CSS_PATH)} (${themeVarCount} theme vars, ${componentOverrideCount} component overrides)`
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
function syncTokensToCss(_fileName) {
|
|
742
|
+
regenerateTokensCss();
|
|
281
743
|
}
|
|
282
744
|
function syncFontsToCss(fileName) {
|
|
283
745
|
const themePath = import_path2.default.join(THEMES_DIR, `${fileName}.json`);
|
|
@@ -289,15 +751,20 @@ ${newVars}
|
|
|
289
751
|
lines.push("/* Generated from the production theme by syncFontsToCss. Do not edit. */");
|
|
290
752
|
lines.push("/* Both fonts.css and fonts/ are in dist/, so relative paths work at runtime. */");
|
|
291
753
|
lines.push("");
|
|
292
|
-
|
|
754
|
+
const urlSources = sources.filter((s) => s.kind !== "font-face" && s.url);
|
|
755
|
+
const faceSources = sources.filter((s) => s.kind === "font-face" && s.cssText);
|
|
756
|
+
for (const source of urlSources) {
|
|
293
757
|
const familyNames = source.families.map((f) => f.name).join(", ");
|
|
294
758
|
const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
|
|
295
759
|
lines.push(`/* ${label} */`);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
760
|
+
lines.push(`@import url('${source.url}');`);
|
|
761
|
+
lines.push("");
|
|
762
|
+
}
|
|
763
|
+
for (const source of faceSources) {
|
|
764
|
+
const familyNames = source.families.map((f) => f.name).join(", ");
|
|
765
|
+
const label = source.label ? `${source.label} \u2014 ${familyNames}` : familyNames;
|
|
766
|
+
lines.push(`/* ${label} */`);
|
|
767
|
+
lines.push(source.cssText);
|
|
301
768
|
lines.push("");
|
|
302
769
|
}
|
|
303
770
|
const content = lines.join("\n");
|
|
@@ -437,6 +904,33 @@ ${newVars}
|
|
|
437
904
|
import_fs2.default.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2));
|
|
438
905
|
return true;
|
|
439
906
|
}
|
|
907
|
+
function formatAliasGradient(v) {
|
|
908
|
+
const stopColor = (s) => {
|
|
909
|
+
const base = s.color.startsWith("--") ? `var(${s.color})` : s.color;
|
|
910
|
+
const opacity = s.opacity ?? 100;
|
|
911
|
+
return opacity >= 100 ? base : `color-mix(in srgb, ${base} ${opacity}%, transparent)`;
|
|
912
|
+
};
|
|
913
|
+
if (v.type === "none") return "transparent";
|
|
914
|
+
if (v.type === "solid") {
|
|
915
|
+
const first = v.stops[0];
|
|
916
|
+
if (!first) return "transparent";
|
|
917
|
+
return stopColor(first);
|
|
918
|
+
}
|
|
919
|
+
const stops = v.stops.map((s) => `${stopColor(s)} ${s.position}%`).join(", ");
|
|
920
|
+
if (v.type === "linear") return `linear-gradient(${v.angle}deg, ${stops})`;
|
|
921
|
+
const radial = v.radius && v.radius > 0 ? `circle ${v.radius}px at center` : "circle";
|
|
922
|
+
return `radial-gradient(${radial}, ${stops})`;
|
|
923
|
+
}
|
|
924
|
+
function aliasValueToCss(v) {
|
|
925
|
+
if (typeof v === "string") return v.startsWith("--") ? `var(${v})` : v;
|
|
926
|
+
return formatAliasGradient(v.value);
|
|
927
|
+
}
|
|
928
|
+
function aliasValuesEqual(a, b) {
|
|
929
|
+
if (a === void 0 || b === void 0) return a === b;
|
|
930
|
+
if (typeof a === "string" && typeof b === "string") return a === b;
|
|
931
|
+
if (typeof a !== typeof b) return false;
|
|
932
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
933
|
+
}
|
|
440
934
|
function readComponentConfig(comp, name) {
|
|
441
935
|
const filePath = componentResource(comp).filePath(name);
|
|
442
936
|
if (!import_fs2.default.existsSync(filePath)) return null;
|
|
@@ -446,54 +940,8 @@ ${newVars}
|
|
|
446
940
|
return null;
|
|
447
941
|
}
|
|
448
942
|
}
|
|
449
|
-
const COMPONENT_OVERRIDES_START = "/* component-aliases:start */";
|
|
450
|
-
const COMPONENT_OVERRIDES_END = "/* component-aliases:end */";
|
|
451
943
|
function syncComponentsToCss() {
|
|
452
|
-
|
|
453
|
-
if (!import_fs2.default.existsSync(COMPONENT_CONFIGS_DIR)) return;
|
|
454
|
-
const lines = [];
|
|
455
|
-
const components = listComponentNames();
|
|
456
|
-
for (const comp of components) {
|
|
457
|
-
const prod = componentResource(comp).getProductionName();
|
|
458
|
-
if (prod === "default") continue;
|
|
459
|
-
const prodCfg = readComponentConfig(comp, prod);
|
|
460
|
-
const defaultCfg = readComponentConfig(comp, "default");
|
|
461
|
-
if (!prodCfg || !defaultCfg) continue;
|
|
462
|
-
const overrides = [];
|
|
463
|
-
for (const [varName, semanticName] of Object.entries(prodCfg.aliases ?? {})) {
|
|
464
|
-
if ((defaultCfg.aliases ?? {})[varName] !== semanticName) {
|
|
465
|
-
overrides.push([varName, semanticName]);
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
if (overrides.length === 0) continue;
|
|
469
|
-
lines.push(` /* ${comp} (${prod}) */`);
|
|
470
|
-
for (const [varName, semanticName] of overrides) {
|
|
471
|
-
lines.push(` ${varName}: var(${semanticName});`);
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
const block = lines.length > 0 ? `
|
|
475
|
-
|
|
476
|
-
${COMPONENT_OVERRIDES_START}
|
|
477
|
-
:root:root {
|
|
478
|
-
${lines.join("\n")}
|
|
479
|
-
}
|
|
480
|
-
${COMPONENT_OVERRIDES_END}
|
|
481
|
-
` : "";
|
|
482
|
-
let cssContent = import_fs2.default.readFileSync(CSS_PATH, "utf-8");
|
|
483
|
-
const startIdx = cssContent.indexOf(COMPONENT_OVERRIDES_START);
|
|
484
|
-
const endIdx = cssContent.indexOf(COMPONENT_OVERRIDES_END);
|
|
485
|
-
if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {
|
|
486
|
-
let stripStart = startIdx;
|
|
487
|
-
while (stripStart > 0 && cssContent[stripStart - 1] === "\n") stripStart--;
|
|
488
|
-
const after = cssContent.slice(endIdx + COMPONENT_OVERRIDES_END.length);
|
|
489
|
-
cssContent = cssContent.slice(0, stripStart) + (block || "\n") + after.replace(/^\n+/, "");
|
|
490
|
-
} else if (block) {
|
|
491
|
-
cssContent = cssContent.replace(/\n*$/, "") + block;
|
|
492
|
-
}
|
|
493
|
-
import_fs2.default.writeFileSync(CSS_PATH, cssContent);
|
|
494
|
-
console.log(
|
|
495
|
-
`[syncComponentsToCss] Wrote ${lines.filter((l) => !l.trim().startsWith("/*")).length} alias override(s) to tokens.css`
|
|
496
|
-
);
|
|
944
|
+
regenerateTokensCss();
|
|
497
945
|
}
|
|
498
946
|
const escapedBase = API_BASE.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
499
947
|
const THEMES_ROUTE = `${API_BASE}/themes`;
|
|
@@ -508,6 +956,8 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
508
956
|
const COMP_PRODUCTION_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/production$`);
|
|
509
957
|
const COMP_BY_NAME_REGEX = new RegExp(`^${escapedBase}/component-configs/([a-z0-9\\-_]+)/([a-z0-9\\-_]+)$`);
|
|
510
958
|
const MANIFEST_APPLY_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/apply$`);
|
|
959
|
+
const MANIFEST_EXPORT_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)/export$`);
|
|
960
|
+
const MANIFEST_IMPORT_ROUTE = `${API_BASE}/manifests/import`;
|
|
511
961
|
const MANIFEST_BY_NAME_REGEX = new RegExp(`^${escapedBase}/manifests/([a-z0-9\\-_]+)$`);
|
|
512
962
|
async function handleListThemes(_ctx) {
|
|
513
963
|
const activeFile = themesResource.getActiveName();
|
|
@@ -531,7 +981,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
531
981
|
jsonResponse(res, 404, { error: "Active theme not found" });
|
|
532
982
|
return;
|
|
533
983
|
}
|
|
534
|
-
const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
|
|
984
|
+
const data = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8")));
|
|
535
985
|
data._fileName = activeFile;
|
|
536
986
|
jsonResponse(res, 200, data);
|
|
537
987
|
}
|
|
@@ -552,7 +1002,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
552
1002
|
jsonResponse(res, 200, { fileName: prodFile, name: prodFile, cssVariables: {} });
|
|
553
1003
|
return;
|
|
554
1004
|
}
|
|
555
|
-
const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
|
|
1005
|
+
const data = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8")));
|
|
556
1006
|
jsonResponse(res, 200, {
|
|
557
1007
|
fileName: prodFile,
|
|
558
1008
|
name: data.name || prodFile,
|
|
@@ -595,7 +1045,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
595
1045
|
jsonResponse(res, 404, { error: "Not found" });
|
|
596
1046
|
return;
|
|
597
1047
|
}
|
|
598
|
-
const data = JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8"));
|
|
1048
|
+
const data = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(filePath, "utf-8")));
|
|
599
1049
|
data._fileName = fileName;
|
|
600
1050
|
jsonResponse(res, 200, data);
|
|
601
1051
|
return;
|
|
@@ -915,11 +1365,15 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
915
1365
|
apply.push([comp, sanitized]);
|
|
916
1366
|
}
|
|
917
1367
|
themesResource.setActiveName(themeName);
|
|
918
|
-
|
|
1368
|
+
themesResource.setProductionName(themeName);
|
|
1369
|
+
syncTokensToCss(themeName);
|
|
1370
|
+
syncFontsToCss(themeName);
|
|
1371
|
+
const themeData = normalizeTheme(JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8")));
|
|
919
1372
|
themeData._fileName = themeName;
|
|
920
1373
|
for (const [comp, configFile] of apply) {
|
|
921
1374
|
const r = componentResource(comp);
|
|
922
1375
|
r.setActiveName(configFile);
|
|
1376
|
+
r.setProductionName(configFile);
|
|
923
1377
|
const cfg = readComponentConfig(comp, configFile);
|
|
924
1378
|
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: configFile };
|
|
925
1379
|
}
|
|
@@ -929,6 +1383,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
929
1383
|
const cfg = readComponentConfig(comp, activeName);
|
|
930
1384
|
if (cfg) resolvedConfigs[comp] = { ...cfg, _fileName: activeName };
|
|
931
1385
|
}
|
|
1386
|
+
syncComponentsToCss();
|
|
932
1387
|
manifestsResource.setActiveName(fileName);
|
|
933
1388
|
jsonResponse(res, 200, {
|
|
934
1389
|
ok: true,
|
|
@@ -937,6 +1392,157 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
937
1392
|
componentConfigs: resolvedConfigs
|
|
938
1393
|
});
|
|
939
1394
|
}
|
|
1395
|
+
async function handleExportManifest({ params, res }) {
|
|
1396
|
+
const [fileName] = params;
|
|
1397
|
+
const manifestPath = manifestsResource.filePath(fileName);
|
|
1398
|
+
if (!import_fs2.default.existsSync(manifestPath)) {
|
|
1399
|
+
jsonResponse(res, 404, { error: "Manifest not found" });
|
|
1400
|
+
return;
|
|
1401
|
+
}
|
|
1402
|
+
const manifest = JSON.parse(import_fs2.default.readFileSync(manifestPath, "utf-8"));
|
|
1403
|
+
const themeName = sanitizeFileName(manifest.theme || "default");
|
|
1404
|
+
const themePath = themesResource.filePath(themeName);
|
|
1405
|
+
if (!import_fs2.default.existsSync(themePath)) {
|
|
1406
|
+
jsonResponse(res, 422, { error: `Manifest references missing theme: ${themeName}` });
|
|
1407
|
+
return;
|
|
1408
|
+
}
|
|
1409
|
+
const theme = JSON.parse(import_fs2.default.readFileSync(themePath, "utf-8"));
|
|
1410
|
+
const knownComponents = new Set(listComponentNames());
|
|
1411
|
+
const componentConfigs = {};
|
|
1412
|
+
for (const [comp, configFile] of Object.entries(manifest.componentConfigs ?? {})) {
|
|
1413
|
+
if (!knownComponents.has(comp)) continue;
|
|
1414
|
+
const sanitized = sanitizeFileName(String(configFile) || "default");
|
|
1415
|
+
if (sanitized === "default") continue;
|
|
1416
|
+
const cfgPath = componentResource(comp).filePath(sanitized);
|
|
1417
|
+
if (!import_fs2.default.existsSync(cfgPath)) {
|
|
1418
|
+
jsonResponse(res, 422, {
|
|
1419
|
+
error: `Manifest references missing config: ${comp}/${sanitized}`
|
|
1420
|
+
});
|
|
1421
|
+
return;
|
|
1422
|
+
}
|
|
1423
|
+
const cfg = JSON.parse(import_fs2.default.readFileSync(cfgPath, "utf-8"));
|
|
1424
|
+
componentConfigs[`${comp}/${sanitized}`] = cfg;
|
|
1425
|
+
}
|
|
1426
|
+
const bundle = {
|
|
1427
|
+
kind: "manifest-bundle",
|
|
1428
|
+
schemaVersion: 1,
|
|
1429
|
+
liveTokensVersion: PKG_VERSION,
|
|
1430
|
+
exportedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1431
|
+
manifest,
|
|
1432
|
+
theme,
|
|
1433
|
+
componentConfigs
|
|
1434
|
+
};
|
|
1435
|
+
res.statusCode = 200;
|
|
1436
|
+
res.setHeader("Content-Type", "application/json");
|
|
1437
|
+
res.setHeader(
|
|
1438
|
+
"Content-Disposition",
|
|
1439
|
+
`attachment; filename="${fileName}.bundle.json"`
|
|
1440
|
+
);
|
|
1441
|
+
res.end(JSON.stringify(bundle, null, 2));
|
|
1442
|
+
}
|
|
1443
|
+
function nextAvailableName2(resourceFilePath, baseName) {
|
|
1444
|
+
return nextAvailableName(
|
|
1445
|
+
(n) => import_fs2.default.existsSync(resourceFilePath(n)),
|
|
1446
|
+
sanitizeFileName(baseName)
|
|
1447
|
+
);
|
|
1448
|
+
}
|
|
1449
|
+
async function handleImportManifest({ req, res }) {
|
|
1450
|
+
let bundle;
|
|
1451
|
+
try {
|
|
1452
|
+
bundle = JSON.parse(await readBody(req));
|
|
1453
|
+
} catch {
|
|
1454
|
+
jsonResponse(res, 400, { error: "Body is not valid JSON" });
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1457
|
+
if (!bundle || bundle.kind !== "manifest-bundle") {
|
|
1458
|
+
jsonResponse(res, 400, {
|
|
1459
|
+
error: "Not a manifest bundle (kind discriminator missing or wrong)"
|
|
1460
|
+
});
|
|
1461
|
+
return;
|
|
1462
|
+
}
|
|
1463
|
+
if (bundle.schemaVersion !== 1) {
|
|
1464
|
+
jsonResponse(res, 400, {
|
|
1465
|
+
error: `Unsupported bundle schemaVersion: ${bundle.schemaVersion}`
|
|
1466
|
+
});
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (!bundle.manifest || !bundle.theme || !bundle.componentConfigs) {
|
|
1470
|
+
jsonResponse(res, 400, { error: "Bundle missing manifest / theme / componentConfigs" });
|
|
1471
|
+
return;
|
|
1472
|
+
}
|
|
1473
|
+
const renames = {};
|
|
1474
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1475
|
+
const originalThemeName = sanitizeFileName(bundle.manifest.theme || "default");
|
|
1476
|
+
const finalThemeName = nextAvailableName2(
|
|
1477
|
+
(n) => themesResource.filePath(n),
|
|
1478
|
+
originalThemeName
|
|
1479
|
+
);
|
|
1480
|
+
if (finalThemeName !== originalThemeName) {
|
|
1481
|
+
renames[`theme:${originalThemeName}`] = finalThemeName;
|
|
1482
|
+
}
|
|
1483
|
+
const themeBody = { ...bundle.theme };
|
|
1484
|
+
themeBody.updatedAt = now;
|
|
1485
|
+
if (!themeBody.createdAt) themeBody.createdAt = now;
|
|
1486
|
+
themesResource.ensureDir();
|
|
1487
|
+
import_fs2.default.writeFileSync(themesResource.filePath(finalThemeName), JSON.stringify(themeBody, null, 2));
|
|
1488
|
+
const knownComponents = new Set(listComponentNames());
|
|
1489
|
+
const componentRenames = {};
|
|
1490
|
+
for (const [key, cfgValue] of Object.entries(bundle.componentConfigs)) {
|
|
1491
|
+
const [comp, originalName] = key.split("/");
|
|
1492
|
+
if (!comp || !originalName) continue;
|
|
1493
|
+
if (!knownComponents.has(comp)) continue;
|
|
1494
|
+
const r = componentResource(comp);
|
|
1495
|
+
const finalName = nextAvailableName2(
|
|
1496
|
+
(n) => r.filePath(n),
|
|
1497
|
+
originalName
|
|
1498
|
+
);
|
|
1499
|
+
if (finalName !== originalName) {
|
|
1500
|
+
renames[`componentConfig:${comp}/${originalName}`] = finalName;
|
|
1501
|
+
}
|
|
1502
|
+
componentRenames[`${comp}/${originalName}`] = finalName;
|
|
1503
|
+
const cfgBody = { ...cfgValue };
|
|
1504
|
+
cfgBody.component = comp;
|
|
1505
|
+
cfgBody.name = finalName;
|
|
1506
|
+
cfgBody.updatedAt = now;
|
|
1507
|
+
if (!cfgBody.createdAt) cfgBody.createdAt = now;
|
|
1508
|
+
r.ensureDir();
|
|
1509
|
+
import_fs2.default.writeFileSync(r.filePath(finalName), JSON.stringify(cfgBody, null, 2));
|
|
1510
|
+
}
|
|
1511
|
+
const rewrittenManifest = {
|
|
1512
|
+
...bundle.manifest,
|
|
1513
|
+
theme: finalThemeName,
|
|
1514
|
+
componentConfigs: {}
|
|
1515
|
+
};
|
|
1516
|
+
for (const [comp, configName] of Object.entries(bundle.manifest.componentConfigs ?? {})) {
|
|
1517
|
+
const original = String(configName);
|
|
1518
|
+
if (original === "default") {
|
|
1519
|
+
rewrittenManifest.componentConfigs[comp] = "default";
|
|
1520
|
+
continue;
|
|
1521
|
+
}
|
|
1522
|
+
const finalName = componentRenames[`${comp}/${original}`] ?? original;
|
|
1523
|
+
rewrittenManifest.componentConfigs[comp] = finalName;
|
|
1524
|
+
}
|
|
1525
|
+
rewrittenManifest.updatedAt = now;
|
|
1526
|
+
if (!rewrittenManifest.createdAt) rewrittenManifest.createdAt = now;
|
|
1527
|
+
const originalManifestName = sanitizeFileName(bundle.manifest.name || "imported");
|
|
1528
|
+
const finalManifestName = nextAvailableName2(
|
|
1529
|
+
(n) => manifestsResource.filePath(n),
|
|
1530
|
+
originalManifestName
|
|
1531
|
+
);
|
|
1532
|
+
if (finalManifestName !== originalManifestName) {
|
|
1533
|
+
renames[`manifest:${originalManifestName}`] = finalManifestName;
|
|
1534
|
+
}
|
|
1535
|
+
manifestsResource.ensureDir();
|
|
1536
|
+
import_fs2.default.writeFileSync(
|
|
1537
|
+
manifestsResource.filePath(finalManifestName),
|
|
1538
|
+
JSON.stringify(rewrittenManifest, null, 2)
|
|
1539
|
+
);
|
|
1540
|
+
jsonResponse(res, 200, {
|
|
1541
|
+
ok: true,
|
|
1542
|
+
manifest: finalManifestName,
|
|
1543
|
+
renames
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
940
1546
|
function methodNotAllowed({ res }) {
|
|
941
1547
|
jsonResponse(res, 405, { error: "Method not allowed" });
|
|
942
1548
|
}
|
|
@@ -974,11 +1580,21 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
974
1580
|
{ method: "GET", pattern: MANIFESTS_ROUTE, handler: handleListManifests },
|
|
975
1581
|
{ method: "GET", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleGetActiveManifest },
|
|
976
1582
|
{ method: "PUT", pattern: MANIFESTS_ACTIVE_ROUTE, handler: handleSetActiveManifest },
|
|
1583
|
+
// Manifests — exact import route runs before :name regexes
|
|
1584
|
+
{ method: "POST", pattern: MANIFEST_IMPORT_ROUTE, handler: handleImportManifest },
|
|
1585
|
+
{ method: "PUT", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1586
|
+
{ method: "GET", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
1587
|
+
{ method: "DELETE", pattern: MANIFEST_IMPORT_ROUTE, handler: methodNotAllowed },
|
|
977
1588
|
// Manifests — :name/apply (more specific than :name)
|
|
978
1589
|
{ method: "PUT", pattern: MANIFEST_APPLY_REGEX, handler: handleApplyManifest },
|
|
979
1590
|
{ method: "POST", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
980
1591
|
{ method: "GET", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
981
1592
|
{ method: "DELETE", pattern: MANIFEST_APPLY_REGEX, handler: methodNotAllowed },
|
|
1593
|
+
// Manifests — :name/export (more specific than :name)
|
|
1594
|
+
{ method: "GET", pattern: MANIFEST_EXPORT_REGEX, handler: handleExportManifest },
|
|
1595
|
+
{ method: "PUT", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1596
|
+
{ method: "POST", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
1597
|
+
{ method: "DELETE", pattern: MANIFEST_EXPORT_REGEX, handler: methodNotAllowed },
|
|
982
1598
|
// Manifests — :name CRUD (broadest manifest route, runs last)
|
|
983
1599
|
{ method: "GET", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
984
1600
|
{ method: "PUT", pattern: MANIFEST_BY_NAME_REGEX, handler: handleManifestByName },
|
|
@@ -998,6 +1614,7 @@ ${COMPONENT_OVERRIDES_END}
|
|
|
998
1614
|
ensureThemesDir();
|
|
999
1615
|
ensureComponentConfigsDir();
|
|
1000
1616
|
ensureManifestsDir();
|
|
1617
|
+
regenerateTokensCss();
|
|
1001
1618
|
server.middlewares.use(async (req, res, next) => {
|
|
1002
1619
|
const handled = await dispatch(req, res, routes);
|
|
1003
1620
|
if (!handled) next();
|