chaincss 2.1.38 → 2.2.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/ROADMAP.md +31 -0
- package/dist/cli/index.js +458 -3
- package/dist/compiler/analyzer.d.ts +12 -0
- package/dist/compiler/css-if-transpiler.d.ts +33 -0
- package/dist/compiler/design-orchestrator.d.ts +119 -0
- package/dist/compiler/intent-engine.d.ts +49 -0
- package/dist/compiler/math-engine.d.ts +89 -0
- package/dist/compiler/scroll-timeline.d.ts +91 -0
- package/dist/compiler/style-graph.d.ts +30 -0
- package/dist/core/compiler.d.ts +12 -0
- package/dist/core/types.d.ts +145 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +1765 -9
- package/dist/plugins/vite.js +451 -3
- package/package.json +1 -1
- package/src/compiler/analyzer.ts +62 -0
- package/src/compiler/css-if-transpiler.ts +117 -0
- package/src/compiler/design-orchestrator.ts +322 -0
- package/src/compiler/intent-engine.ts +402 -0
- package/src/compiler/math-engine.ts +511 -0
- package/src/compiler/scroll-timeline.ts +284 -0
- package/src/compiler/style-graph.ts +660 -0
- package/src/core/compiler.ts +40 -0
- package/src/core/types.ts +206 -0
- package/src/index.ts +103 -1
- package/demo/demo/node_modules/caniuse-db/fulldata-json/data-2.0.json +0 -1
- package/demo/index.html +0 -16
- package/demo/package.json +0 -20
- package/demo/src/App.tsx +0 -117
- package/demo/src/chaincss-barrel.ts +0 -9
- package/demo/src/main.tsx +0 -8
- package/demo/src/styles.chain.ts +0 -300
- package/demo/vite.config.ts +0 -46
package/dist/index.js
CHANGED
|
@@ -545,10 +545,10 @@ var init_shorthands = __esm({
|
|
|
545
545
|
c.overflowX = "hidden";
|
|
546
546
|
c.overflowY = "auto";
|
|
547
547
|
if (!c.nestedRules) c.nestedRules = [];
|
|
548
|
-
const
|
|
548
|
+
const scale2 = typeof v === "number" ? v : 2;
|
|
549
549
|
c.nestedRules.push({
|
|
550
550
|
selector: "& > *",
|
|
551
|
-
styles: { transform: `translateZ(-1px) scale(${
|
|
551
|
+
styles: { transform: `translateZ(-1px) scale(${scale2})` }
|
|
552
552
|
});
|
|
553
553
|
},
|
|
554
554
|
lineClamp: (v, c) => {
|
|
@@ -2371,7 +2371,7 @@ var init_Chain = __esm({
|
|
|
2371
2371
|
if (prop === "pressable") return () => this.macroHandler("pressable");
|
|
2372
2372
|
if (prop === "focusRing") return (color) => this.macroHandler("focusRing", color);
|
|
2373
2373
|
if (prop === "outlineDebug") return () => this.macroHandler("outlineDebug");
|
|
2374
|
-
if (prop === "parallax") return (
|
|
2374
|
+
if (prop === "parallax") return (scale2) => this.macroHandler("parallax", scale2);
|
|
2375
2375
|
if (prop === "lineClamp") return (lines) => this.macroHandler("lineClamp", lines);
|
|
2376
2376
|
if (prop === "frostedNav") return (blur) => this.macroHandler("frostedNav", blur);
|
|
2377
2377
|
if (prop === "gap") return (value) => this.setProperty("gap", value);
|
|
@@ -3545,7 +3545,7 @@ function withSmartStyles(WrappedComponent, styles) {
|
|
|
3545
3545
|
// src/core/compiler.ts
|
|
3546
3546
|
import fs6 from "fs";
|
|
3547
3547
|
import path5 from "path";
|
|
3548
|
-
import
|
|
3548
|
+
import crypto5 from "crypto";
|
|
3549
3549
|
import chalk2 from "chalk";
|
|
3550
3550
|
import { fileURLToPath, pathToFileURL } from "url";
|
|
3551
3551
|
|
|
@@ -6167,6 +6167,431 @@ var PersistentCache = class {
|
|
|
6167
6167
|
|
|
6168
6168
|
// src/core/compiler.ts
|
|
6169
6169
|
init_shorthands();
|
|
6170
|
+
|
|
6171
|
+
// src/compiler/style-graph.ts
|
|
6172
|
+
import crypto4 from "crypto";
|
|
6173
|
+
function calculateSpecificity(selector) {
|
|
6174
|
+
let a = 0;
|
|
6175
|
+
let b = 0;
|
|
6176
|
+
let c = 0;
|
|
6177
|
+
const idMatches = selector.match(/#[a-zA-Z0-9_-]+/g);
|
|
6178
|
+
if (idMatches) a += idMatches.length;
|
|
6179
|
+
const classMatches = selector.match(/\.[a-zA-Z0-9_-]+/g);
|
|
6180
|
+
if (classMatches) b += classMatches.length;
|
|
6181
|
+
const attrMatches = selector.match(/\[[^\]]+\]/g);
|
|
6182
|
+
if (attrMatches) b += attrMatches.length;
|
|
6183
|
+
const pseudoClassMatches = selector.match(/:[a-zA-Z-]+(?:\([^)]*\))?/g);
|
|
6184
|
+
if (pseudoClassMatches) {
|
|
6185
|
+
const notMatches = selector.match(/:not\(([^)]+)\)/g);
|
|
6186
|
+
const regularPseudoClasses = pseudoClassMatches.length - (notMatches?.length || 0);
|
|
6187
|
+
b += Math.max(0, regularPseudoClasses);
|
|
6188
|
+
}
|
|
6189
|
+
const elementMatches = selector.match(/^[a-zA-Z]+|[a-zA-Z]+(?=[.#[:])/g);
|
|
6190
|
+
if (elementMatches) c += elementMatches.length;
|
|
6191
|
+
return a * 1e4 + b * 100 + c;
|
|
6192
|
+
}
|
|
6193
|
+
function hashProperties(properties) {
|
|
6194
|
+
const sorted = Object.entries(properties).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${k}:${v}`).join(";");
|
|
6195
|
+
return crypto4.createHash("md5").update(sorted).digest("hex").slice(0, 8);
|
|
6196
|
+
}
|
|
6197
|
+
function kebab2(prop) {
|
|
6198
|
+
return prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
6199
|
+
}
|
|
6200
|
+
var StyleGraphBuilder = class {
|
|
6201
|
+
entries = [];
|
|
6202
|
+
nodes = /* @__PURE__ */ new Map();
|
|
6203
|
+
edges = [];
|
|
6204
|
+
orderCounter = 0;
|
|
6205
|
+
addEntry(entry) {
|
|
6206
|
+
entry.sourceOrder = this.orderCounter++;
|
|
6207
|
+
this.entries.push(entry);
|
|
6208
|
+
}
|
|
6209
|
+
build() {
|
|
6210
|
+
this.nodes.clear();
|
|
6211
|
+
this.edges = [];
|
|
6212
|
+
for (const entry of this.entries) {
|
|
6213
|
+
const id = `node-${this.nodes.size}`;
|
|
6214
|
+
const node = {
|
|
6215
|
+
id,
|
|
6216
|
+
selector: entry.selector,
|
|
6217
|
+
properties: entry.properties,
|
|
6218
|
+
specificity: calculateSpecificity(entry.selector),
|
|
6219
|
+
dependencies: [],
|
|
6220
|
+
dependents: [],
|
|
6221
|
+
mediaQuery: entry.mediaQuery,
|
|
6222
|
+
isDead: false,
|
|
6223
|
+
hash: hashProperties(entry.properties),
|
|
6224
|
+
sourceComponent: entry.sourceComponent
|
|
6225
|
+
};
|
|
6226
|
+
this.nodes.set(id, node);
|
|
6227
|
+
}
|
|
6228
|
+
const nodeArray = Array.from(this.nodes.values());
|
|
6229
|
+
for (let i = 0; i < nodeArray.length; i++) {
|
|
6230
|
+
for (let j = i + 1; j < nodeArray.length; j++) {
|
|
6231
|
+
const a = nodeArray[i];
|
|
6232
|
+
const b = nodeArray[j];
|
|
6233
|
+
if (this.selectorsOverlap(a.selector, b.selector)) {
|
|
6234
|
+
if (a.specificity <= b.specificity) {
|
|
6235
|
+
this.edges.push({ from: a.id, to: b.id, type: "overrides" });
|
|
6236
|
+
a.dependents.push(b.id);
|
|
6237
|
+
b.dependencies.push(a.id);
|
|
6238
|
+
}
|
|
6239
|
+
if (b.specificity <= a.specificity) {
|
|
6240
|
+
this.edges.push({ from: b.id, to: a.id, type: "overrides" });
|
|
6241
|
+
b.dependents.push(a.id);
|
|
6242
|
+
a.dependencies.push(b.id);
|
|
6243
|
+
}
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
const rootNodes = nodeArray.filter((n) => n.dependencies.length === 0).map((n) => n.id);
|
|
6248
|
+
const leafNodes = nodeArray.filter((n) => n.dependents.length === 0).map((n) => n.id);
|
|
6249
|
+
return {
|
|
6250
|
+
nodes: this.nodes,
|
|
6251
|
+
edges: this.edges,
|
|
6252
|
+
rootNodes,
|
|
6253
|
+
leafNodes
|
|
6254
|
+
};
|
|
6255
|
+
}
|
|
6256
|
+
selectorsOverlap(a, b) {
|
|
6257
|
+
const partsA = a.split(/[\s>+~]+/).filter(Boolean);
|
|
6258
|
+
const partsB = b.split(/[\s>+~]+/).filter(Boolean);
|
|
6259
|
+
for (const pa of partsA) {
|
|
6260
|
+
for (const pb of partsB) {
|
|
6261
|
+
if (pa === pb) return true;
|
|
6262
|
+
if (pa.startsWith(".") && pb.startsWith(".") && pa === pb) return true;
|
|
6263
|
+
}
|
|
6264
|
+
}
|
|
6265
|
+
return false;
|
|
6266
|
+
}
|
|
6267
|
+
};
|
|
6268
|
+
function eliminateDeadStyles(graph, knownSelectors) {
|
|
6269
|
+
if (knownSelectors.length === 0) {
|
|
6270
|
+
return { eliminated: 0, graph };
|
|
6271
|
+
}
|
|
6272
|
+
const reachable = /* @__PURE__ */ new Set();
|
|
6273
|
+
const queue = [];
|
|
6274
|
+
for (const [id, node] of graph.nodes) {
|
|
6275
|
+
if (knownSelectors.some((ks) => node.selector.includes(ks) || ks.includes(node.selector))) {
|
|
6276
|
+
reachable.add(id);
|
|
6277
|
+
queue.push(id);
|
|
6278
|
+
}
|
|
6279
|
+
}
|
|
6280
|
+
while (queue.length > 0) {
|
|
6281
|
+
const current = queue.shift();
|
|
6282
|
+
const node = graph.nodes.get(current);
|
|
6283
|
+
if (!node) continue;
|
|
6284
|
+
for (const depId of node.dependents) {
|
|
6285
|
+
if (!reachable.has(depId)) {
|
|
6286
|
+
reachable.add(depId);
|
|
6287
|
+
queue.push(depId);
|
|
6288
|
+
}
|
|
6289
|
+
}
|
|
6290
|
+
}
|
|
6291
|
+
let eliminated = 0;
|
|
6292
|
+
for (const [id, node] of graph.nodes) {
|
|
6293
|
+
if (!reachable.has(id)) {
|
|
6294
|
+
node.isDead = true;
|
|
6295
|
+
eliminated++;
|
|
6296
|
+
}
|
|
6297
|
+
}
|
|
6298
|
+
return { eliminated, graph };
|
|
6299
|
+
}
|
|
6300
|
+
function mergeIdenticalRules(graph, threshold) {
|
|
6301
|
+
const hashGroups = /* @__PURE__ */ new Map();
|
|
6302
|
+
for (const [, node] of graph.nodes) {
|
|
6303
|
+
if (node.isDead) continue;
|
|
6304
|
+
if (Object.keys(node.properties).length < threshold) continue;
|
|
6305
|
+
const existing = hashGroups.get(node.hash) || [];
|
|
6306
|
+
existing.push(node);
|
|
6307
|
+
hashGroups.set(node.hash, existing);
|
|
6308
|
+
}
|
|
6309
|
+
let merged = 0;
|
|
6310
|
+
for (const [, group] of hashGroups) {
|
|
6311
|
+
if (group.length < 2) continue;
|
|
6312
|
+
const mergedSelector = group.map((n) => n.selector).join(", ");
|
|
6313
|
+
const primary = group[0];
|
|
6314
|
+
primary.selector = mergedSelector;
|
|
6315
|
+
for (let i = 1; i < group.length; i++) {
|
|
6316
|
+
group[i].isDead = true;
|
|
6317
|
+
merged++;
|
|
6318
|
+
}
|
|
6319
|
+
}
|
|
6320
|
+
return { merged, graph };
|
|
6321
|
+
}
|
|
6322
|
+
function topologicalSort(graph) {
|
|
6323
|
+
const visited = /* @__PURE__ */ new Set();
|
|
6324
|
+
const sorted = [];
|
|
6325
|
+
const visiting = /* @__PURE__ */ new Set();
|
|
6326
|
+
function visit(id) {
|
|
6327
|
+
if (visited.has(id)) return true;
|
|
6328
|
+
if (visiting.has(id)) return false;
|
|
6329
|
+
visiting.add(id);
|
|
6330
|
+
const node = graph.nodes.get(id);
|
|
6331
|
+
if (node) {
|
|
6332
|
+
for (const depId of node.dependencies) {
|
|
6333
|
+
if (!visit(depId)) return false;
|
|
6334
|
+
}
|
|
6335
|
+
}
|
|
6336
|
+
visiting.delete(id);
|
|
6337
|
+
visited.add(id);
|
|
6338
|
+
if (node && !node.isDead) {
|
|
6339
|
+
sorted.push(node);
|
|
6340
|
+
}
|
|
6341
|
+
return true;
|
|
6342
|
+
}
|
|
6343
|
+
for (const id of graph.rootNodes) {
|
|
6344
|
+
if (!visit(id)) {
|
|
6345
|
+
return Array.from(graph.nodes.values()).filter((n) => !n.isDead).sort((a, b) => a.sourceComponent?.localeCompare(b.sourceComponent || "") || 0);
|
|
6346
|
+
}
|
|
6347
|
+
}
|
|
6348
|
+
for (const [id] of graph.nodes) {
|
|
6349
|
+
if (!visited.has(id)) {
|
|
6350
|
+
visit(id);
|
|
6351
|
+
}
|
|
6352
|
+
}
|
|
6353
|
+
return sorted;
|
|
6354
|
+
}
|
|
6355
|
+
function generateCSSFromGraph(graph, sortOutput = "specificity") {
|
|
6356
|
+
let nodes;
|
|
6357
|
+
switch (sortOutput) {
|
|
6358
|
+
case "specificity":
|
|
6359
|
+
nodes = Array.from(graph.nodes.values()).filter((n) => !n.isDead).sort((a, b) => a.specificity - b.specificity);
|
|
6360
|
+
break;
|
|
6361
|
+
case "topological":
|
|
6362
|
+
nodes = topologicalSort(graph);
|
|
6363
|
+
break;
|
|
6364
|
+
case "source-order":
|
|
6365
|
+
default:
|
|
6366
|
+
nodes = Array.from(graph.nodes.values()).filter((n) => !n.isDead);
|
|
6367
|
+
break;
|
|
6368
|
+
}
|
|
6369
|
+
let css = "";
|
|
6370
|
+
let currentMediaQuery;
|
|
6371
|
+
for (const node of nodes) {
|
|
6372
|
+
if (node.isDead) continue;
|
|
6373
|
+
if (node.mediaQuery !== currentMediaQuery) {
|
|
6374
|
+
if (currentMediaQuery) {
|
|
6375
|
+
css += "}\n\n";
|
|
6376
|
+
}
|
|
6377
|
+
if (node.mediaQuery) {
|
|
6378
|
+
css += `@media ${node.mediaQuery} {
|
|
6379
|
+
`;
|
|
6380
|
+
}
|
|
6381
|
+
currentMediaQuery = node.mediaQuery;
|
|
6382
|
+
}
|
|
6383
|
+
const rules = Object.entries(node.properties).map(([prop, value]) => ` ${kebab2(prop)}: ${value};`).join("\n");
|
|
6384
|
+
if (rules) {
|
|
6385
|
+
css += `${node.selector} {
|
|
6386
|
+
${rules}
|
|
6387
|
+
}
|
|
6388
|
+
`;
|
|
6389
|
+
}
|
|
6390
|
+
}
|
|
6391
|
+
if (currentMediaQuery) {
|
|
6392
|
+
css += "}\n";
|
|
6393
|
+
}
|
|
6394
|
+
return css;
|
|
6395
|
+
}
|
|
6396
|
+
var StyleGraphCompiler = class {
|
|
6397
|
+
options;
|
|
6398
|
+
constructor(options = {}) {
|
|
6399
|
+
this.options = {
|
|
6400
|
+
eliminateDead: options.eliminateDead ?? false,
|
|
6401
|
+
knownSelectors: options.knownSelectors ?? [],
|
|
6402
|
+
mergeIdentical: options.mergeIdentical ?? false,
|
|
6403
|
+
mergeThreshold: options.mergeThreshold ?? 3,
|
|
6404
|
+
sortOutput: options.sortOutput ?? "specificity",
|
|
6405
|
+
verbose: options.verbose ?? false
|
|
6406
|
+
};
|
|
6407
|
+
}
|
|
6408
|
+
/**
|
|
6409
|
+
* Compile a set of style definitions through the graph compiler.
|
|
6410
|
+
*/
|
|
6411
|
+
compile(styles) {
|
|
6412
|
+
const startTime = Date.now();
|
|
6413
|
+
const builder = new StyleGraphBuilder();
|
|
6414
|
+
let preOptimizationSize = 0;
|
|
6415
|
+
for (const [componentName, styleDef] of Object.entries(styles)) {
|
|
6416
|
+
if (!styleDef || !styleDef.selectors) continue;
|
|
6417
|
+
for (const selector of styleDef.selectors) {
|
|
6418
|
+
const properties = {};
|
|
6419
|
+
for (const [prop, value] of Object.entries(styleDef)) {
|
|
6420
|
+
if (prop === "selectors" || prop === "atRules" || prop === "nestedRules" || prop === "hover" || prop === "themes" || prop.startsWith("_")) {
|
|
6421
|
+
continue;
|
|
6422
|
+
}
|
|
6423
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
6424
|
+
properties[prop] = String(value);
|
|
6425
|
+
preOptimizationSize += String(value).length + prop.length;
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
if (Object.keys(properties).length > 0) {
|
|
6429
|
+
builder.addEntry({
|
|
6430
|
+
selector,
|
|
6431
|
+
properties,
|
|
6432
|
+
sourceComponent: componentName,
|
|
6433
|
+
sourceOrder: 0
|
|
6434
|
+
});
|
|
6435
|
+
}
|
|
6436
|
+
if (styleDef.hover && typeof styleDef.hover === "object") {
|
|
6437
|
+
const hoverProperties = {};
|
|
6438
|
+
for (const [prop, value] of Object.entries(styleDef.hover)) {
|
|
6439
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
6440
|
+
hoverProperties[prop] = String(value);
|
|
6441
|
+
}
|
|
6442
|
+
}
|
|
6443
|
+
if (Object.keys(hoverProperties).length > 0) {
|
|
6444
|
+
builder.addEntry({
|
|
6445
|
+
selector: `${selector}:hover`,
|
|
6446
|
+
properties: hoverProperties,
|
|
6447
|
+
sourceComponent: componentName,
|
|
6448
|
+
sourceOrder: 0
|
|
6449
|
+
});
|
|
6450
|
+
}
|
|
6451
|
+
}
|
|
6452
|
+
if (styleDef.atRules) {
|
|
6453
|
+
for (const rule of styleDef.atRules) {
|
|
6454
|
+
if (rule.type === "media" && rule.styles && rule.query) {
|
|
6455
|
+
const mediaProperties = {};
|
|
6456
|
+
for (const [prop, value] of Object.entries(rule.styles)) {
|
|
6457
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
6458
|
+
mediaProperties[prop] = String(value);
|
|
6459
|
+
}
|
|
6460
|
+
}
|
|
6461
|
+
if (Object.keys(mediaProperties).length > 0) {
|
|
6462
|
+
builder.addEntry({
|
|
6463
|
+
selector,
|
|
6464
|
+
properties: mediaProperties,
|
|
6465
|
+
sourceComponent: componentName,
|
|
6466
|
+
sourceOrder: 0,
|
|
6467
|
+
mediaQuery: rule.query
|
|
6468
|
+
});
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
}
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
}
|
|
6475
|
+
let graph = builder.build();
|
|
6476
|
+
let eliminatedDead = 0;
|
|
6477
|
+
if (this.options.eliminateDead && this.options.knownSelectors.length > 0) {
|
|
6478
|
+
const result = eliminateDeadStyles(graph, this.options.knownSelectors);
|
|
6479
|
+
eliminatedDead = result.eliminated;
|
|
6480
|
+
graph = result.graph;
|
|
6481
|
+
}
|
|
6482
|
+
let mergedRules = 0;
|
|
6483
|
+
if (this.options.mergeIdentical) {
|
|
6484
|
+
const result = mergeIdenticalRules(graph, this.options.mergeThreshold);
|
|
6485
|
+
mergedRules = result.merged;
|
|
6486
|
+
graph = result.graph;
|
|
6487
|
+
}
|
|
6488
|
+
const css = generateCSSFromGraph(graph, this.options.sortOutput);
|
|
6489
|
+
let postOptimizationSize = css.length;
|
|
6490
|
+
if (postOptimizationSize === 0) {
|
|
6491
|
+
postOptimizationSize = preOptimizationSize;
|
|
6492
|
+
}
|
|
6493
|
+
const classMap = {};
|
|
6494
|
+
for (const [, node] of graph.nodes) {
|
|
6495
|
+
if (!node.isDead && node.sourceComponent) {
|
|
6496
|
+
if (classMap[node.sourceComponent]) {
|
|
6497
|
+
classMap[node.sourceComponent] += ` ${node.selector.replace(/^\./, "")}`;
|
|
6498
|
+
} else {
|
|
6499
|
+
classMap[node.sourceComponent] = node.selector.replace(/^\./, "");
|
|
6500
|
+
}
|
|
6501
|
+
}
|
|
6502
|
+
}
|
|
6503
|
+
const totalNodes = graph.nodes.size;
|
|
6504
|
+
const aliveNodes = totalNodes - eliminatedDead;
|
|
6505
|
+
const savingsPercent = preOptimizationSize > 0 ? `${((preOptimizationSize - postOptimizationSize) / preOptimizationSize * 100).toFixed(1)}%` : "0%";
|
|
6506
|
+
const stats = {
|
|
6507
|
+
totalStyles: totalNodes,
|
|
6508
|
+
atomicStyles: 0,
|
|
6509
|
+
uniqueProperties: new Set(
|
|
6510
|
+
Array.from(graph.nodes.values()).filter((n) => !n.isDead).flatMap((n) => Object.keys(n.properties))
|
|
6511
|
+
).size,
|
|
6512
|
+
savings: savingsPercent,
|
|
6513
|
+
compileTime: Date.now() - startTime
|
|
6514
|
+
};
|
|
6515
|
+
return {
|
|
6516
|
+
css,
|
|
6517
|
+
classMap,
|
|
6518
|
+
atomicClasses: [],
|
|
6519
|
+
stats,
|
|
6520
|
+
graph,
|
|
6521
|
+
eliminatedDead,
|
|
6522
|
+
mergedRules,
|
|
6523
|
+
optimizationTime: Date.now() - startTime,
|
|
6524
|
+
preOptimizationSize,
|
|
6525
|
+
postOptimizationSize
|
|
6526
|
+
};
|
|
6527
|
+
}
|
|
6528
|
+
/**
|
|
6529
|
+
* Analyze a style graph without generating CSS.
|
|
6530
|
+
*/
|
|
6531
|
+
analyze(styles) {
|
|
6532
|
+
const builder = new StyleGraphBuilder();
|
|
6533
|
+
for (const [componentName, styleDef] of Object.entries(styles)) {
|
|
6534
|
+
if (!styleDef || !styleDef.selectors) continue;
|
|
6535
|
+
for (const selector of styleDef.selectors) {
|
|
6536
|
+
const properties = {};
|
|
6537
|
+
for (const [prop, value] of Object.entries(styleDef)) {
|
|
6538
|
+
if (prop === "selectors" || prop.startsWith("_")) continue;
|
|
6539
|
+
if (typeof value === "string" || typeof value === "number") {
|
|
6540
|
+
properties[prop] = String(value);
|
|
6541
|
+
}
|
|
6542
|
+
}
|
|
6543
|
+
if (Object.keys(properties).length > 0) {
|
|
6544
|
+
builder.addEntry({ selector, properties, sourceComponent: componentName, sourceOrder: 0 });
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
}
|
|
6548
|
+
return builder.build();
|
|
6549
|
+
}
|
|
6550
|
+
/**
|
|
6551
|
+
* Get optimization statistics for a graph.
|
|
6552
|
+
*/
|
|
6553
|
+
getStats(graph) {
|
|
6554
|
+
const nodes = Array.from(graph.nodes.values());
|
|
6555
|
+
const deadNodes = nodes.filter((n) => n.isDead).length;
|
|
6556
|
+
const averageSpecificity = nodes.length > 0 ? nodes.reduce((sum, n) => sum + n.specificity, 0) / nodes.length : 0;
|
|
6557
|
+
let maxDepth = 0;
|
|
6558
|
+
const depths = /* @__PURE__ */ new Map();
|
|
6559
|
+
function getDepth(id) {
|
|
6560
|
+
if (depths.has(id)) return depths.get(id);
|
|
6561
|
+
const node = graph.nodes.get(id);
|
|
6562
|
+
if (!node || node.dependencies.length === 0) {
|
|
6563
|
+
depths.set(id, 0);
|
|
6564
|
+
return 0;
|
|
6565
|
+
}
|
|
6566
|
+
const max = Math.max(...node.dependencies.map((d) => getDepth(d)));
|
|
6567
|
+
const depth = max + 1;
|
|
6568
|
+
depths.set(id, depth);
|
|
6569
|
+
return depth;
|
|
6570
|
+
}
|
|
6571
|
+
for (const [id] of graph.nodes) {
|
|
6572
|
+
maxDepth = Math.max(maxDepth, getDepth(id));
|
|
6573
|
+
}
|
|
6574
|
+
return {
|
|
6575
|
+
totalNodes: nodes.length,
|
|
6576
|
+
deadNodes,
|
|
6577
|
+
mergedGroups: 0,
|
|
6578
|
+
averageSpecificity: Math.round(averageSpecificity * 100) / 100,
|
|
6579
|
+
deepestDependencyChain: maxDepth
|
|
6580
|
+
};
|
|
6581
|
+
}
|
|
6582
|
+
/**
|
|
6583
|
+
* Update options.
|
|
6584
|
+
*/
|
|
6585
|
+
configure(options) {
|
|
6586
|
+
this.options = { ...this.options, ...options };
|
|
6587
|
+
}
|
|
6588
|
+
};
|
|
6589
|
+
function compileGraph(styles, options) {
|
|
6590
|
+
const compiler = new StyleGraphCompiler(options);
|
|
6591
|
+
return compiler.compile(styles);
|
|
6592
|
+
}
|
|
6593
|
+
|
|
6594
|
+
// src/core/compiler.ts
|
|
6170
6595
|
var __filename = typeof import.meta !== "undefined" ? (() => {
|
|
6171
6596
|
try {
|
|
6172
6597
|
return fileURLToPath(import.meta.url);
|
|
@@ -6217,6 +6642,35 @@ var ChainCSSCompiler = class {
|
|
|
6217
6642
|
this.initOptimizer();
|
|
6218
6643
|
this.initPrefixer();
|
|
6219
6644
|
}
|
|
6645
|
+
/**
|
|
6646
|
+
* Compile using the style graph compiler for advanced optimizations.
|
|
6647
|
+
*
|
|
6648
|
+
* @example
|
|
6649
|
+
* const result = compiler.compileWithGraph(styles, {
|
|
6650
|
+
* eliminateDead: true,
|
|
6651
|
+
* knownSelectors: ['.header', '.footer'],
|
|
6652
|
+
* mergeIdentical: true
|
|
6653
|
+
* });
|
|
6654
|
+
*/
|
|
6655
|
+
compileWithGraph(styles, options) {
|
|
6656
|
+
const graphCompiler = new StyleGraphCompiler({
|
|
6657
|
+
...options,
|
|
6658
|
+
verbose: this.config.verbose
|
|
6659
|
+
});
|
|
6660
|
+
const result = graphCompiler.compile(styles);
|
|
6661
|
+
if (this.config.verbose) {
|
|
6662
|
+
if (result.eliminatedDead > 0) {
|
|
6663
|
+
console.log(` \u{1F9F9} Eliminated ${result.eliminatedDead} dead styles`);
|
|
6664
|
+
}
|
|
6665
|
+
if (result.mergedRules > 0) {
|
|
6666
|
+
console.log(` \u{1F517} Merged ${result.mergedRules} identical rules`);
|
|
6667
|
+
}
|
|
6668
|
+
if (result.optimizationTime > 0) {
|
|
6669
|
+
console.log(` \u26A1 Graph compilation: ${result.optimizationTime}ms`);
|
|
6670
|
+
}
|
|
6671
|
+
}
|
|
6672
|
+
return result;
|
|
6673
|
+
}
|
|
6220
6674
|
hasStyles() {
|
|
6221
6675
|
const combined = this.getCombinedCSS();
|
|
6222
6676
|
return !!(combined && combined.trim().length > 0);
|
|
@@ -6254,7 +6708,7 @@ var ChainCSSCompiler = class {
|
|
|
6254
6708
|
if (result.css && result.css.trim()) {
|
|
6255
6709
|
this.accumulatedCSS += result.css + "\n";
|
|
6256
6710
|
}
|
|
6257
|
-
const cacheKey =
|
|
6711
|
+
const cacheKey = crypto5.createHash("sha256").update(`${componentName}-${JSON.stringify(styleObj)}`).digest("hex").slice(0, 16);
|
|
6258
6712
|
this.addToCache(cacheKey, {
|
|
6259
6713
|
result: {
|
|
6260
6714
|
css: result.css || "",
|
|
@@ -6382,7 +6836,7 @@ var ChainCSSCompiler = class {
|
|
|
6382
6836
|
// ============================================================================
|
|
6383
6837
|
hashStyleDef(styleDef) {
|
|
6384
6838
|
const { _componentName, _generateComponent, _framework, _propsDefinition, ...relevant } = styleDef;
|
|
6385
|
-
return
|
|
6839
|
+
return crypto5.createHash("sha256").update(JSON.stringify(relevant)).digest("hex").slice(0, 16);
|
|
6386
6840
|
}
|
|
6387
6841
|
async importModule(filePath) {
|
|
6388
6842
|
const absolutePath = path5.resolve(filePath);
|
|
@@ -6810,7 +7264,7 @@ function createThemeContract(contractShape) {
|
|
|
6810
7264
|
}
|
|
6811
7265
|
function validateTheme(contract, theme = {}, path6 = "") {
|
|
6812
7266
|
const errors = [];
|
|
6813
|
-
function
|
|
7267
|
+
function validate2(contractPart, themePart, currentPath) {
|
|
6814
7268
|
if (typeof contractPart === "object" && contractPart !== null) {
|
|
6815
7269
|
const requiredKeys = Object.keys(contractPart);
|
|
6816
7270
|
const themeKeys = Object.keys(themePart || {});
|
|
@@ -6819,7 +7273,7 @@ function validateTheme(contract, theme = {}, path6 = "") {
|
|
|
6819
7273
|
if (!themePart || !(key in themePart)) {
|
|
6820
7274
|
errors.push(` \u2717 Missing required token: "${newPath}"`);
|
|
6821
7275
|
} else {
|
|
6822
|
-
|
|
7276
|
+
validate2(
|
|
6823
7277
|
contractPart[key],
|
|
6824
7278
|
themePart[key],
|
|
6825
7279
|
newPath
|
|
@@ -6838,7 +7292,7 @@ function validateTheme(contract, theme = {}, path6 = "") {
|
|
|
6838
7292
|
}
|
|
6839
7293
|
}
|
|
6840
7294
|
}
|
|
6841
|
-
|
|
7295
|
+
validate2(contract, theme, path6);
|
|
6842
7296
|
if (errors.length > 0) {
|
|
6843
7297
|
throw new Error(`Theme Contract Validation Failed:
|
|
6844
7298
|
${errors.join("\n")}`);
|
|
@@ -6876,7 +7330,1277 @@ init_shorthands();
|
|
|
6876
7330
|
init_helpers();
|
|
6877
7331
|
init_animations();
|
|
6878
7332
|
init_suggestions();
|
|
7333
|
+
|
|
7334
|
+
// src/compiler/design-orchestrator.ts
|
|
7335
|
+
function parseColor(color) {
|
|
7336
|
+
const trimmed = color.trim().toLowerCase();
|
|
7337
|
+
const hexMatch = trimmed.match(/^#([a-f0-9]{3}|[a-f0-9]{6}|[a-f0-9]{8})$/);
|
|
7338
|
+
if (hexMatch) {
|
|
7339
|
+
let hex = hexMatch[1];
|
|
7340
|
+
if (hex.length === 3) hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
|
|
7341
|
+
if (hex.length === 8) {
|
|
7342
|
+
return {
|
|
7343
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
7344
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
7345
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
7346
|
+
a: parseInt(hex.slice(6, 8), 16) / 255
|
|
7347
|
+
};
|
|
7348
|
+
}
|
|
7349
|
+
return {
|
|
7350
|
+
r: parseInt(hex.slice(0, 2), 16),
|
|
7351
|
+
g: parseInt(hex.slice(2, 4), 16),
|
|
7352
|
+
b: parseInt(hex.slice(4, 6), 16),
|
|
7353
|
+
a: 1
|
|
7354
|
+
};
|
|
7355
|
+
}
|
|
7356
|
+
const rgbMatch = trimmed.match(/^rgba?\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*(?:,\s*([\d.]+))?\s*\)$/);
|
|
7357
|
+
if (rgbMatch) {
|
|
7358
|
+
return {
|
|
7359
|
+
r: parseInt(rgbMatch[1]),
|
|
7360
|
+
g: parseInt(rgbMatch[2]),
|
|
7361
|
+
b: parseInt(rgbMatch[3]),
|
|
7362
|
+
a: rgbMatch[4] ? parseFloat(rgbMatch[4]) : 1
|
|
7363
|
+
};
|
|
7364
|
+
}
|
|
7365
|
+
const named = {
|
|
7366
|
+
white: [255, 255, 255],
|
|
7367
|
+
black: [0, 0, 0],
|
|
7368
|
+
red: [255, 0, 0],
|
|
7369
|
+
green: [0, 128, 0],
|
|
7370
|
+
blue: [0, 0, 255],
|
|
7371
|
+
gray: [128, 128, 128],
|
|
7372
|
+
grey: [128, 128, 128],
|
|
7373
|
+
transparent: [0, 0, 0]
|
|
7374
|
+
};
|
|
7375
|
+
if (named[trimmed]) {
|
|
7376
|
+
const [r, g, b] = named[trimmed];
|
|
7377
|
+
return { r, g, b, a: trimmed === "transparent" ? 0 : 1 };
|
|
7378
|
+
}
|
|
7379
|
+
return null;
|
|
7380
|
+
}
|
|
7381
|
+
function relativeLuminance(r, g, b) {
|
|
7382
|
+
const rsrgb = r / 255;
|
|
7383
|
+
const gsrgb = g / 255;
|
|
7384
|
+
const bsrgb = b / 255;
|
|
7385
|
+
const rLin = rsrgb <= 0.04045 ? rsrgb / 12.92 : Math.pow((rsrgb + 0.055) / 1.055, 2.4);
|
|
7386
|
+
const gLin = gsrgb <= 0.04045 ? gsrgb / 12.92 : Math.pow((gsrgb + 0.055) / 1.055, 2.4);
|
|
7387
|
+
const bLin = bsrgb <= 0.04045 ? bsrgb / 12.92 : Math.pow((bsrgb + 0.055) / 1.055, 2.4);
|
|
7388
|
+
return 0.2126 * rLin + 0.7152 * gLin + 0.0722 * bLin;
|
|
7389
|
+
}
|
|
7390
|
+
function contrastRatio(foreground, background) {
|
|
7391
|
+
const fg = parseColor(foreground);
|
|
7392
|
+
const bg = parseColor(background);
|
|
7393
|
+
if (!fg || !bg) return -1;
|
|
7394
|
+
const lumFg = relativeLuminance(fg.r, fg.g, fg.b) + 0.05;
|
|
7395
|
+
const lumBg = relativeLuminance(bg.r, bg.g, bg.b) + 0.05;
|
|
7396
|
+
const lighter = Math.max(lumFg, lumBg);
|
|
7397
|
+
const darker = Math.min(lumFg, lumBg);
|
|
7398
|
+
return lighter / darker;
|
|
7399
|
+
}
|
|
7400
|
+
function checkContrast(foreground, background) {
|
|
7401
|
+
const ratio = contrastRatio(foreground, background);
|
|
7402
|
+
return {
|
|
7403
|
+
foreground,
|
|
7404
|
+
background,
|
|
7405
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
7406
|
+
passes: {
|
|
7407
|
+
AA: ratio >= 4.5,
|
|
7408
|
+
AALarge: ratio >= 3,
|
|
7409
|
+
AAA: ratio >= 7,
|
|
7410
|
+
AAALarge: ratio >= 4.5
|
|
7411
|
+
},
|
|
7412
|
+
suggestion: ratio < 4.5 ? `Contrast ratio ${Math.round(ratio * 100) / 100} fails AA. Need ${Math.round((4.5 - ratio) * 100) / 100} more. Consider darkening/lightening.` : void 0
|
|
7413
|
+
};
|
|
7414
|
+
}
|
|
7415
|
+
function auditContrast(styles) {
|
|
7416
|
+
const checks = [];
|
|
7417
|
+
for (const style of styles) {
|
|
7418
|
+
if (style.color && style.backgroundColor) {
|
|
7419
|
+
checks.push(checkContrast(style.color, style.backgroundColor));
|
|
7420
|
+
}
|
|
7421
|
+
}
|
|
7422
|
+
const failures = checks.filter((c) => !c.passes.AA);
|
|
7423
|
+
const warnings = checks.filter((c) => c.passes.AA && !c.passes.AAA);
|
|
7424
|
+
return {
|
|
7425
|
+
checks,
|
|
7426
|
+
failures,
|
|
7427
|
+
warnings,
|
|
7428
|
+
passCount: checks.length - failures.length,
|
|
7429
|
+
failCount: failures.length,
|
|
7430
|
+
summary: failures.length === 0 ? "All " + checks.length + " contrast checks pass AA." : failures.length + " of " + checks.length + " contrast checks FAIL AA."
|
|
7431
|
+
};
|
|
7432
|
+
}
|
|
7433
|
+
function createContextualToken(defaultValue, contexts = {}) {
|
|
7434
|
+
const name = "ctx-" + Math.random().toString(36).slice(2, 8);
|
|
7435
|
+
return { name, default: defaultValue, contexts };
|
|
7436
|
+
}
|
|
7437
|
+
function resolveContextual(token, selectorPath) {
|
|
7438
|
+
let bestMatch = token.default;
|
|
7439
|
+
let bestLength = 0;
|
|
7440
|
+
for (const [context, value] of Object.entries(token.contexts)) {
|
|
7441
|
+
if (selectorPath.includes(context) && context.length > bestLength) {
|
|
7442
|
+
bestMatch = value;
|
|
7443
|
+
bestLength = context.length;
|
|
7444
|
+
}
|
|
7445
|
+
}
|
|
7446
|
+
return bestMatch;
|
|
7447
|
+
}
|
|
7448
|
+
function generateContextualCSS(propertyName, token, baseSelector) {
|
|
7449
|
+
let css = "";
|
|
7450
|
+
css += baseSelector + " { " + propertyName + ": " + token.default + "; }\n";
|
|
7451
|
+
for (const [context, value] of Object.entries(token.contexts)) {
|
|
7452
|
+
css += context + " " + baseSelector + " { " + propertyName + ": " + value + "; }\n";
|
|
7453
|
+
}
|
|
7454
|
+
return css;
|
|
7455
|
+
}
|
|
7456
|
+
function validateTokenRelationships(tokens3, pairs) {
|
|
7457
|
+
const styles = [];
|
|
7458
|
+
for (const pair of pairs) {
|
|
7459
|
+
const fg = resolveTokenPath2(tokens3, pair.foreground);
|
|
7460
|
+
const bg = resolveTokenPath2(tokens3, pair.background);
|
|
7461
|
+
if (fg && bg) {
|
|
7462
|
+
styles.push({ selector: pair.label, color: fg, backgroundColor: bg });
|
|
7463
|
+
}
|
|
7464
|
+
}
|
|
7465
|
+
return auditContrast(styles);
|
|
7466
|
+
}
|
|
7467
|
+
function resolveTokenPath2(tokens3, path6) {
|
|
7468
|
+
const parts = path6.split(".");
|
|
7469
|
+
let current = tokens3;
|
|
7470
|
+
for (const part of parts) {
|
|
7471
|
+
if (current === void 0 || current === null) return null;
|
|
7472
|
+
current = current[part];
|
|
7473
|
+
}
|
|
7474
|
+
return typeof current === "string" ? current : null;
|
|
7475
|
+
}
|
|
7476
|
+
var orchestrator = {
|
|
7477
|
+
contrastRatio,
|
|
7478
|
+
checkContrast,
|
|
7479
|
+
auditContrast,
|
|
7480
|
+
createContextualToken,
|
|
7481
|
+
resolveContextual,
|
|
7482
|
+
generateContextualCSS,
|
|
7483
|
+
validateTokenRelationships,
|
|
7484
|
+
parseColor
|
|
7485
|
+
};
|
|
7486
|
+
|
|
7487
|
+
// src/compiler/scroll-timeline.ts
|
|
7488
|
+
var SCROLL_PRESETS = {
|
|
7489
|
+
fadeIn: {
|
|
7490
|
+
selector: "",
|
|
7491
|
+
timeline: { name: "fade-in", source: "view", range: "entry" },
|
|
7492
|
+
keyframes: [
|
|
7493
|
+
{ offset: "0%", properties: { opacity: "0", transform: "translateY(20px)" } },
|
|
7494
|
+
{ offset: "100%", properties: { opacity: "1", transform: "translateY(0)" } }
|
|
7495
|
+
]
|
|
7496
|
+
},
|
|
7497
|
+
fadeOut: {
|
|
7498
|
+
selector: "",
|
|
7499
|
+
timeline: { name: "fade-out", source: "view", range: "exit" },
|
|
7500
|
+
keyframes: [
|
|
7501
|
+
{ offset: "0%", properties: { opacity: "1" } },
|
|
7502
|
+
{ offset: "100%", properties: { opacity: "0" } }
|
|
7503
|
+
]
|
|
7504
|
+
},
|
|
7505
|
+
scaleIn: {
|
|
7506
|
+
selector: "",
|
|
7507
|
+
timeline: { name: "scale-in", source: "view", range: "entry" },
|
|
7508
|
+
keyframes: [
|
|
7509
|
+
{ offset: "0%", properties: { opacity: "0", transform: "scale(0.8)" } },
|
|
7510
|
+
{ offset: "100%", properties: { opacity: "1", transform: "scale(1)" } }
|
|
7511
|
+
]
|
|
7512
|
+
},
|
|
7513
|
+
slideLeft: {
|
|
7514
|
+
selector: "",
|
|
7515
|
+
timeline: { name: "slide-left", source: "view", range: "entry" },
|
|
7516
|
+
keyframes: [
|
|
7517
|
+
{ offset: "0%", properties: { opacity: "0", transform: "translateX(-40px)" } },
|
|
7518
|
+
{ offset: "100%", properties: { opacity: "1", transform: "translateX(0)" } }
|
|
7519
|
+
]
|
|
7520
|
+
},
|
|
7521
|
+
slideRight: {
|
|
7522
|
+
selector: "",
|
|
7523
|
+
timeline: { name: "slide-right", source: "view", range: "entry" },
|
|
7524
|
+
keyframes: [
|
|
7525
|
+
{ offset: "0%", properties: { opacity: "0", transform: "translateX(40px)" } },
|
|
7526
|
+
{ offset: "100%", properties: { opacity: "1", transform: "translateX(0)" } }
|
|
7527
|
+
]
|
|
7528
|
+
},
|
|
7529
|
+
parallax: {
|
|
7530
|
+
selector: "",
|
|
7531
|
+
timeline: { name: "parallax", source: "scroll", scroller: "root" },
|
|
7532
|
+
keyframes: [
|
|
7533
|
+
{ offset: "0%", properties: { transform: "translateY(0)" } },
|
|
7534
|
+
{ offset: "100%", properties: { transform: "translateY(-20%)" } }
|
|
7535
|
+
]
|
|
7536
|
+
},
|
|
7537
|
+
stickyReveal: {
|
|
7538
|
+
selector: "",
|
|
7539
|
+
timeline: { name: "sticky-reveal", source: "view", range: "contain" },
|
|
7540
|
+
keyframes: [
|
|
7541
|
+
{ offset: "0%", properties: { opacity: "0", clipPath: "inset(0 0 100% 0)" } },
|
|
7542
|
+
{ offset: "50%", properties: { opacity: "1", clipPath: "inset(0 0 0% 0)" } },
|
|
7543
|
+
{ offset: "100%", properties: { opacity: "1", clipPath: "inset(0 0 0% 0)" } }
|
|
7544
|
+
]
|
|
7545
|
+
}
|
|
7546
|
+
};
|
|
7547
|
+
var animCounter = 0;
|
|
7548
|
+
function generateName(prefix) {
|
|
7549
|
+
return prefix + "-" + (animCounter++).toString(36);
|
|
7550
|
+
}
|
|
7551
|
+
function compileScrollAnimation(animation) {
|
|
7552
|
+
const animName = animation.timeline.name || generateName("scroll-anim");
|
|
7553
|
+
const timelineName = "--" + animName + "-tl";
|
|
7554
|
+
let css = "";
|
|
7555
|
+
css += "/* Scroll Timeline: " + animName + " */\n";
|
|
7556
|
+
if (animation.timeline.source === "view") {
|
|
7557
|
+
const range = animation.timeline.range || "entry";
|
|
7558
|
+
css += animation.selector + " {\n";
|
|
7559
|
+
css += " view-timeline-name: " + timelineName + ";\n";
|
|
7560
|
+
css += " view-timeline-axis: " + (animation.timeline.axis || "block") + ";\n";
|
|
7561
|
+
if (animation.timeline.inset) {
|
|
7562
|
+
const inset = typeof animation.timeline.inset === "string" ? animation.timeline.inset : animation.timeline.inset.start + " " + animation.timeline.inset.end;
|
|
7563
|
+
css += " view-timeline-inset: " + inset + ";\n";
|
|
7564
|
+
}
|
|
7565
|
+
css += "}\n\n";
|
|
7566
|
+
} else {
|
|
7567
|
+
const scroller = animation.timeline.scroller === "root" ? "root" : animation.timeline.scroller === "self" ? "self" : animation.timeline.scroller || "nearest";
|
|
7568
|
+
css += animation.selector + " {\n";
|
|
7569
|
+
if (scroller === "root" || scroller === "self" || scroller === "nearest") {
|
|
7570
|
+
css += " scroll-timeline-name: " + timelineName + ";\n";
|
|
7571
|
+
css += " scroll-timeline-axis: " + (animation.timeline.axis || "block") + ";\n";
|
|
7572
|
+
}
|
|
7573
|
+
css += "}\n\n";
|
|
7574
|
+
}
|
|
7575
|
+
css += "@keyframes " + animName + " {\n";
|
|
7576
|
+
for (const step of animation.keyframes) {
|
|
7577
|
+
css += " " + step.offset + " {\n";
|
|
7578
|
+
for (const [prop, value] of Object.entries(step.properties)) {
|
|
7579
|
+
const kebabProp = prop.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
7580
|
+
css += " " + kebabProp + ": " + value + ";\n";
|
|
7581
|
+
}
|
|
7582
|
+
css += " }\n";
|
|
7583
|
+
}
|
|
7584
|
+
css += "}\n\n";
|
|
7585
|
+
const targetSelector = animation.selector + " > *" || animation.selector;
|
|
7586
|
+
css += "/* Apply animation to children */\n";
|
|
7587
|
+
css += targetSelector + " {\n";
|
|
7588
|
+
css += " animation: " + animName + " linear both;\n";
|
|
7589
|
+
css += " animation-timeline: " + timelineName + ";\n";
|
|
7590
|
+
if (animation.timeline.source === "view") {
|
|
7591
|
+
const range = animation.timeline.range || "entry";
|
|
7592
|
+
css += " animation-range: " + range + ";\n";
|
|
7593
|
+
}
|
|
7594
|
+
if (animation.delay) css += " animation-delay: " + animation.delay + ";\n";
|
|
7595
|
+
css += "}\n\n";
|
|
7596
|
+
css += "/* Fallback for browsers without scroll-timeline */\n";
|
|
7597
|
+
css += "@supports not (animation-timeline: scroll()) {\n";
|
|
7598
|
+
css += " " + targetSelector + " {\n";
|
|
7599
|
+
css += " /* Use JS polyfill: https://github.com/flackr/scroll-timeline */\n";
|
|
7600
|
+
css += " animation: " + animName + " 1s ease both;\n";
|
|
7601
|
+
css += " }\n";
|
|
7602
|
+
css += "}\n";
|
|
7603
|
+
return {
|
|
7604
|
+
css,
|
|
7605
|
+
animationName: animName,
|
|
7606
|
+
timelineName,
|
|
7607
|
+
fallback: ""
|
|
7608
|
+
};
|
|
7609
|
+
}
|
|
7610
|
+
function compileScrollAnimations(animations) {
|
|
7611
|
+
let css = "/* ============================================================\n";
|
|
7612
|
+
css += " ChainCSS Scroll-Driven Animations\n";
|
|
7613
|
+
css += " Generated: " + (/* @__PURE__ */ new Date()).toISOString() + "\n";
|
|
7614
|
+
css += " ============================================================ */\n\n";
|
|
7615
|
+
for (const animation of animations) {
|
|
7616
|
+
const result = compileScrollAnimation(animation);
|
|
7617
|
+
css += result.css;
|
|
7618
|
+
}
|
|
7619
|
+
return css;
|
|
7620
|
+
}
|
|
7621
|
+
function createScrollAnimation(preset, selector, overrides) {
|
|
7622
|
+
const base = SCROLL_PRESETS[preset];
|
|
7623
|
+
if (!base) throw new Error("Unknown scroll preset: " + preset);
|
|
7624
|
+
return {
|
|
7625
|
+
...base,
|
|
7626
|
+
selector,
|
|
7627
|
+
timeline: { ...base.timeline, ...overrides?.timeline },
|
|
7628
|
+
keyframes: overrides?.keyframes || base.keyframes,
|
|
7629
|
+
...overrides
|
|
7630
|
+
};
|
|
7631
|
+
}
|
|
7632
|
+
function getScrollPresets() {
|
|
7633
|
+
return Object.keys(SCROLL_PRESETS);
|
|
7634
|
+
}
|
|
7635
|
+
var scrollTimeline = {
|
|
7636
|
+
compile: compileScrollAnimation,
|
|
7637
|
+
compileAll: compileScrollAnimations,
|
|
7638
|
+
create: createScrollAnimation,
|
|
7639
|
+
presets: SCROLL_PRESETS,
|
|
7640
|
+
getPresets: getScrollPresets
|
|
7641
|
+
};
|
|
7642
|
+
|
|
7643
|
+
// src/index.ts
|
|
6879
7644
|
init_Chain();
|
|
7645
|
+
|
|
7646
|
+
// src/compiler/math-engine.ts
|
|
7647
|
+
var DEFAULT_CONTEXT = {
|
|
7648
|
+
rootFontSize: 16,
|
|
7649
|
+
viewportWidth: 1920,
|
|
7650
|
+
viewportHeight: 1080,
|
|
7651
|
+
parentFontSize: 16,
|
|
7652
|
+
dpi: 96,
|
|
7653
|
+
elementWidth: 1920,
|
|
7654
|
+
elementHeight: 1080
|
|
7655
|
+
};
|
|
7656
|
+
var UNIT_CATEGORIES = {
|
|
7657
|
+
absolute: ["px", "cm", "mm", "in", "pt", "pc"],
|
|
7658
|
+
relative: ["rem", "em", "%", "ch", "ex"],
|
|
7659
|
+
viewport: ["vw", "vh", "vmin", "vmax"],
|
|
7660
|
+
angle: ["deg", "rad", "turn", "grad"],
|
|
7661
|
+
time: ["s", "ms"],
|
|
7662
|
+
resolution: ["dpi", "dpcm", "dppx"]
|
|
7663
|
+
};
|
|
7664
|
+
var PX_CONVERSIONS = {
|
|
7665
|
+
"cm": 37.795,
|
|
7666
|
+
"mm": 3.7795,
|
|
7667
|
+
"in": 96,
|
|
7668
|
+
"pt": 1.333,
|
|
7669
|
+
"pc": 16
|
|
7670
|
+
};
|
|
7671
|
+
function parseCSSValue(input) {
|
|
7672
|
+
if (typeof input === "number") {
|
|
7673
|
+
return { value: input, unit: "px" };
|
|
7674
|
+
}
|
|
7675
|
+
const trimmed = input.trim();
|
|
7676
|
+
const match = trimmed.match(/^(-?\d+(?:\.\d+)?)\s*(px|rem|em|%|vw|vh|vmin|vmax|ch|ex|cm|mm|in|pt|pc|deg|rad|turn|grad|s|ms|dpi|dpcm|dppx)?$/i);
|
|
7677
|
+
if (match) {
|
|
7678
|
+
return {
|
|
7679
|
+
value: parseFloat(match[1]),
|
|
7680
|
+
unit: match[2]?.toLowerCase() || "px"
|
|
7681
|
+
};
|
|
7682
|
+
}
|
|
7683
|
+
const numMatch = trimmed.match(/(-?\d+(?:\.\d+)?)/);
|
|
7684
|
+
return {
|
|
7685
|
+
value: numMatch ? parseFloat(numMatch[1]) : 0,
|
|
7686
|
+
unit: "px"
|
|
7687
|
+
};
|
|
7688
|
+
}
|
|
7689
|
+
function getUnitCategory(unit) {
|
|
7690
|
+
for (const [category, units] of Object.entries(UNIT_CATEGORIES)) {
|
|
7691
|
+
if (units.includes(unit)) return category;
|
|
7692
|
+
}
|
|
7693
|
+
return "unknown";
|
|
7694
|
+
}
|
|
7695
|
+
function resolveToPx(value, context) {
|
|
7696
|
+
const { value: v, unit } = value;
|
|
7697
|
+
switch (unit) {
|
|
7698
|
+
case "px":
|
|
7699
|
+
return v;
|
|
7700
|
+
case "rem":
|
|
7701
|
+
return v * context.rootFontSize;
|
|
7702
|
+
case "em":
|
|
7703
|
+
return v * context.parentFontSize;
|
|
7704
|
+
case "%":
|
|
7705
|
+
return v / 100 * context.parentFontSize;
|
|
7706
|
+
// Default to font-relative
|
|
7707
|
+
case "vw":
|
|
7708
|
+
return v / 100 * context.viewportWidth;
|
|
7709
|
+
case "vh":
|
|
7710
|
+
return v / 100 * context.viewportHeight;
|
|
7711
|
+
case "vmin":
|
|
7712
|
+
return v / 100 * Math.min(context.viewportWidth, context.viewportHeight);
|
|
7713
|
+
case "vmax":
|
|
7714
|
+
return v / 100 * Math.max(context.viewportWidth, context.viewportHeight);
|
|
7715
|
+
case "cm":
|
|
7716
|
+
return v * (PX_CONVERSIONS["cm"] || 37.795);
|
|
7717
|
+
case "mm":
|
|
7718
|
+
return v * (PX_CONVERSIONS["mm"] || 3.7795);
|
|
7719
|
+
case "in":
|
|
7720
|
+
return v * (PX_CONVERSIONS["in"] || 96);
|
|
7721
|
+
case "pt":
|
|
7722
|
+
return v * (PX_CONVERSIONS["pt"] || 1.333);
|
|
7723
|
+
case "pc":
|
|
7724
|
+
return v * (PX_CONVERSIONS["pc"] || 16);
|
|
7725
|
+
// ch/ex are approximate
|
|
7726
|
+
case "ch":
|
|
7727
|
+
return v * (context.parentFontSize * 0.5);
|
|
7728
|
+
case "ex":
|
|
7729
|
+
return v * (context.parentFontSize * 0.45);
|
|
7730
|
+
default:
|
|
7731
|
+
return v;
|
|
7732
|
+
}
|
|
7733
|
+
}
|
|
7734
|
+
function resolveFromPx(px, targetUnit, context) {
|
|
7735
|
+
switch (targetUnit) {
|
|
7736
|
+
case "px":
|
|
7737
|
+
return px;
|
|
7738
|
+
case "rem":
|
|
7739
|
+
return px / context.rootFontSize;
|
|
7740
|
+
case "em":
|
|
7741
|
+
return px / context.parentFontSize;
|
|
7742
|
+
case "%":
|
|
7743
|
+
return px / context.parentFontSize * 100;
|
|
7744
|
+
case "vw":
|
|
7745
|
+
return px / context.viewportWidth * 100;
|
|
7746
|
+
case "vh":
|
|
7747
|
+
return px / context.viewportHeight * 100;
|
|
7748
|
+
case "vmin":
|
|
7749
|
+
return px / Math.min(context.viewportWidth, context.viewportHeight) * 100;
|
|
7750
|
+
case "vmax":
|
|
7751
|
+
return px / Math.max(context.viewportWidth, context.viewportHeight) * 100;
|
|
7752
|
+
case "cm":
|
|
7753
|
+
return px / (PX_CONVERSIONS["cm"] || 37.795);
|
|
7754
|
+
case "mm":
|
|
7755
|
+
return px / (PX_CONVERSIONS["mm"] || 3.7795);
|
|
7756
|
+
case "in":
|
|
7757
|
+
return px / (PX_CONVERSIONS["in"] || 96);
|
|
7758
|
+
case "pt":
|
|
7759
|
+
return px / (PX_CONVERSIONS["pt"] || 1.333);
|
|
7760
|
+
case "pc":
|
|
7761
|
+
return px / (PX_CONVERSIONS["pc"] || 16);
|
|
7762
|
+
case "ch":
|
|
7763
|
+
return px / (context.parentFontSize * 0.5);
|
|
7764
|
+
case "ex":
|
|
7765
|
+
return px / (context.parentFontSize * 0.45);
|
|
7766
|
+
default:
|
|
7767
|
+
return px;
|
|
7768
|
+
}
|
|
7769
|
+
}
|
|
7770
|
+
function canResolve(a, b, context) {
|
|
7771
|
+
const catA = getUnitCategory(a.unit);
|
|
7772
|
+
const catB = getUnitCategory(b.unit);
|
|
7773
|
+
if (catA === catB) return true;
|
|
7774
|
+
if ((catA === "absolute" || catA === "relative") && (catB === "absolute" || catB === "relative")) return true;
|
|
7775
|
+
return false;
|
|
7776
|
+
}
|
|
7777
|
+
function createResult(value, unit, expression, resolved, explanations = []) {
|
|
7778
|
+
return {
|
|
7779
|
+
value,
|
|
7780
|
+
unit,
|
|
7781
|
+
expression,
|
|
7782
|
+
resolved,
|
|
7783
|
+
explanations,
|
|
7784
|
+
toString() {
|
|
7785
|
+
return this.expression;
|
|
7786
|
+
},
|
|
7787
|
+
toCalc() {
|
|
7788
|
+
return unit === "calc" ? expression : `calc(${expression})`;
|
|
7789
|
+
}
|
|
7790
|
+
};
|
|
7791
|
+
}
|
|
7792
|
+
function operate(a, op, b, context) {
|
|
7793
|
+
const ctx = { ...DEFAULT_CONTEXT, ...context };
|
|
7794
|
+
const valA = parseCSSValue(a);
|
|
7795
|
+
const valB = parseCSSValue(b);
|
|
7796
|
+
const explanations = [];
|
|
7797
|
+
if (valA.unit === valB.unit) {
|
|
7798
|
+
let result;
|
|
7799
|
+
switch (op) {
|
|
7800
|
+
case "add":
|
|
7801
|
+
result = valA.value + valB.value;
|
|
7802
|
+
break;
|
|
7803
|
+
case "subtract":
|
|
7804
|
+
result = valA.value - valB.value;
|
|
7805
|
+
break;
|
|
7806
|
+
case "multiply":
|
|
7807
|
+
result = valA.value * valB.value;
|
|
7808
|
+
break;
|
|
7809
|
+
case "divide":
|
|
7810
|
+
result = valA.value / valB.value;
|
|
7811
|
+
break;
|
|
7812
|
+
}
|
|
7813
|
+
const rounded = Math.round(result * 100) / 100;
|
|
7814
|
+
explanations.push(`Same unit (${valA.unit}) \u2014 direct ${op}`);
|
|
7815
|
+
return createResult(
|
|
7816
|
+
rounded,
|
|
7817
|
+
valA.unit,
|
|
7818
|
+
`${rounded}${valA.unit}`,
|
|
7819
|
+
{ value: rounded, unit: valA.unit },
|
|
7820
|
+
explanations
|
|
7821
|
+
);
|
|
7822
|
+
}
|
|
7823
|
+
if (canResolve(valA, valB, ctx)) {
|
|
7824
|
+
const pxA = resolveToPx(valA, ctx);
|
|
7825
|
+
const pxB = resolveToPx(valB, ctx);
|
|
7826
|
+
let pxResult;
|
|
7827
|
+
switch (op) {
|
|
7828
|
+
case "add":
|
|
7829
|
+
pxResult = pxA + pxB;
|
|
7830
|
+
break;
|
|
7831
|
+
case "subtract":
|
|
7832
|
+
pxResult = pxA - pxB;
|
|
7833
|
+
break;
|
|
7834
|
+
case "multiply":
|
|
7835
|
+
pxResult = pxA * pxB;
|
|
7836
|
+
break;
|
|
7837
|
+
case "divide":
|
|
7838
|
+
pxResult = pxA / pxB;
|
|
7839
|
+
break;
|
|
7840
|
+
}
|
|
7841
|
+
const rounded = Math.round(pxResult * 100) / 100;
|
|
7842
|
+
explanations.push(`Resolved ${valA.value}${valA.unit} \u2192 ${Math.round(pxA * 100) / 100}px`);
|
|
7843
|
+
explanations.push(`Resolved ${valB.value}${valB.unit} \u2192 ${Math.round(pxB * 100) / 100}px`);
|
|
7844
|
+
explanations.push(`${op} \u2192 ${rounded}px`);
|
|
7845
|
+
return createResult(
|
|
7846
|
+
rounded,
|
|
7847
|
+
"px",
|
|
7848
|
+
`${rounded}px`,
|
|
7849
|
+
{ value: rounded, unit: "px" },
|
|
7850
|
+
explanations
|
|
7851
|
+
);
|
|
7852
|
+
}
|
|
7853
|
+
const expr = `${valA.value}${valA.unit} ${getOpSymbol(op)} ${valB.value}${valB.unit}`;
|
|
7854
|
+
explanations.push(`Cannot resolve ${valA.unit} \u2194 ${valB.unit} \u2014 using calc()`);
|
|
7855
|
+
return createResult(
|
|
7856
|
+
0,
|
|
7857
|
+
"calc",
|
|
7858
|
+
`calc(${expr})`,
|
|
7859
|
+
null,
|
|
7860
|
+
explanations
|
|
7861
|
+
);
|
|
7862
|
+
}
|
|
7863
|
+
function getOpSymbol(op) {
|
|
7864
|
+
switch (op) {
|
|
7865
|
+
case "add":
|
|
7866
|
+
return "+";
|
|
7867
|
+
case "subtract":
|
|
7868
|
+
return "-";
|
|
7869
|
+
case "multiply":
|
|
7870
|
+
return "*";
|
|
7871
|
+
case "divide":
|
|
7872
|
+
return "/";
|
|
7873
|
+
}
|
|
7874
|
+
}
|
|
7875
|
+
var math = {
|
|
7876
|
+
/**
|
|
7877
|
+
* Add two CSS values with unit resolution.
|
|
7878
|
+
*
|
|
7879
|
+
* @example
|
|
7880
|
+
* math.add('10px', '2rem') // → '42px' (with default context)
|
|
7881
|
+
* math.add('10px', '2rem', { rootFontSize: 16 }) // → '42px'
|
|
7882
|
+
* math.add('10px', '2vw') // → 'calc(10px + 2vw)'
|
|
7883
|
+
*/
|
|
7884
|
+
add(a, b, context) {
|
|
7885
|
+
return operate(a, "add", b, context);
|
|
7886
|
+
},
|
|
7887
|
+
/**
|
|
7888
|
+
* Subtract two CSS values with unit resolution.
|
|
7889
|
+
*/
|
|
7890
|
+
subtract(a, b, context) {
|
|
7891
|
+
return operate(a, "subtract", b, context);
|
|
7892
|
+
},
|
|
7893
|
+
/**
|
|
7894
|
+
* Multiply two CSS values with unit resolution.
|
|
7895
|
+
*/
|
|
7896
|
+
multiply(a, b, context) {
|
|
7897
|
+
return operate(a, "multiply", b, context);
|
|
7898
|
+
},
|
|
7899
|
+
/**
|
|
7900
|
+
* Divide two CSS values with unit resolution.
|
|
7901
|
+
*/
|
|
7902
|
+
divide(a, b, context) {
|
|
7903
|
+
return operate(a, "divide", b, context);
|
|
7904
|
+
},
|
|
7905
|
+
/**
|
|
7906
|
+
* Sum multiple CSS values.
|
|
7907
|
+
*/
|
|
7908
|
+
sum(...values) {
|
|
7909
|
+
if (values.length === 0) {
|
|
7910
|
+
return createResult(0, "px", "0px", { value: 0, unit: "px" });
|
|
7911
|
+
}
|
|
7912
|
+
if (values.length === 1) {
|
|
7913
|
+
const parsed = parseCSSValue(values[0]);
|
|
7914
|
+
return createResult(parsed.value, parsed.unit, `${parsed.value}${parsed.unit}`, parsed);
|
|
7915
|
+
}
|
|
7916
|
+
let result = this.add(values[0], values[1]);
|
|
7917
|
+
for (let i = 2; i < values.length; i++) {
|
|
7918
|
+
result = this.add(result.expression, values[i]);
|
|
7919
|
+
}
|
|
7920
|
+
return result;
|
|
7921
|
+
},
|
|
7922
|
+
/**
|
|
7923
|
+
* Resolve a CSS value to pixels.
|
|
7924
|
+
*/
|
|
7925
|
+
toPx(value, context) {
|
|
7926
|
+
const ctx = { ...DEFAULT_CONTEXT, ...context };
|
|
7927
|
+
const parsed = parseCSSValue(value);
|
|
7928
|
+
return resolveToPx(parsed, ctx);
|
|
7929
|
+
},
|
|
7930
|
+
/**
|
|
7931
|
+
* Convert between CSS units.
|
|
7932
|
+
*/
|
|
7933
|
+
convert(value, toUnit, context) {
|
|
7934
|
+
const ctx = { ...DEFAULT_CONTEXT, ...context };
|
|
7935
|
+
const parsed = parseCSSValue(value);
|
|
7936
|
+
const px = resolveToPx(parsed, ctx);
|
|
7937
|
+
const converted = resolveFromPx(px, toUnit, ctx);
|
|
7938
|
+
const rounded = Math.round(converted * 1e3) / 1e3;
|
|
7939
|
+
return createResult(
|
|
7940
|
+
rounded,
|
|
7941
|
+
toUnit,
|
|
7942
|
+
`${rounded}${toUnit}`,
|
|
7943
|
+
{ value: rounded, unit: toUnit },
|
|
7944
|
+
[`${parsed.value}${parsed.unit} \u2192 ${rounded}${toUnit}`]
|
|
7945
|
+
);
|
|
7946
|
+
},
|
|
7947
|
+
/**
|
|
7948
|
+
* Create a fluid typography clamp() expression.
|
|
7949
|
+
*
|
|
7950
|
+
* @example
|
|
7951
|
+
* math.fluidType({ minSize: 14, maxSize: 20 })
|
|
7952
|
+
* // → 'clamp(14px, 0.625vw + 12px, 20px)'
|
|
7953
|
+
* math.fluidType({ minSize: 14, maxSize: 20, unit: 'rem', rootFontSize: 16 })
|
|
7954
|
+
* // → 'clamp(0.875rem, 0.625vw + 0.75rem, 1.25rem)'
|
|
7955
|
+
*/
|
|
7956
|
+
fluidType(config) {
|
|
7957
|
+
const {
|
|
7958
|
+
minSize,
|
|
7959
|
+
maxSize,
|
|
7960
|
+
minWidth = 320,
|
|
7961
|
+
maxWidth = 1280,
|
|
7962
|
+
unit = "px",
|
|
7963
|
+
rootFontSize = 16
|
|
7964
|
+
} = config;
|
|
7965
|
+
const slope = (maxSize - minSize) / (maxWidth - minWidth);
|
|
7966
|
+
const intercept = minSize - slope * minWidth;
|
|
7967
|
+
const slopeVw = Math.round(slope * 100 * 1e4) / 1e4;
|
|
7968
|
+
const interceptRounded = Math.round(intercept * 100) / 100;
|
|
7969
|
+
const minStr = unit === "rem" ? `${minSize / rootFontSize}rem` : `${minSize}${unit}`;
|
|
7970
|
+
const maxStr = unit === "rem" ? `${maxSize / rootFontSize}rem` : `${maxSize}${unit}`;
|
|
7971
|
+
const prefStr = `${slopeVw}vw + ${unit === "rem" ? interceptRounded / rootFontSize + "rem" : interceptRounded + unit}`;
|
|
7972
|
+
const expression = `clamp(${minStr}, ${prefStr}, ${maxStr})`;
|
|
7973
|
+
return createResult(
|
|
7974
|
+
0,
|
|
7975
|
+
"calc",
|
|
7976
|
+
expression,
|
|
7977
|
+
null,
|
|
7978
|
+
[`Fluid type: ${minSize}${unit} \u2192 ${maxSize}${unit} between ${minWidth}px and ${maxWidth}px`]
|
|
7979
|
+
);
|
|
7980
|
+
},
|
|
7981
|
+
/**
|
|
7982
|
+
* Scale a value by a factor with unit preservation.
|
|
7983
|
+
*/
|
|
7984
|
+
scale(value, factor) {
|
|
7985
|
+
const parsed = parseCSSValue(value);
|
|
7986
|
+
const scaled = Math.round(parsed.value * factor * 100) / 100;
|
|
7987
|
+
return createResult(
|
|
7988
|
+
scaled,
|
|
7989
|
+
parsed.unit,
|
|
7990
|
+
`${scaled}${parsed.unit}`,
|
|
7991
|
+
{ value: scaled, unit: parsed.unit },
|
|
7992
|
+
[`Scaled ${parsed.value}${parsed.unit} \xD7 ${factor} = ${scaled}${parsed.unit}`]
|
|
7993
|
+
);
|
|
7994
|
+
},
|
|
7995
|
+
/**
|
|
7996
|
+
* Clamp a CSS value between min and max.
|
|
7997
|
+
*/
|
|
7998
|
+
clampValue(value, min, max, context) {
|
|
7999
|
+
const parsed = parseCSSValue(value);
|
|
8000
|
+
const parsedMin = parseCSSValue(min);
|
|
8001
|
+
const parsedMax = parseCSSValue(max);
|
|
8002
|
+
if (parsed.unit === parsedMin.unit && parsed.unit === parsedMax.unit) {
|
|
8003
|
+
const clamped = Math.max(parsedMin.value, Math.min(parsedMax.value, parsed.value));
|
|
8004
|
+
return createResult(
|
|
8005
|
+
clamped,
|
|
8006
|
+
parsed.unit,
|
|
8007
|
+
`${clamped}${parsed.unit}`,
|
|
8008
|
+
{ value: clamped, unit: parsed.unit },
|
|
8009
|
+
[`Clamped ${parsed.value} between ${parsedMin.value} and ${parsedMax.value}`]
|
|
8010
|
+
);
|
|
8011
|
+
}
|
|
8012
|
+
const valStr = `${parsed.value}${parsed.unit}`;
|
|
8013
|
+
const minStr = `${parsedMin.value}${parsedMin.unit}`;
|
|
8014
|
+
const maxStr = `${parsedMax.value}${parsedMax.unit}`;
|
|
8015
|
+
return createResult(
|
|
8016
|
+
0,
|
|
8017
|
+
"calc",
|
|
8018
|
+
`clamp(${minStr}, ${valStr}, ${maxStr})`,
|
|
8019
|
+
null,
|
|
8020
|
+
["Mixed units \u2014 using clamp()"]
|
|
8021
|
+
);
|
|
8022
|
+
},
|
|
8023
|
+
/**
|
|
8024
|
+
* Parse a CSS value into its numeric and unit parts.
|
|
8025
|
+
*/
|
|
8026
|
+
parse(value) {
|
|
8027
|
+
return parseCSSValue(value);
|
|
8028
|
+
},
|
|
8029
|
+
/**
|
|
8030
|
+
* Check if two values have compatible units for direct operations.
|
|
8031
|
+
*/
|
|
8032
|
+
compatible(a, b) {
|
|
8033
|
+
const valA = parseCSSValue(a);
|
|
8034
|
+
const valB = parseCSSValue(b);
|
|
8035
|
+
return valA.unit === valB.unit || getUnitCategory(valA.unit) === getUnitCategory(valB.unit);
|
|
8036
|
+
},
|
|
8037
|
+
/**
|
|
8038
|
+
* Get the category of a CSS unit.
|
|
8039
|
+
*/
|
|
8040
|
+
unitCategory(unit) {
|
|
8041
|
+
return getUnitCategory(unit);
|
|
8042
|
+
},
|
|
8043
|
+
/**
|
|
8044
|
+
* Create a CSS min() expression.
|
|
8045
|
+
*/
|
|
8046
|
+
cssMin(...values) {
|
|
8047
|
+
const formatted = values.map((v) => {
|
|
8048
|
+
const parsed = parseCSSValue(v);
|
|
8049
|
+
return `${parsed.value}${parsed.unit}`;
|
|
8050
|
+
});
|
|
8051
|
+
return `min(${formatted.join(", ")})`;
|
|
8052
|
+
},
|
|
8053
|
+
/**
|
|
8054
|
+
* Create a CSS max() expression.
|
|
8055
|
+
*/
|
|
8056
|
+
cssMax(...values) {
|
|
8057
|
+
const formatted = values.map((v) => {
|
|
8058
|
+
const parsed = parseCSSValue(v);
|
|
8059
|
+
return `${parsed.value}${parsed.unit}`;
|
|
8060
|
+
});
|
|
8061
|
+
return `max(${formatted.join(", ")})`;
|
|
8062
|
+
},
|
|
8063
|
+
/**
|
|
8064
|
+
* Format a number with specified precision.
|
|
8065
|
+
*/
|
|
8066
|
+
precision(value, decimals = 2) {
|
|
8067
|
+
return value.toFixed(decimals);
|
|
8068
|
+
}
|
|
8069
|
+
};
|
|
8070
|
+
var add = math.add.bind(math);
|
|
8071
|
+
var subtract = math.subtract.bind(math);
|
|
8072
|
+
var multiply = math.multiply.bind(math);
|
|
8073
|
+
var divide = math.divide.bind(math);
|
|
8074
|
+
var fluidType = math.fluidType.bind(math);
|
|
8075
|
+
var convert = math.convert.bind(math);
|
|
8076
|
+
var toPx = math.toPx.bind(math);
|
|
8077
|
+
var scale = math.scale.bind(math);
|
|
8078
|
+
|
|
8079
|
+
// src/compiler/css-if-transpiler.ts
|
|
8080
|
+
function detectIfPatterns(styles) {
|
|
8081
|
+
const conditions = [];
|
|
8082
|
+
if (!styles._conditions) return conditions;
|
|
8083
|
+
const condEntries = Object.entries(styles._conditions || {});
|
|
8084
|
+
for (const [variable, branches] of condEntries) {
|
|
8085
|
+
const branch = branches;
|
|
8086
|
+
const trueStyles = branch.true || {};
|
|
8087
|
+
const falseStyles = branch.false || {};
|
|
8088
|
+
const allProps = /* @__PURE__ */ new Set([...Object.keys(trueStyles), ...Object.keys(falseStyles)]);
|
|
8089
|
+
for (const prop of allProps) {
|
|
8090
|
+
if (prop.startsWith("_") || prop === "selectors") continue;
|
|
8091
|
+
const trueVal = trueStyles[prop];
|
|
8092
|
+
const falseVal = falseStyles[prop];
|
|
8093
|
+
if (trueVal !== void 0 && falseVal !== void 0 && trueVal !== falseVal) {
|
|
8094
|
+
conditions.push({
|
|
8095
|
+
property: prop,
|
|
8096
|
+
variable: variable.startsWith("--") ? variable : "--" + variable,
|
|
8097
|
+
conditions: { true: trueVal },
|
|
8098
|
+
defaultValue: falseVal
|
|
8099
|
+
});
|
|
8100
|
+
}
|
|
8101
|
+
}
|
|
8102
|
+
}
|
|
8103
|
+
return conditions;
|
|
8104
|
+
}
|
|
8105
|
+
function emitCSSIf(selector, detectedConditions, baseProperties = {}) {
|
|
8106
|
+
if (detectedConditions.length === 0) return "";
|
|
8107
|
+
let css = "";
|
|
8108
|
+
css += "/* Native CSS if() \u2014 Chrome 137+ */\n";
|
|
8109
|
+
css += selector + " {\n";
|
|
8110
|
+
for (const [prop, value] of Object.entries(baseProperties)) {
|
|
8111
|
+
css += " " + prop + ": " + value + ";\n";
|
|
8112
|
+
}
|
|
8113
|
+
for (const cond of detectedConditions) {
|
|
8114
|
+
const entries = Object.entries(cond.conditions);
|
|
8115
|
+
if (entries.length === 1) {
|
|
8116
|
+
const [condition, val] = entries[0];
|
|
8117
|
+
css += " " + cond.property + ": if(style(" + cond.variable + ": " + condition + "): " + val + " else " + cond.defaultValue + ");\n";
|
|
8118
|
+
} else {
|
|
8119
|
+
let chain3 = "";
|
|
8120
|
+
for (let i = 0; i < entries.length; i++) {
|
|
8121
|
+
const [condition, val] = entries[i];
|
|
8122
|
+
chain3 += i === 0 ? "if(style(" + cond.variable + ": " + condition + "): " + val : " else if(style(" + cond.variable + ": " + condition + "): " + val;
|
|
8123
|
+
}
|
|
8124
|
+
chain3 += " else " + cond.defaultValue + ")".repeat(entries.length);
|
|
8125
|
+
css += " " + cond.property + ": " + chain3 + ";\n";
|
|
8126
|
+
}
|
|
8127
|
+
}
|
|
8128
|
+
css += "}\n\n";
|
|
8129
|
+
css += "/* Fallback for browsers without CSS if() */\n";
|
|
8130
|
+
css += "@supports not (property: if()) {\n";
|
|
8131
|
+
css += " " + selector + " {\n";
|
|
8132
|
+
for (const [prop, value] of Object.entries(baseProperties)) {
|
|
8133
|
+
css += " " + prop + ": " + value + ";\n";
|
|
8134
|
+
}
|
|
8135
|
+
for (const cond of detectedConditions) {
|
|
8136
|
+
css += " " + cond.property + ": " + cond.defaultValue + ";\n";
|
|
8137
|
+
}
|
|
8138
|
+
css += " }\n";
|
|
8139
|
+
for (const cond of detectedConditions) {
|
|
8140
|
+
for (const [condition, val] of Object.entries(cond.conditions)) {
|
|
8141
|
+
const modClass = selector + "--" + cond.variable.replace("--", "") + "-" + condition;
|
|
8142
|
+
css += " " + modClass + " { " + cond.property + ": " + val + "; }\n";
|
|
8143
|
+
}
|
|
8144
|
+
}
|
|
8145
|
+
css += "}\n";
|
|
8146
|
+
return css;
|
|
8147
|
+
}
|
|
8148
|
+
|
|
8149
|
+
// src/compiler/intent-engine.ts
|
|
8150
|
+
var SEMANTIC_INTENTS = [
|
|
8151
|
+
{ pattern: /^flexbox$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "display", corrected: "flex", defaults: { display: "flex", justifyContent: "center", alignItems: "center" }, confidence: 0.95, intent: "flexbox-centering", explanation: '"flexbox" mapped to display: flex with centering defaults.' }), description: "flexbox -> flex + centering" },
|
|
8152
|
+
{ pattern: /^(absolutely|abs)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "position", corrected: "absolute", defaults: { position: "absolute" }, confidence: 0.9, intent: "absolute-position", explanation: '"abs/absolutely" -> position: absolute' }), description: "abs -> absolute" },
|
|
8153
|
+
{ pattern: /^(rel|relatively)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "position", corrected: "relative", defaults: { position: "relative" }, confidence: 0.9, intent: "relative-position", explanation: '"rel/relatively" -> position: relative' }), description: "rel -> relative" },
|
|
8154
|
+
{ pattern: /^(hidden|invisible)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "visibility", corrected: v.toLowerCase() === "invisible" ? "hidden" : v.toLowerCase(), defaults: { visibility: "hidden" }, confidence: 0.9, intent: "visibility-toggle", explanation: '"' + v + '" -> visibility: hidden' }), description: "invisible -> hidden" },
|
|
8155
|
+
{ pattern: /^(full|fullscreen|full-screen)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "size", corrected: "100%", defaults: { width: "100%", height: "100%" }, confidence: 0.85, intent: "full-size", explanation: '"full/fullscreen" -> width/height: 100%' }), description: "full -> 100%" },
|
|
8156
|
+
{ pattern: /^(rounded|round)$/i, handler: (v, ctx) => ({ original: v, property: ctx.property || "border-radius", corrected: "9999px", defaults: { borderRadius: "9999px" }, confidence: 0.8, intent: "rounded-pill", explanation: '"rounded" -> border-radius: 9999px (pill)' }), description: "rounded -> pill" }
|
|
8157
|
+
];
|
|
8158
|
+
var LAYOUT_MACROS = {
|
|
8159
|
+
stickyHeader: {
|
|
8160
|
+
name: "stickyHeader",
|
|
8161
|
+
description: "Sticky header with scroll shadow and entrance animation",
|
|
8162
|
+
properties: {
|
|
8163
|
+
position: "sticky",
|
|
8164
|
+
top: "0",
|
|
8165
|
+
zIndex: "50",
|
|
8166
|
+
backgroundColor: "var(--header-bg, white)",
|
|
8167
|
+
backdropFilter: "blur(8px)",
|
|
8168
|
+
borderBottom: "1px solid transparent"
|
|
8169
|
+
},
|
|
8170
|
+
defaults: {
|
|
8171
|
+
"--header-bg": "white",
|
|
8172
|
+
"--header-shadow": "0 4px 12px rgba(0,0,0,0.1)"
|
|
8173
|
+
},
|
|
8174
|
+
mediaQueries: {
|
|
8175
|
+
"(max-width: 768px)": {
|
|
8176
|
+
padding: "12px 16px"
|
|
8177
|
+
},
|
|
8178
|
+
"(min-width: 769px)": {
|
|
8179
|
+
padding: "16px 32px"
|
|
8180
|
+
}
|
|
8181
|
+
}
|
|
8182
|
+
},
|
|
8183
|
+
card: {
|
|
8184
|
+
name: "card",
|
|
8185
|
+
description: "Standard card container with hover lift effect",
|
|
8186
|
+
properties: {
|
|
8187
|
+
display: "flex",
|
|
8188
|
+
flexDirection: "column",
|
|
8189
|
+
borderRadius: "12px",
|
|
8190
|
+
backgroundColor: "var(--card-bg, white)",
|
|
8191
|
+
boxShadow: "0 1px 3px rgba(0,0,0,0.12), 0 1px 2px rgba(0,0,0,0.08)",
|
|
8192
|
+
transition: "box-shadow 0.2s ease, transform 0.2s ease",
|
|
8193
|
+
overflow: "hidden"
|
|
8194
|
+
},
|
|
8195
|
+
defaults: {
|
|
8196
|
+
"--card-bg": "white",
|
|
8197
|
+
"--card-hover-shadow": "0 10px 30px rgba(0,0,0,0.15)"
|
|
8198
|
+
},
|
|
8199
|
+
mediaQueries: {
|
|
8200
|
+
"(hover: hover)": {
|
|
8201
|
+
"&:hover": {
|
|
8202
|
+
boxShadow: "var(--card-hover-shadow)",
|
|
8203
|
+
transform: "translateY(-2px)"
|
|
8204
|
+
}
|
|
8205
|
+
}
|
|
8206
|
+
}
|
|
8207
|
+
},
|
|
8208
|
+
hero: {
|
|
8209
|
+
name: "hero",
|
|
8210
|
+
description: "Full-width hero section with centered content",
|
|
8211
|
+
properties: {
|
|
8212
|
+
display: "flex",
|
|
8213
|
+
flexDirection: "column",
|
|
8214
|
+
justifyContent: "center",
|
|
8215
|
+
alignItems: "center",
|
|
8216
|
+
width: "100%",
|
|
8217
|
+
minHeight: "60vh",
|
|
8218
|
+
padding: "48px 24px",
|
|
8219
|
+
textAlign: "center"
|
|
8220
|
+
},
|
|
8221
|
+
defaults: {},
|
|
8222
|
+
mediaQueries: {
|
|
8223
|
+
"(max-width: 768px)": {
|
|
8224
|
+
minHeight: "40vh",
|
|
8225
|
+
padding: "32px 16px"
|
|
8226
|
+
}
|
|
8227
|
+
}
|
|
8228
|
+
},
|
|
8229
|
+
container: {
|
|
8230
|
+
name: "container",
|
|
8231
|
+
description: "Responsive centered container with max-width",
|
|
8232
|
+
properties: {
|
|
8233
|
+
width: "100%",
|
|
8234
|
+
maxWidth: "1200px",
|
|
8235
|
+
marginLeft: "auto",
|
|
8236
|
+
marginRight: "auto",
|
|
8237
|
+
paddingLeft: "16px",
|
|
8238
|
+
paddingRight: "16px"
|
|
8239
|
+
},
|
|
8240
|
+
defaults: {},
|
|
8241
|
+
mediaQueries: {
|
|
8242
|
+
"(min-width: 768px)": {
|
|
8243
|
+
paddingLeft: "24px",
|
|
8244
|
+
paddingRight: "24px"
|
|
8245
|
+
},
|
|
8246
|
+
"(min-width: 1024px)": {
|
|
8247
|
+
paddingLeft: "32px",
|
|
8248
|
+
paddingRight: "32px"
|
|
8249
|
+
}
|
|
8250
|
+
}
|
|
8251
|
+
},
|
|
8252
|
+
center: {
|
|
8253
|
+
name: "center",
|
|
8254
|
+
description: "Absolute centering using flexbox",
|
|
8255
|
+
properties: {
|
|
8256
|
+
display: "flex",
|
|
8257
|
+
justifyContent: "center",
|
|
8258
|
+
alignItems: "center"
|
|
8259
|
+
},
|
|
8260
|
+
defaults: {}
|
|
8261
|
+
},
|
|
8262
|
+
gridList: {
|
|
8263
|
+
name: "gridList",
|
|
8264
|
+
description: "Responsive grid list with auto-fit columns",
|
|
8265
|
+
properties: {
|
|
8266
|
+
display: "grid",
|
|
8267
|
+
gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
|
|
8268
|
+
gap: "24px"
|
|
8269
|
+
},
|
|
8270
|
+
defaults: {},
|
|
8271
|
+
mediaQueries: {
|
|
8272
|
+
"(max-width: 640px)": {
|
|
8273
|
+
gridTemplateColumns: "1fr",
|
|
8274
|
+
gap: "16px"
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
},
|
|
8278
|
+
sidebar: {
|
|
8279
|
+
name: "sidebar",
|
|
8280
|
+
description: "Two-column layout: sidebar + main content",
|
|
8281
|
+
properties: {
|
|
8282
|
+
display: "grid",
|
|
8283
|
+
gridTemplateColumns: "280px 1fr",
|
|
8284
|
+
gap: "32px",
|
|
8285
|
+
minHeight: "100vh"
|
|
8286
|
+
},
|
|
8287
|
+
defaults: {},
|
|
8288
|
+
mediaQueries: {
|
|
8289
|
+
"(max-width: 1024px)": {
|
|
8290
|
+
gridTemplateColumns: "1fr",
|
|
8291
|
+
gap: "24px"
|
|
8292
|
+
}
|
|
8293
|
+
}
|
|
8294
|
+
},
|
|
8295
|
+
pill: {
|
|
8296
|
+
name: "pill",
|
|
8297
|
+
description: "Pill-shaped element (fully rounded)",
|
|
8298
|
+
properties: {
|
|
8299
|
+
borderRadius: "9999px",
|
|
8300
|
+
padding: "8px 20px",
|
|
8301
|
+
display: "inline-flex",
|
|
8302
|
+
alignItems: "center",
|
|
8303
|
+
justifyContent: "center"
|
|
8304
|
+
},
|
|
8305
|
+
defaults: {}
|
|
8306
|
+
},
|
|
8307
|
+
glass: {
|
|
8308
|
+
name: "glass",
|
|
8309
|
+
description: "Frosted glass morphism effect",
|
|
8310
|
+
properties: {
|
|
8311
|
+
backgroundColor: "rgba(255, 255, 255, 0.1)",
|
|
8312
|
+
backdropFilter: "blur(16px)",
|
|
8313
|
+
border: "1px solid rgba(255, 255, 255, 0.2)",
|
|
8314
|
+
borderRadius: "16px"
|
|
8315
|
+
},
|
|
8316
|
+
defaults: {}
|
|
8317
|
+
},
|
|
8318
|
+
truncate: {
|
|
8319
|
+
name: "truncate",
|
|
8320
|
+
description: "Single-line text truncation with ellipsis",
|
|
8321
|
+
properties: {
|
|
8322
|
+
overflow: "hidden",
|
|
8323
|
+
textOverflow: "ellipsis",
|
|
8324
|
+
whiteSpace: "nowrap"
|
|
8325
|
+
},
|
|
8326
|
+
defaults: {}
|
|
8327
|
+
},
|
|
8328
|
+
srOnly: {
|
|
8329
|
+
name: "srOnly",
|
|
8330
|
+
description: "Screen-reader only (visually hidden but accessible)",
|
|
8331
|
+
properties: {
|
|
8332
|
+
position: "absolute",
|
|
8333
|
+
width: "1px",
|
|
8334
|
+
height: "1px",
|
|
8335
|
+
padding: "0",
|
|
8336
|
+
margin: "-1px",
|
|
8337
|
+
overflow: "hidden",
|
|
8338
|
+
clip: "rect(0, 0, 0, 0)",
|
|
8339
|
+
whiteSpace: "nowrap",
|
|
8340
|
+
borderWidth: "0"
|
|
8341
|
+
},
|
|
8342
|
+
defaults: {}
|
|
8343
|
+
}
|
|
8344
|
+
};
|
|
8345
|
+
function resolveLayoutMacro(name) {
|
|
8346
|
+
return LAYOUT_MACROS[name] || null;
|
|
8347
|
+
}
|
|
8348
|
+
function expandLayoutMacro(name) {
|
|
8349
|
+
const macro2 = resolveLayoutMacro(name);
|
|
8350
|
+
if (!macro2) return null;
|
|
8351
|
+
const result = { ...macro2.properties };
|
|
8352
|
+
if (macro2.defaults) {
|
|
8353
|
+
Object.assign(result, macro2.defaults);
|
|
8354
|
+
}
|
|
8355
|
+
if (macro2.mediaQueries) {
|
|
8356
|
+
result.atRules = result.atRules || [];
|
|
8357
|
+
for (const [query, props] of Object.entries(macro2.mediaQueries)) {
|
|
8358
|
+
result.atRules.push({
|
|
8359
|
+
type: "media",
|
|
8360
|
+
query,
|
|
8361
|
+
styles: props
|
|
8362
|
+
});
|
|
8363
|
+
}
|
|
8364
|
+
}
|
|
8365
|
+
return result;
|
|
8366
|
+
}
|
|
8367
|
+
function getAvailableMacros() {
|
|
8368
|
+
return Object.keys(LAYOUT_MACROS);
|
|
8369
|
+
}
|
|
8370
|
+
function getMacroDescription(name) {
|
|
8371
|
+
const macro2 = resolveLayoutMacro(name);
|
|
8372
|
+
return macro2?.description || null;
|
|
8373
|
+
}
|
|
8374
|
+
var VALUE_CORRECTIONS = {
|
|
8375
|
+
"display": [{ wrong: "flexbox", correct: "flex", confidence: 0.95 }, { wrong: "inline-flexbox", correct: "inline-flex", confidence: 0.95 }],
|
|
8376
|
+
"position": [{ wrong: "abs", correct: "absolute", confidence: 0.9 }, { wrong: "rel", correct: "relative", confidence: 0.9 }],
|
|
8377
|
+
"text-align": [{ wrong: "centered", correct: "center", confidence: 0.85 }, { wrong: "justified", correct: "justify", confidence: 0.85 }],
|
|
8378
|
+
"overflow": [{ wrong: "scrollable", correct: "auto", confidence: 0.8 }],
|
|
8379
|
+
"cursor": [{ wrong: "hand", correct: "pointer", confidence: 0.9 }],
|
|
8380
|
+
"user-select": [{ wrong: "unselectable", correct: "none", confidence: 0.85 }]
|
|
8381
|
+
};
|
|
8382
|
+
var KNOWN_PROPERTIES = ["display", "position", "color", "background", "width", "height", "font-size", "text-align", "cursor", "opacity", "z-index", "overflow", "visibility", "flex", "flex-direction", "justify-content", "align-items", "gap", "grid", "transition", "transform", "animation", "box-shadow", "pointer-events", "user-select", "line-height"];
|
|
8383
|
+
function levenshtein(a, b) {
|
|
8384
|
+
const m = [];
|
|
8385
|
+
for (let i = 0; i <= b.length; i++) m[i] = [i];
|
|
8386
|
+
for (let j = 0; j <= a.length; j++) m[0][j] = j;
|
|
8387
|
+
for (let i = 1; i <= b.length; i++)
|
|
8388
|
+
for (let j = 1; j <= a.length; j++)
|
|
8389
|
+
m[i][j] = Math.min(m[i - 1][j] + 1, m[i][j - 1] + 1, m[i - 1][j - 1] + (a[j - 1] === b[i - 1] ? 0 : 1));
|
|
8390
|
+
return m[b.length][a.length];
|
|
8391
|
+
}
|
|
8392
|
+
function findClosestProperty(prop) {
|
|
8393
|
+
const lp = prop.toLowerCase();
|
|
8394
|
+
let best = null, bestDist = Infinity;
|
|
8395
|
+
for (const k of KNOWN_PROPERTIES) {
|
|
8396
|
+
const d = levenshtein(lp, k);
|
|
8397
|
+
if (d < bestDist && d <= 3) {
|
|
8398
|
+
bestDist = d;
|
|
8399
|
+
best = k;
|
|
8400
|
+
}
|
|
8401
|
+
}
|
|
8402
|
+
return best;
|
|
8403
|
+
}
|
|
8404
|
+
function detectIntent(value, ctx = {}) {
|
|
8405
|
+
const lv = value.toLowerCase();
|
|
8406
|
+
for (const rule of SEMANTIC_INTENTS) {
|
|
8407
|
+
if (rule.pattern.test(lv)) {
|
|
8408
|
+
const r = rule.handler(value, ctx);
|
|
8409
|
+
if (r) return r;
|
|
8410
|
+
}
|
|
8411
|
+
}
|
|
8412
|
+
return null;
|
|
8413
|
+
}
|
|
8414
|
+
var intent = {
|
|
8415
|
+
correct(property, value, context) {
|
|
8416
|
+
const ctx = { property, value, ...context };
|
|
8417
|
+
const si = detectIntent(value, ctx);
|
|
8418
|
+
if (si) return si;
|
|
8419
|
+
if (VALUE_CORRECTIONS[property]) {
|
|
8420
|
+
const c = VALUE_CORRECTIONS[property].find((c2) => c2.wrong === value.toLowerCase());
|
|
8421
|
+
if (c) return { original: value, property, corrected: c.correct, defaults: { [property]: c.correct }, confidence: c.confidence, intent: "value-correction", explanation: `"${value}" is not valid for ${property}. Did you mean "${c.correct}"?` };
|
|
8422
|
+
}
|
|
8423
|
+
const pc = findClosestProperty(property);
|
|
8424
|
+
if (pc && pc !== property.toLowerCase()) {
|
|
8425
|
+
const d = levenshtein(property.toLowerCase(), pc);
|
|
8426
|
+
return { original: property, property, corrected: pc, defaults: {}, confidence: Math.max(0, 1 - d / Math.max(property.length, pc.length)), intent: "property-correction", explanation: `Unknown property "${property}". Did you mean "${pc}"?` };
|
|
8427
|
+
}
|
|
8428
|
+
return null;
|
|
8429
|
+
},
|
|
8430
|
+
heal(styles, mode = "smart", context) {
|
|
8431
|
+
const corrections = [], warnings = [], fixed = {};
|
|
8432
|
+
for (const [prop, value] of Object.entries(styles)) {
|
|
8433
|
+
if (prop.startsWith("_") || prop === "selectors") {
|
|
8434
|
+
fixed[prop] = value;
|
|
8435
|
+
continue;
|
|
8436
|
+
}
|
|
8437
|
+
if (typeof value === "object" && value !== null && prop === "hover") {
|
|
8438
|
+
const hr = this.heal(value, mode, { ...context, property: prop });
|
|
8439
|
+
fixed[prop] = hr.fixed;
|
|
8440
|
+
corrections.push(...hr.corrections);
|
|
8441
|
+
warnings.push(...hr.warnings);
|
|
8442
|
+
continue;
|
|
8443
|
+
}
|
|
8444
|
+
if (typeof value !== "string" && typeof value !== "number") {
|
|
8445
|
+
fixed[prop] = value;
|
|
8446
|
+
continue;
|
|
8447
|
+
}
|
|
8448
|
+
const sv = String(value), corr = this.correct(prop, sv, { ...context, property: prop, value: sv });
|
|
8449
|
+
if (corr) {
|
|
8450
|
+
corrections.push(corr);
|
|
8451
|
+
if (mode === "strict") {
|
|
8452
|
+
warnings.push("[strict] " + corr.explanation);
|
|
8453
|
+
fixed[prop] = sv;
|
|
8454
|
+
} else if (mode === "dev") {
|
|
8455
|
+
fixed[prop] = corr.corrected;
|
|
8456
|
+
Object.assign(fixed, corr.defaults);
|
|
8457
|
+
} else {
|
|
8458
|
+
warnings.push("[auto-fix] " + corr.explanation);
|
|
8459
|
+
fixed[prop] = corr.corrected;
|
|
8460
|
+
Object.assign(fixed, corr.defaults);
|
|
8461
|
+
}
|
|
8462
|
+
} else {
|
|
8463
|
+
fixed[prop] = value;
|
|
8464
|
+
}
|
|
8465
|
+
}
|
|
8466
|
+
return { fixed, corrections, warnings, mode };
|
|
8467
|
+
},
|
|
8468
|
+
getIntent(value, ctx) {
|
|
8469
|
+
const r = detectIntent(value, ctx);
|
|
8470
|
+
return r?.intent || null;
|
|
8471
|
+
},
|
|
8472
|
+
validate(property, value) {
|
|
8473
|
+
if (VALUE_CORRECTIONS[property]) {
|
|
8474
|
+
const c = VALUE_CORRECTIONS[property].find((c2) => c2.wrong === value.toLowerCase());
|
|
8475
|
+
if (c) return c.confidence < 1 ? { valid: false, suggestion: c.correct } : { valid: true };
|
|
8476
|
+
}
|
|
8477
|
+
if (!KNOWN_PROPERTIES.includes(property.toLowerCase())) {
|
|
8478
|
+
const s = findClosestProperty(property);
|
|
8479
|
+
return s ? { valid: false, suggestion: s } : { valid: false };
|
|
8480
|
+
}
|
|
8481
|
+
return { valid: true };
|
|
8482
|
+
},
|
|
8483
|
+
getCorrections(property) {
|
|
8484
|
+
return VALUE_CORRECTIONS[property] || [];
|
|
8485
|
+
},
|
|
8486
|
+
explain(correction) {
|
|
8487
|
+
return correction.explanation;
|
|
8488
|
+
},
|
|
8489
|
+
cssIf: { detect: detectIfPatterns, emit: emitCSSIf },
|
|
8490
|
+
getIntents() {
|
|
8491
|
+
return SEMANTIC_INTENTS.map((r) => ({ pattern: r.pattern.toString(), description: r.description }));
|
|
8492
|
+
},
|
|
8493
|
+
getKnownProperties() {
|
|
8494
|
+
return [...KNOWN_PROPERTIES];
|
|
8495
|
+
},
|
|
8496
|
+
// Layout Macros
|
|
8497
|
+
macro(name) {
|
|
8498
|
+
return expandLayoutMacro(name);
|
|
8499
|
+
},
|
|
8500
|
+
getMacros() {
|
|
8501
|
+
return getAvailableMacros();
|
|
8502
|
+
},
|
|
8503
|
+
getMacroDescription(name) {
|
|
8504
|
+
return getMacroDescription(name);
|
|
8505
|
+
},
|
|
8506
|
+
hasMacro(name) {
|
|
8507
|
+
return name in LAYOUT_MACROS;
|
|
8508
|
+
},
|
|
8509
|
+
/**
|
|
8510
|
+
* Apply a layout macro to an existing styles object.
|
|
8511
|
+
* Merges macro properties with user overrides.
|
|
8512
|
+
*/
|
|
8513
|
+
applyMacro(name, overrides) {
|
|
8514
|
+
const macro2 = expandLayoutMacro(name);
|
|
8515
|
+
if (!macro2) return null;
|
|
8516
|
+
if (!overrides) return macro2;
|
|
8517
|
+
const merged = { ...macro2 };
|
|
8518
|
+
for (const [key, value] of Object.entries(overrides)) {
|
|
8519
|
+
if (key === "atRules" && Array.isArray(value) && Array.isArray(merged.atRules)) {
|
|
8520
|
+
merged.atRules = [...merged.atRules, ...value];
|
|
8521
|
+
} else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
8522
|
+
merged[key] = { ...merged[key] || {}, ...value };
|
|
8523
|
+
} else {
|
|
8524
|
+
merged[key] = value;
|
|
8525
|
+
}
|
|
8526
|
+
}
|
|
8527
|
+
return merged;
|
|
8528
|
+
}
|
|
8529
|
+
};
|
|
8530
|
+
var correct = intent.correct.bind(intent);
|
|
8531
|
+
var heal = intent.heal.bind(intent);
|
|
8532
|
+
var validate = intent.validate.bind(intent);
|
|
8533
|
+
var getIntent = intent.getIntent.bind(intent);
|
|
8534
|
+
var macro = intent.macro.bind(intent);
|
|
8535
|
+
var applyMacro = intent.applyMacro.bind(intent);
|
|
8536
|
+
var getMacros = intent.getMacros.bind(intent);
|
|
8537
|
+
var hasMacro = intent.hasMacro.bind(intent);
|
|
8538
|
+
|
|
8539
|
+
// src/compiler/analyzer.ts
|
|
8540
|
+
var CONFLICTS = [
|
|
8541
|
+
{ props: ["position", "z-index", "zIndex"], msg: "z-index only works on positioned elements (not static)", sev: "warning" },
|
|
8542
|
+
{ props: ["flex-direction", "align-items", "justify-content"], msg: "Flex properties require display: flex", sev: "warning" },
|
|
8543
|
+
{ props: ["grid-template-columns", "grid-template-rows"], msg: "Grid properties require display: grid", sev: "warning" },
|
|
8544
|
+
{ props: ["overflow", "overflow-x", "overflow-y"], msg: "Consider using the overflow shorthand", sev: "hint" }
|
|
8545
|
+
];
|
|
8546
|
+
function detectShorthands(styles) {
|
|
8547
|
+
const d = [];
|
|
8548
|
+
const m = ["margin-top", "margin-right", "margin-bottom", "margin-left"].filter((p) => p in styles);
|
|
8549
|
+
if (m.length >= 3) d.push({ property: "margin", severity: "hint", message: 'Use "margin" shorthand instead of: ' + m.join(", "), suggestion: "margin" });
|
|
8550
|
+
return d;
|
|
8551
|
+
}
|
|
8552
|
+
var StyleAnalyzer = class {
|
|
8553
|
+
_diagnostics = [];
|
|
8554
|
+
analyzeStyle(selector, styles, _opts) {
|
|
8555
|
+
const r = [];
|
|
8556
|
+
for (const [p, v] of Object.entries(styles)) {
|
|
8557
|
+
if (p === "selectors" || p.startsWith("_") || typeof v === "object") continue;
|
|
8558
|
+
const val = intent.validate(p, String(v));
|
|
8559
|
+
if (!val.valid) r.push({ property: p, value: String(v), selector, severity: "warning", message: `"${v}" unrecognized for "${p}"`, suggestion: val.suggestion });
|
|
8560
|
+
}
|
|
8561
|
+
r.push(...detectShorthands(styles));
|
|
8562
|
+
this._diagnostics.push(...r);
|
|
8563
|
+
const sp = Object.keys(styles).filter((k) => typeof styles[k] !== "object");
|
|
8564
|
+
for (const c of CONFLICTS) {
|
|
8565
|
+
const ix = c.props.filter((k) => sp.includes(k));
|
|
8566
|
+
if (ix.length >= 2) r.push({ property: c.props.join(", "), selector, severity: c.sev, message: c.msg });
|
|
8567
|
+
}
|
|
8568
|
+
return r;
|
|
8569
|
+
}
|
|
8570
|
+
analyze(sd) {
|
|
8571
|
+
const ad = [];
|
|
8572
|
+
const sels = sd.selectors || ["&"];
|
|
8573
|
+
for (const s of sels) ad.push(...this.analyzeStyle(s, sd));
|
|
8574
|
+
if (sd.hover && typeof sd.hover === "object") for (const s of sels) ad.push(...this.analyzeStyle(s + ":hover", sd.hover));
|
|
8575
|
+
return {
|
|
8576
|
+
diagnostics: ad,
|
|
8577
|
+
conflicts: [],
|
|
8578
|
+
breakpoints: [],
|
|
8579
|
+
unusedSelectors: [],
|
|
8580
|
+
deadStyles: [],
|
|
8581
|
+
duplicationWarnings: [],
|
|
8582
|
+
optimizationSuggestions: [],
|
|
8583
|
+
stats: {
|
|
8584
|
+
totalProperties: Object.keys(sd).filter((k) => !["selectors", "atRules", "nestedRules", "hover", "themes"].includes(k) && !k.startsWith("_")).length,
|
|
8585
|
+
totalSelectors: sels.length,
|
|
8586
|
+
shorthandOpportunities: ad.filter((d) => d.message.includes("shorthand")).length,
|
|
8587
|
+
animationSuggestions: 0,
|
|
8588
|
+
responsiveIssues: 0
|
|
8589
|
+
}
|
|
8590
|
+
};
|
|
8591
|
+
}
|
|
8592
|
+
reset() {
|
|
8593
|
+
this._diagnostics = [];
|
|
8594
|
+
}
|
|
8595
|
+
getDiagnostics() {
|
|
8596
|
+
return [...this._diagnostics];
|
|
8597
|
+
}
|
|
8598
|
+
};
|
|
8599
|
+
function analyze(sd) {
|
|
8600
|
+
return new StyleAnalyzer().analyze(sd);
|
|
8601
|
+
}
|
|
8602
|
+
|
|
8603
|
+
// src/index.ts
|
|
6880
8604
|
var VERSION2 = "3.0.0";
|
|
6881
8605
|
var index_default = chain;
|
|
6882
8606
|
if (typeof process !== "undefined" && process.env?.NODE_ENV !== "test") {
|
|
@@ -6895,31 +8619,51 @@ export {
|
|
|
6895
8619
|
ChainCSSPrefixer,
|
|
6896
8620
|
DesignTokens,
|
|
6897
8621
|
PersistentCache,
|
|
8622
|
+
SCROLL_PRESETS,
|
|
8623
|
+
StyleAnalyzer,
|
|
8624
|
+
StyleGraphCompiler,
|
|
6898
8625
|
Theme,
|
|
6899
8626
|
VERSION2 as VERSION,
|
|
8627
|
+
add,
|
|
8628
|
+
analyze as analyzeStyle,
|
|
6900
8629
|
animationPresets,
|
|
8630
|
+
auditContrast,
|
|
6901
8631
|
autoDetector,
|
|
6902
8632
|
buildChain,
|
|
6903
8633
|
chain,
|
|
6904
8634
|
smartChain as chainV3,
|
|
8635
|
+
checkContrast,
|
|
6905
8636
|
clearTimeline,
|
|
6906
8637
|
compileChainCSS,
|
|
8638
|
+
compileGraph,
|
|
8639
|
+
compileScrollAnimation,
|
|
8640
|
+
compileScrollAnimations,
|
|
6907
8641
|
configureAtomic,
|
|
8642
|
+
contrastRatio,
|
|
8643
|
+
convert,
|
|
8644
|
+
correct,
|
|
6908
8645
|
createAnimation,
|
|
8646
|
+
createContextualToken,
|
|
6909
8647
|
createTokens as createDesignTokens,
|
|
8648
|
+
createScrollAnimation,
|
|
6910
8649
|
createSmartComponent,
|
|
6911
8650
|
createTheme,
|
|
6912
8651
|
createThemeContract,
|
|
6913
8652
|
createTokens2 as createTokens,
|
|
6914
8653
|
index_default as default,
|
|
8654
|
+
divide,
|
|
6915
8655
|
enableDebug,
|
|
6916
8656
|
enableTimeline,
|
|
6917
8657
|
expandShorthand,
|
|
6918
8658
|
exportTimeline,
|
|
8659
|
+
fluidType,
|
|
8660
|
+
generateContextualCSS,
|
|
6919
8661
|
getAnimationPreset,
|
|
6920
8662
|
getAnimationPresetNames,
|
|
6921
8663
|
getAvailableShorthands,
|
|
8664
|
+
getIntent,
|
|
6922
8665
|
getPropertySuggestion,
|
|
8666
|
+
getScrollPresets,
|
|
6923
8667
|
getShorthandSuggestion,
|
|
6924
8668
|
getStyleChanges,
|
|
6925
8669
|
getStyleDiff,
|
|
@@ -6928,17 +8672,29 @@ export {
|
|
|
6928
8672
|
getSuggestions,
|
|
6929
8673
|
handleShorthand,
|
|
6930
8674
|
hasAnimationPreset,
|
|
8675
|
+
heal,
|
|
6931
8676
|
helpers,
|
|
6932
8677
|
injectChainStyles,
|
|
8678
|
+
intent,
|
|
6933
8679
|
isShorthand,
|
|
6934
8680
|
macros,
|
|
8681
|
+
math,
|
|
8682
|
+
multiply,
|
|
8683
|
+
orchestrator,
|
|
6935
8684
|
recipe,
|
|
8685
|
+
resolveContextual,
|
|
6936
8686
|
runtimeChain,
|
|
8687
|
+
scale,
|
|
8688
|
+
scrollTimeline,
|
|
6937
8689
|
setBreakpoints,
|
|
6938
8690
|
shorthandMap,
|
|
6939
8691
|
smartChain,
|
|
8692
|
+
subtract,
|
|
8693
|
+
toPx,
|
|
6940
8694
|
tokens2 as tokens,
|
|
6941
8695
|
useSmartStyles,
|
|
6942
8696
|
validateTheme,
|
|
8697
|
+
validateTokenRelationships,
|
|
8698
|
+
validate as validateValue,
|
|
6943
8699
|
withSmartStyles
|
|
6944
8700
|
};
|