domflax 0.1.4 → 0.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/README.md +47 -29
- package/dist/{chunk-EVENAJYI.js → chunk-EYQXQQQH.js} +3 -3
- package/dist/{chunk-3Z5ZWLXX.js → chunk-FPT4EJ6Q.js} +805 -1612
- package/dist/chunk-FPT4EJ6Q.js.map +1 -0
- package/dist/{chunk-5FWENSD2.js → chunk-JBM3MJRM.js} +149 -10
- package/dist/chunk-JBM3MJRM.js.map +1 -0
- package/dist/{chunk-H5KTGI3A.js → chunk-TTJEXWAC.js} +172 -5
- package/dist/chunk-TTJEXWAC.js.map +1 -0
- package/dist/cli.cjs +1032 -1640
- package/dist/cli.cjs.map +1 -1
- package/dist/cli.js +30 -10
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1116 -1627
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +226 -485
- package/dist/index.d.ts +226 -485
- package/dist/index.js +16 -36
- package/dist/{pattern-CP9_HpVK.d.cts → pattern-DotR_dHs.d.cts} +1 -1
- package/dist/pattern-kit.cjs +60 -1
- package/dist/pattern-kit.cjs.map +1 -1
- package/dist/pattern-kit.d.cts +2 -2
- package/dist/pattern-kit.d.ts +2 -2
- package/dist/pattern-kit.js +1 -1
- package/dist/{pattern-CYgsv-jO.d.ts → pattern-urm5uuwj.d.ts} +1 -1
- package/dist/{resolve-ops-Ci7LgYHC.d.ts → resolve-ops-D8aQina5.d.cts} +11 -0
- package/dist/{resolve-ops-Ci7LgYHC.d.cts → resolve-ops-D8aQina5.d.ts} +11 -0
- package/dist/verify.d.cts +1 -1
- package/dist/verify.d.ts +1 -1
- package/dist/webpack-loader.cjs +1014 -1578
- package/dist/webpack-loader.cjs.map +1 -1
- package/dist/webpack-loader.d.cts +8 -2
- package/dist/webpack-loader.d.ts +8 -2
- package/dist/webpack-loader.js +7 -4
- package/dist/webpack-loader.js.map +1 -1
- package/dist/worker.cjs +983 -1601
- package/dist/worker.cjs.map +1 -1
- package/dist/worker.js +3 -3
- package/package.json +1 -1
- package/dist/chunk-3Z5ZWLXX.js.map +0 -1
- package/dist/chunk-5FWENSD2.js.map +0 -1
- package/dist/chunk-H5KTGI3A.js.map +0 -1
- /package/dist/{chunk-EVENAJYI.js.map → chunk-EYQXQQQH.js.map} +0 -0
package/dist/cli.cjs
CHANGED
|
@@ -175,8 +175,8 @@ init_cjs_shims();
|
|
|
175
175
|
|
|
176
176
|
// ../cli/src/index.ts
|
|
177
177
|
init_cjs_shims();
|
|
178
|
-
var
|
|
179
|
-
var
|
|
178
|
+
var import_node_fs6 = require("fs");
|
|
179
|
+
var path11 = __toESM(require("path"), 1);
|
|
180
180
|
|
|
181
181
|
// ../cli/src/options.ts
|
|
182
182
|
init_cjs_shims();
|
|
@@ -210,6 +210,7 @@ function parseInvocation(argv) {
|
|
|
210
210
|
css: { type: "string", multiple: true },
|
|
211
211
|
"dry-run": { type: "boolean", default: false },
|
|
212
212
|
report: { type: "boolean", default: false },
|
|
213
|
+
details: { type: "boolean", default: false },
|
|
213
214
|
"dangerously-overwrite-source": { type: "boolean", default: false },
|
|
214
215
|
"no-git-check": { type: "boolean", default: false },
|
|
215
216
|
"no-interactive": { type: "boolean", default: false },
|
|
@@ -231,6 +232,7 @@ function parseInvocation(argv) {
|
|
|
231
232
|
css: values.css ?? [],
|
|
232
233
|
dryRun: values["dry-run"] === true,
|
|
233
234
|
report: values.report === true,
|
|
235
|
+
details: values.details === true,
|
|
234
236
|
dangerouslyOverwriteSource: values["dangerously-overwrite-source"] === true,
|
|
235
237
|
noGitCheck: values["no-git-check"] === true,
|
|
236
238
|
interactive: values["no-interactive"] !== true && values.yes !== true,
|
|
@@ -247,7 +249,7 @@ function shouldPrompt(options, isTty) {
|
|
|
247
249
|
var USAGE = [
|
|
248
250
|
"Usage: domflax [paths...] [options]",
|
|
249
251
|
"",
|
|
250
|
-
"Optimizes .jsx/.tsx files (flatten redundant wrappers + compress class sets).",
|
|
252
|
+
"Optimizes .jsx/.tsx/.html files (flatten redundant wrappers + compress class sets).",
|
|
251
253
|
"Source is READ-ONLY by default \u2014 output goes to --out or ./domflax-out.",
|
|
252
254
|
"",
|
|
253
255
|
"Arguments:",
|
|
@@ -259,6 +261,7 @@ var USAGE = [
|
|
|
259
261
|
" --css <file...> stylesheets feeding the custom-CSS provider",
|
|
260
262
|
" --dry-run print per-file diffs; write nothing",
|
|
261
263
|
" --report print a summary of what changed",
|
|
264
|
+
" --details print per-file optimization stats (nodes/classes/bytes)",
|
|
262
265
|
" --dangerously-overwrite-source overwrite source in place (needs a clean git tree)",
|
|
263
266
|
" --no-git-check skip the clean-git-tree gate",
|
|
264
267
|
" --safety <0|1|2|3> optimization aggressiveness (default: 2)",
|
|
@@ -332,6 +335,7 @@ function runPool(files, init, plan, onWrote) {
|
|
|
332
335
|
const workerPath = resolveWorkerPath();
|
|
333
336
|
const totals = emptyTotals();
|
|
334
337
|
const wrote = [];
|
|
338
|
+
const changedFiles = [];
|
|
335
339
|
const errors = [];
|
|
336
340
|
let failures = 0;
|
|
337
341
|
const budgetBytes = plan.budgetMB * 1024 * 1024;
|
|
@@ -340,7 +344,7 @@ function runPool(files, init, plan, onWrote) {
|
|
|
340
344
|
const total = files.length;
|
|
341
345
|
let respawns = 0;
|
|
342
346
|
const maxRespawns = total + plan.workers + 8;
|
|
343
|
-
return new Promise((
|
|
347
|
+
return new Promise((resolve7) => {
|
|
344
348
|
const handles = /* @__PURE__ */ new Set();
|
|
345
349
|
const finishIfDone = () => {
|
|
346
350
|
if (completed < total) return;
|
|
@@ -354,7 +358,7 @@ function runPool(files, init, plan, onWrote) {
|
|
|
354
358
|
}
|
|
355
359
|
}
|
|
356
360
|
handles.clear();
|
|
357
|
-
|
|
361
|
+
resolve7({ totals, failures, wrote, changedFiles, errors });
|
|
358
362
|
};
|
|
359
363
|
const recordFailure = (file, error) => {
|
|
360
364
|
failures += 1;
|
|
@@ -394,6 +398,7 @@ function runPool(files, init, plan, onWrote) {
|
|
|
394
398
|
addStats(totals, msg.stats, msg.changed);
|
|
395
399
|
if (msg.wrote) {
|
|
396
400
|
wrote.push(msg.wrote);
|
|
401
|
+
changedFiles.push({ dest: msg.wrote, stats: msg.stats });
|
|
397
402
|
onWrote?.(msg.wrote);
|
|
398
403
|
}
|
|
399
404
|
} else {
|
|
@@ -577,6 +582,7 @@ function defaultMeta(safetyFloor = 0) {
|
|
|
577
582
|
hasDynamicChildren: false,
|
|
578
583
|
isComponent: false,
|
|
579
584
|
hasDangerousHtml: false,
|
|
585
|
+
hasUnresolvedClasses: false,
|
|
580
586
|
targetedByCombinator: false,
|
|
581
587
|
targetedByStructuralPseudo: false,
|
|
582
588
|
selectorDependents: 0,
|
|
@@ -1624,6 +1630,9 @@ function classifyFlattenOps(before, after, ops, norm) {
|
|
|
1624
1630
|
if (!wrapper || wrapper.kind !== "element") {
|
|
1625
1631
|
return { kind: "provably-safe", wrapperId: null, childId: null };
|
|
1626
1632
|
}
|
|
1633
|
+
if (wrapper.meta.hasUnresolvedClasses) {
|
|
1634
|
+
return { kind: "needs-verification", wrapperId, childId: survivingChildOf(ops, wrapper, before) };
|
|
1635
|
+
}
|
|
1627
1636
|
const childId = survivingChildOf(ops, wrapper, before);
|
|
1628
1637
|
const wrapperComputed = norm.normalizeStyleMap(wrapper.computed);
|
|
1629
1638
|
const childAfter = childId != null ? getElement(after, childId)?.computed ?? null : null;
|
|
@@ -1927,15 +1936,26 @@ function residualStyle(computed2, covered, norm) {
|
|
|
1927
1936
|
}
|
|
1928
1937
|
return { blocks };
|
|
1929
1938
|
}
|
|
1939
|
+
function joinedLength(tokens) {
|
|
1940
|
+
if (tokens.length === 0) return 0;
|
|
1941
|
+
let len = tokens.length - 1;
|
|
1942
|
+
for (const t of tokens) len += t.length;
|
|
1943
|
+
return len;
|
|
1944
|
+
}
|
|
1945
|
+
var COMPRESS_FLOOR = 1;
|
|
1930
1946
|
function syncClassesFromComputed(doc, resolver, norm) {
|
|
1931
1947
|
const sink = createSyntheticSink();
|
|
1948
|
+
const isDroppable = (t) => resolver.owns(t) && resolver.selectorUsage(t).droppable;
|
|
1932
1949
|
for (const id of elementIds(doc)) {
|
|
1933
1950
|
const el = getElement(doc, id);
|
|
1934
1951
|
if (!el) continue;
|
|
1935
|
-
if (!el.meta.styleDirty) continue;
|
|
1936
1952
|
if (el.classes.opaque || el.classes.hasDynamic) continue;
|
|
1953
|
+
const compressOnly = !el.meta.styleDirty;
|
|
1954
|
+
if (compressOnly && el.meta.safetyFloor < COMPRESS_FLOOR) continue;
|
|
1937
1955
|
const tokens = staticTokensOf(el.classes);
|
|
1938
|
-
|
|
1956
|
+
if (tokens.length === 0) continue;
|
|
1957
|
+
const retained = tokens.filter((t) => !isDroppable(t));
|
|
1958
|
+
if (compressOnly && retained.length === tokens.length) continue;
|
|
1939
1959
|
const covered = retained.length > 0 ? resolver.resolve({ classes: retained }).styles : null;
|
|
1940
1960
|
const target = covered ? residualStyle(el.computed, covered, norm) : el.computed;
|
|
1941
1961
|
const ctx = { normalizer: norm, sink };
|
|
@@ -1946,7 +1966,7 @@ function syncClassesFromComputed(doc, resolver, norm) {
|
|
|
1946
1966
|
const seen = /* @__PURE__ */ new Set();
|
|
1947
1967
|
for (const t of tokens) {
|
|
1948
1968
|
if (seen.has(t)) continue;
|
|
1949
|
-
const keep = emittedSet.has(t) || !
|
|
1969
|
+
const keep = emittedSet.has(t) || !isDroppable(t);
|
|
1950
1970
|
if (keep) {
|
|
1951
1971
|
next.push(t);
|
|
1952
1972
|
seen.add(t);
|
|
@@ -1958,10 +1978,99 @@ function syncClassesFromComputed(doc, resolver, norm) {
|
|
|
1958
1978
|
seen.add(c);
|
|
1959
1979
|
}
|
|
1960
1980
|
if (sameTokens(next, tokens)) continue;
|
|
1981
|
+
if (compressOnly) {
|
|
1982
|
+
if (!norm.equals(resolver.resolve({ classes: next }).styles, el.computed)) continue;
|
|
1983
|
+
if (joinedLength(next) > joinedLength(tokens)) continue;
|
|
1984
|
+
}
|
|
1961
1985
|
el.classes = staticClassList(el.classes, next);
|
|
1962
1986
|
}
|
|
1963
1987
|
}
|
|
1964
1988
|
|
|
1989
|
+
// ../core/src/compress-engine.ts
|
|
1990
|
+
init_cjs_shims();
|
|
1991
|
+
var SEP = "";
|
|
1992
|
+
function tupleKey(condition, property, value, important) {
|
|
1993
|
+
return `${condition}${SEP}${property}${SEP}${value}${SEP}${important ? "1" : "0"}`;
|
|
1994
|
+
}
|
|
1995
|
+
function styleMapTuples(map, norm) {
|
|
1996
|
+
const out = [];
|
|
1997
|
+
const normalized = norm.normalizeStyleMap(map);
|
|
1998
|
+
for (const [ck, block] of normalized.blocks) {
|
|
1999
|
+
for (const [prop, decl] of block.decls) {
|
|
2000
|
+
out.push(tupleKey(String(ck), String(prop), String(decl.value), decl.important));
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
return out;
|
|
2004
|
+
}
|
|
2005
|
+
var DEFAULT_MAX_UNIVERSE = 20;
|
|
2006
|
+
function minStringCover(universe, vocabulary, options = {}) {
|
|
2007
|
+
const uniq = [...new Set(universe)];
|
|
2008
|
+
if (uniq.length === 0) return [];
|
|
2009
|
+
const n = uniq.length;
|
|
2010
|
+
const max = options.maxUniverse ?? DEFAULT_MAX_UNIVERSE;
|
|
2011
|
+
if (n > max) return null;
|
|
2012
|
+
const bitOf = /* @__PURE__ */ new Map();
|
|
2013
|
+
uniq.forEach((t, i) => bitOf.set(t, i));
|
|
2014
|
+
const byMask = /* @__PURE__ */ new Map();
|
|
2015
|
+
for (const entry of vocabulary) {
|
|
2016
|
+
if (entry.tuples.length === 0) continue;
|
|
2017
|
+
let mask = 0;
|
|
2018
|
+
let ok = true;
|
|
2019
|
+
for (const t of entry.tuples) {
|
|
2020
|
+
const b3 = bitOf.get(t);
|
|
2021
|
+
if (b3 === void 0) {
|
|
2022
|
+
ok = false;
|
|
2023
|
+
break;
|
|
2024
|
+
}
|
|
2025
|
+
mask |= 1 << b3;
|
|
2026
|
+
}
|
|
2027
|
+
if (!ok || mask === 0) continue;
|
|
2028
|
+
const cost = entry.token.length + 1;
|
|
2029
|
+
const prev = byMask.get(mask);
|
|
2030
|
+
if (!prev || cost < prev.cost || cost === prev.cost && entry.token < prev.token) {
|
|
2031
|
+
byMask.set(mask, { token: entry.token, mask, cost });
|
|
2032
|
+
}
|
|
2033
|
+
}
|
|
2034
|
+
const cands = [...byMask.values()];
|
|
2035
|
+
if (cands.length === 0) return null;
|
|
2036
|
+
const full = (1 << n) - 1;
|
|
2037
|
+
const byBit = Array.from({ length: n }, () => []);
|
|
2038
|
+
cands.forEach((c, ci) => {
|
|
2039
|
+
for (let b3 = 0; b3 < n; b3 += 1) if (c.mask & 1 << b3) byBit[b3].push(ci);
|
|
2040
|
+
});
|
|
2041
|
+
const size = full + 1;
|
|
2042
|
+
const dp = new Float64Array(size).fill(Infinity);
|
|
2043
|
+
const fromCand = new Int32Array(size).fill(-1);
|
|
2044
|
+
const fromMask = new Int32Array(size).fill(-1);
|
|
2045
|
+
dp[0] = 0;
|
|
2046
|
+
for (let mask = 0; mask < full; mask += 1) {
|
|
2047
|
+
const cur = dp[mask];
|
|
2048
|
+
if (!Number.isFinite(cur)) continue;
|
|
2049
|
+
let b3 = 0;
|
|
2050
|
+
while (b3 < n && mask & 1 << b3) b3 += 1;
|
|
2051
|
+
for (const ci of byBit[b3]) {
|
|
2052
|
+
const c = cands[ci];
|
|
2053
|
+
const nm = mask | c.mask;
|
|
2054
|
+
const cost = cur + c.cost;
|
|
2055
|
+
if (cost < dp[nm]) {
|
|
2056
|
+
dp[nm] = cost;
|
|
2057
|
+
fromCand[nm] = ci;
|
|
2058
|
+
fromMask[nm] = mask;
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
}
|
|
2062
|
+
if (!Number.isFinite(dp[full])) return null;
|
|
2063
|
+
const chosen = [];
|
|
2064
|
+
let m2 = full;
|
|
2065
|
+
while (m2 !== 0) {
|
|
2066
|
+
const ci = fromCand[m2];
|
|
2067
|
+
if (ci < 0) return null;
|
|
2068
|
+
chosen.push(cands[ci].token);
|
|
2069
|
+
m2 = fromMask[m2];
|
|
2070
|
+
}
|
|
2071
|
+
return [...new Set(chosen)].sort();
|
|
2072
|
+
}
|
|
2073
|
+
|
|
1965
2074
|
// ../frontend-html/src/index.ts
|
|
1966
2075
|
init_cjs_shims();
|
|
1967
2076
|
|
|
@@ -2153,9 +2262,10 @@ function doParse(code, ctx) {
|
|
|
2153
2262
|
native: document2
|
|
2154
2263
|
};
|
|
2155
2264
|
doc.sources.set(FILE_ID, sourceFile);
|
|
2156
|
-
const resolveComputed = (tokens, tag, nodeId) => {
|
|
2265
|
+
const resolveComputed = (tokens, tag, nodeId, meta) => {
|
|
2157
2266
|
if (tokens.length === 0) return emptyStyleMap();
|
|
2158
2267
|
const res = ctx.resolver.resolve({ classes: tokens, element: { tagName: tag, namespace: "html" } });
|
|
2268
|
+
if (res.unknown.length > 0) meta.hasUnresolvedClasses = true;
|
|
2159
2269
|
for (const w2 of res.warnings) {
|
|
2160
2270
|
diagnostics.push({
|
|
2161
2271
|
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
@@ -2245,7 +2355,7 @@ function doParse(code, ctx) {
|
|
|
2245
2355
|
order.push(a.name);
|
|
2246
2356
|
}
|
|
2247
2357
|
const attrs = { entries, spreads: [], order };
|
|
2248
|
-
const computed2 = resolveComputed(classTokens, tag, id);
|
|
2358
|
+
const computed2 = resolveComputed(classTokens, tag, id, meta);
|
|
2249
2359
|
const children = [];
|
|
2250
2360
|
if (!opaqueSubtree) {
|
|
2251
2361
|
for (const c of node.childNodes ?? []) appendChild(c, id, children);
|
|
@@ -2656,6 +2766,7 @@ function doParse2(code, ctx) {
|
|
|
2656
2766
|
element: { tagName: tag, namespace: component ? void 0 : "html" }
|
|
2657
2767
|
});
|
|
2658
2768
|
computed2 = ctx.normalizer.normalizeStyleMap(res.styles);
|
|
2769
|
+
if (res.unknown.length > 0) meta.hasUnresolvedClasses = true;
|
|
2659
2770
|
for (const w2 of res.warnings) {
|
|
2660
2771
|
diagnostics.push({
|
|
2661
2772
|
code: "DF_STYLE_CONFLICT_UNRESOLVED",
|
|
@@ -2698,13 +2809,13 @@ function doParse2(code, ctx) {
|
|
|
2698
2809
|
};
|
|
2699
2810
|
const roots = [];
|
|
2700
2811
|
traverse(ast, {
|
|
2701
|
-
JSXElement(
|
|
2702
|
-
roots.push(
|
|
2703
|
-
|
|
2812
|
+
JSXElement(path12) {
|
|
2813
|
+
roots.push(path12.node);
|
|
2814
|
+
path12.skip();
|
|
2704
2815
|
},
|
|
2705
|
-
JSXFragment(
|
|
2706
|
-
roots.push(
|
|
2707
|
-
|
|
2816
|
+
JSXFragment(path12) {
|
|
2817
|
+
roots.push(path12.node);
|
|
2818
|
+
path12.skip();
|
|
2708
2819
|
}
|
|
2709
2820
|
});
|
|
2710
2821
|
const rootFrag = doc.nodes.get(doc.root);
|
|
@@ -3069,6 +3180,18 @@ var BOX_SIDES = {
|
|
|
3069
3180
|
padding: ["padding-top", "padding-right", "padding-bottom", "padding-left"],
|
|
3070
3181
|
margin: ["margin-top", "margin-right", "margin-bottom", "margin-left"],
|
|
3071
3182
|
inset: ["top", "right", "bottom", "left"],
|
|
3183
|
+
"scroll-margin": [
|
|
3184
|
+
"scroll-margin-top",
|
|
3185
|
+
"scroll-margin-right",
|
|
3186
|
+
"scroll-margin-bottom",
|
|
3187
|
+
"scroll-margin-left"
|
|
3188
|
+
],
|
|
3189
|
+
"scroll-padding": [
|
|
3190
|
+
"scroll-padding-top",
|
|
3191
|
+
"scroll-padding-right",
|
|
3192
|
+
"scroll-padding-bottom",
|
|
3193
|
+
"scroll-padding-left"
|
|
3194
|
+
],
|
|
3072
3195
|
"border-width": [
|
|
3073
3196
|
"border-top-width",
|
|
3074
3197
|
"border-right-width",
|
|
@@ -3086,8 +3209,35 @@ var BOX_SIDES = {
|
|
|
3086
3209
|
"border-right-color",
|
|
3087
3210
|
"border-bottom-color",
|
|
3088
3211
|
"border-left-color"
|
|
3212
|
+
],
|
|
3213
|
+
// `border-radius` 1–4 value form maps to the four CORNERS (TL, TR, BR, BL) — the same positional
|
|
3214
|
+
// pattern boxFourSides implements. Only the slash-free form is expanded (see expandShorthand).
|
|
3215
|
+
"border-radius": [
|
|
3216
|
+
"border-top-left-radius",
|
|
3217
|
+
"border-top-right-radius",
|
|
3218
|
+
"border-bottom-right-radius",
|
|
3219
|
+
"border-bottom-left-radius"
|
|
3089
3220
|
]
|
|
3090
3221
|
};
|
|
3222
|
+
var AXIS_PAIRS = {
|
|
3223
|
+
overflow: ["overflow-x", "overflow-y"],
|
|
3224
|
+
"overscroll-behavior": ["overscroll-behavior-x", "overscroll-behavior-y"],
|
|
3225
|
+
"place-items": ["align-items", "justify-items"],
|
|
3226
|
+
"place-content": ["align-content", "justify-content"],
|
|
3227
|
+
"place-self": ["align-self", "justify-self"]
|
|
3228
|
+
};
|
|
3229
|
+
var LOGICAL_PAIRS = {
|
|
3230
|
+
"padding-inline": ["padding-left", "padding-right"],
|
|
3231
|
+
"padding-block": ["padding-top", "padding-bottom"],
|
|
3232
|
+
"margin-inline": ["margin-left", "margin-right"],
|
|
3233
|
+
"margin-block": ["margin-top", "margin-bottom"],
|
|
3234
|
+
"inset-inline": ["left", "right"],
|
|
3235
|
+
"inset-block": ["top", "bottom"],
|
|
3236
|
+
"scroll-padding-inline": ["scroll-padding-left", "scroll-padding-right"],
|
|
3237
|
+
"scroll-padding-block": ["scroll-padding-top", "scroll-padding-bottom"],
|
|
3238
|
+
"scroll-margin-inline": ["scroll-margin-left", "scroll-margin-right"],
|
|
3239
|
+
"scroll-margin-block": ["scroll-margin-top", "scroll-margin-bottom"]
|
|
3240
|
+
};
|
|
3091
3241
|
function splitTopLevel(value) {
|
|
3092
3242
|
const out = [];
|
|
3093
3243
|
let depth = 0;
|
|
@@ -3121,6 +3271,7 @@ function boxFourSides(values) {
|
|
|
3121
3271
|
}
|
|
3122
3272
|
}
|
|
3123
3273
|
function expandShorthand(prop, value) {
|
|
3274
|
+
if (prop === "border-radius" && value.includes("/")) return [[prop, value]];
|
|
3124
3275
|
const box = BOX_SIDES[prop];
|
|
3125
3276
|
if (box) {
|
|
3126
3277
|
const parts = splitTopLevel(value);
|
|
@@ -3130,6 +3281,19 @@ function expandShorthand(prop, value) {
|
|
|
3130
3281
|
}
|
|
3131
3282
|
return [[prop, value]];
|
|
3132
3283
|
}
|
|
3284
|
+
const axis = AXIS_PAIRS[prop];
|
|
3285
|
+
if (axis) {
|
|
3286
|
+
const parts = splitTopLevel(value);
|
|
3287
|
+
if (parts.length === 1) return [[axis[0], parts[0]], [axis[1], parts[0]]];
|
|
3288
|
+
if (parts.length === 2) return [[axis[0], parts[0]], [axis[1], parts[1]]];
|
|
3289
|
+
return [[prop, value]];
|
|
3290
|
+
}
|
|
3291
|
+
const logical = LOGICAL_PAIRS[prop];
|
|
3292
|
+
if (logical) {
|
|
3293
|
+
const parts = splitTopLevel(value);
|
|
3294
|
+
if (parts.length === 1) return [[logical[0], parts[0]], [logical[1], parts[0]]];
|
|
3295
|
+
return [[prop, value]];
|
|
3296
|
+
}
|
|
3133
3297
|
if (prop === "gap" || prop === "grid-gap") {
|
|
3134
3298
|
const parts = splitTopLevel(value);
|
|
3135
3299
|
if (parts.length === 1) {
|
|
@@ -3287,7 +3451,12 @@ var VISUAL_PROPERTIES = /* @__PURE__ */ new Set([
|
|
|
3287
3451
|
"border-right-color",
|
|
3288
3452
|
"border-bottom-color",
|
|
3289
3453
|
"border-left-color",
|
|
3290
|
-
|
|
3454
|
+
// `border-radius` is expanded to its four corner longhands by the shared normalizer, so the
|
|
3455
|
+
// paint-establishing check must match those (a rounded wrapper still clips its background).
|
|
3456
|
+
"border-top-left-radius",
|
|
3457
|
+
"border-top-right-radius",
|
|
3458
|
+
"border-bottom-right-radius",
|
|
3459
|
+
"border-bottom-left-radius",
|
|
3291
3460
|
"box-shadow",
|
|
3292
3461
|
"outline",
|
|
3293
3462
|
"outline-width",
|
|
@@ -3313,6 +3482,7 @@ var hasOwnVisualStyle = (node, ctx) => {
|
|
|
3313
3482
|
const el = asElement(node);
|
|
3314
3483
|
if (!el) return false;
|
|
3315
3484
|
if (el.meta.hasOwnVisualStyle) return true;
|
|
3485
|
+
if (el.meta.hasUnresolvedClasses) return true;
|
|
3316
3486
|
const computedMap = ctx.computedOf(el) ?? el.computed;
|
|
3317
3487
|
const norm = normalizer.normalizeStyleMap(computedMap);
|
|
3318
3488
|
for (const block of norm.blocks.values()) {
|
|
@@ -3530,7 +3700,163 @@ init_cjs_shims();
|
|
|
3530
3700
|
// ../patterns/src/_registry.generated.ts
|
|
3531
3701
|
init_cjs_shims();
|
|
3532
3702
|
|
|
3533
|
-
// ../patterns/src/library/
|
|
3703
|
+
// ../patterns/src/library/flex/flex-center-wrapper.pattern.ts
|
|
3704
|
+
init_cjs_shims();
|
|
3705
|
+
var flexCenterWrapper = definePattern({
|
|
3706
|
+
name: "flex-center-wrapper",
|
|
3707
|
+
category: "flatten/flex/flex-center-wrapper",
|
|
3708
|
+
safety: 2,
|
|
3709
|
+
doc: {
|
|
3710
|
+
title: "Flatten flex-centering wrapper",
|
|
3711
|
+
summary: "A div that only centers a single child (display:flex; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
|
|
3712
|
+
before: '<div style="display:flex;align-items:center;justify-content:center"><Child/></div>',
|
|
3713
|
+
after: '<Child style="place-self:center"/>',
|
|
3714
|
+
safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic children, and is not a combinator subject; inheritable styles are folded onto the child before removal."
|
|
3715
|
+
},
|
|
3716
|
+
match: {
|
|
3717
|
+
tag: "div",
|
|
3718
|
+
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
3719
|
+
onlyChild: "element",
|
|
3720
|
+
paintsNothing: true
|
|
3721
|
+
},
|
|
3722
|
+
rewrite: {
|
|
3723
|
+
flattenInto: "child",
|
|
3724
|
+
childGains: { placeSelf: "center" }
|
|
3725
|
+
},
|
|
3726
|
+
// Collapsing a flex-centering wrapper to `place-self:center` on the child is render-identical ONLY
|
|
3727
|
+
// when the child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there
|
|
3728
|
+
// `place-self`'s align-self AND justify-self both take effect). Under that ONE context the flatten is
|
|
3729
|
+
// classified `provably-safe` and commits; under a flex/block/unknown parent — or when the wrapper
|
|
3730
|
+
// drops any own style — it stays `needs-verification` and the conservative production gate PRESERVES
|
|
3731
|
+
// it. Op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is additionally
|
|
3732
|
+
// asserted by the invariant suite over every pattern.
|
|
3733
|
+
test: {
|
|
3734
|
+
cases: [
|
|
3735
|
+
{
|
|
3736
|
+
name: "grid parent \u2192 flattened (child gains place-self-center)",
|
|
3737
|
+
before: '<div className="grid"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
3738
|
+
after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
|
|
3739
|
+
}
|
|
3740
|
+
],
|
|
3741
|
+
noMatch: [
|
|
3742
|
+
// Non-grid (flex) parent (document root): `justify-self` is ignored in flex → not provably safe.
|
|
3743
|
+
'<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
3744
|
+
// Grid parent, but the wrapper drops padding when removed → not layout-neutral (rule 3).
|
|
3745
|
+
'<div className="grid"><div className="p-4 flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
3746
|
+
// Grid parent forcing place-items-center: the wrapper would not fill its area → fill guard skips.
|
|
3747
|
+
'<div className="grid place-items-center"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
3748
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
3749
|
+
'<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
3750
|
+
]
|
|
3751
|
+
}
|
|
3752
|
+
});
|
|
3753
|
+
|
|
3754
|
+
// ../patterns/src/library/fragment/redundant-fragment.pattern.ts
|
|
3755
|
+
init_cjs_shims();
|
|
3756
|
+
function parentIsRedundantFragment(node, ctx) {
|
|
3757
|
+
const el = node;
|
|
3758
|
+
if (el.kind !== "element") return false;
|
|
3759
|
+
const parentId = el.parent;
|
|
3760
|
+
if (parentId == null) return false;
|
|
3761
|
+
const parent = ctx.doc.nodes.get(parentId);
|
|
3762
|
+
if (!parent || parent.kind !== "fragment") return false;
|
|
3763
|
+
if (parent.parent == null) return false;
|
|
3764
|
+
if (parent.children.length !== 1) return false;
|
|
3765
|
+
const m2 = parent.meta;
|
|
3766
|
+
if (m2.hasKey || m2.hasRef || m2.hasEventHandlers || m2.hasDynamicChildren || m2.hasDangerousHtml || m2.hasSpreadAttrs || m2.isComponent) {
|
|
3767
|
+
return false;
|
|
3768
|
+
}
|
|
3769
|
+
if (m2.targetedByCombinator || m2.targetedByStructuralPseudo) return false;
|
|
3770
|
+
const fid = parentId;
|
|
3771
|
+
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
3772
|
+
return false;
|
|
3773
|
+
}
|
|
3774
|
+
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
3775
|
+
return true;
|
|
3776
|
+
}
|
|
3777
|
+
var redundantFragment = definePattern({
|
|
3778
|
+
name: "redundant-fragment",
|
|
3779
|
+
category: "flatten/fragment/redundant-fragment",
|
|
3780
|
+
safety: 1,
|
|
3781
|
+
doc: {
|
|
3782
|
+
title: "Flatten redundant single-child fragment",
|
|
3783
|
+
summary: "A fragment whose only child is a single node is removed; the child is spliced up into the fragment's slot, preserving its IRNodeId, siblings, attributes and the CSS cascade.",
|
|
3784
|
+
before: "<><Child/></>",
|
|
3785
|
+
after: "<Child/>",
|
|
3786
|
+
safetyRationale: "A fragment paints nothing and renders no box; with exactly one child its removal changes no sibling/structural-pseudo match-set. Keyed fragments and fragments carrying ref/handlers/dynamic-children/raw-html/spread are excluded as opacity barriers."
|
|
3787
|
+
},
|
|
3788
|
+
match: parentIsRedundantFragment,
|
|
3789
|
+
rewrite: (ctx, rw) => {
|
|
3790
|
+
const parentId = ctx.node.parent;
|
|
3791
|
+
if (parentId == null) return null;
|
|
3792
|
+
const fragment = ctx.doc.nodes.get(parentId);
|
|
3793
|
+
if (!fragment || fragment.kind !== "fragment") return null;
|
|
3794
|
+
return [rw.unwrap(fragment)];
|
|
3795
|
+
},
|
|
3796
|
+
test: {
|
|
3797
|
+
cases: [
|
|
3798
|
+
{
|
|
3799
|
+
// A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
|
|
3800
|
+
// → a provably-safe flatten: the child is spliced up into the fragment's slot.
|
|
3801
|
+
before: '<><span className="bg-red-200">Hi</span></>',
|
|
3802
|
+
after: '<span className="bg-red-200">Hi</span>'
|
|
3803
|
+
}
|
|
3804
|
+
],
|
|
3805
|
+
noMatch: [
|
|
3806
|
+
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
3807
|
+
'<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
3808
|
+
]
|
|
3809
|
+
}
|
|
3810
|
+
});
|
|
3811
|
+
|
|
3812
|
+
// ../patterns/src/library/grid/grid-center-wrapper.pattern.ts
|
|
3813
|
+
init_cjs_shims();
|
|
3814
|
+
var gridCenterWrapper = definePattern({
|
|
3815
|
+
name: "grid-center-wrapper",
|
|
3816
|
+
category: "flatten/grid/grid-center-wrapper",
|
|
3817
|
+
safety: 2,
|
|
3818
|
+
doc: {
|
|
3819
|
+
title: "Flatten grid-centering wrapper",
|
|
3820
|
+
summary: "A div that only centers a single child (display:grid; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
|
|
3821
|
+
before: '<div style="display:grid;align-items:center;justify-content:center"><Child/></div>',
|
|
3822
|
+
after: '<Child style="place-self:center"/>',
|
|
3823
|
+
safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic children, and is not a combinator subject; inheritable styles are folded onto the child before removal. The place-self:center collapse is committed by the gate only under a statically-known filling grid parent."
|
|
3824
|
+
},
|
|
3825
|
+
match: {
|
|
3826
|
+
tag: "div",
|
|
3827
|
+
style: { display: "grid", alignItems: "center", justifyContent: "center" },
|
|
3828
|
+
onlyChild: "element",
|
|
3829
|
+
paintsNothing: true
|
|
3830
|
+
},
|
|
3831
|
+
rewrite: {
|
|
3832
|
+
flattenInto: "child",
|
|
3833
|
+
childGains: { placeSelf: "center" }
|
|
3834
|
+
},
|
|
3835
|
+
// Like `flex-center-wrapper`, collapsing to `place-self:center` is render-identical ONLY when the
|
|
3836
|
+
// child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there both halves
|
|
3837
|
+
// of place-self take effect). Under that ONE context the flatten is `provably-safe` and commits; under
|
|
3838
|
+
// a flex/block/unknown parent — or when the wrapper drops any own style — it stays `needs-verification`
|
|
3839
|
+
// and the conservative production gate PRESERVES it. Op-level correctness is asserted by the invariant suite.
|
|
3840
|
+
test: {
|
|
3841
|
+
cases: [
|
|
3842
|
+
{
|
|
3843
|
+
name: "grid parent \u2192 flattened (child gains place-self-center)",
|
|
3844
|
+
before: '<div className="grid"><div className="grid items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
3845
|
+
after: '<div className="grid"><span className="bg-red-200 place-self-center">x</span></div>'
|
|
3846
|
+
}
|
|
3847
|
+
],
|
|
3848
|
+
noMatch: [
|
|
3849
|
+
// Non-grid (document-root) parent: justify-self is ignored outside a grid → not provably safe.
|
|
3850
|
+
'<div className="grid justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
3851
|
+
// Grid parent, but the wrapper drops padding when removed → not layout-neutral, preserved.
|
|
3852
|
+
'<div className="grid"><div className="p-4 grid items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
3853
|
+
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
3854
|
+
'<div className="grid justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
3855
|
+
]
|
|
3856
|
+
}
|
|
3857
|
+
});
|
|
3858
|
+
|
|
3859
|
+
// ../patterns/src/library/wrapper/display-contents-wrapper.pattern.ts
|
|
3534
3860
|
init_cjs_shims();
|
|
3535
3861
|
function asEl(node) {
|
|
3536
3862
|
const n = node;
|
|
@@ -3555,7 +3881,7 @@ var targetedByStructuralPseudo = (node, ctx) => {
|
|
|
3555
3881
|
};
|
|
3556
3882
|
var displayContentsWrapper = definePattern({
|
|
3557
3883
|
name: "display-contents-wrapper",
|
|
3558
|
-
category: "flatten/display-contents-wrapper",
|
|
3884
|
+
category: "flatten/wrapper/display-contents-wrapper",
|
|
3559
3885
|
safety: 2,
|
|
3560
3886
|
doc: {
|
|
3561
3887
|
title: "Flatten display:contents wrapper",
|
|
@@ -3595,7 +3921,7 @@ var displayContentsWrapper = definePattern({
|
|
|
3595
3921
|
}
|
|
3596
3922
|
});
|
|
3597
3923
|
|
|
3598
|
-
// ../patterns/src/library/
|
|
3924
|
+
// ../patterns/src/library/wrapper/empty-style-div.pattern.ts
|
|
3599
3925
|
init_cjs_shims();
|
|
3600
3926
|
function asEl2(node) {
|
|
3601
3927
|
const n = node;
|
|
@@ -3628,7 +3954,7 @@ var hasNonBlockDisplay = (node, ctx) => {
|
|
|
3628
3954
|
};
|
|
3629
3955
|
var emptyStyleDiv = definePattern({
|
|
3630
3956
|
name: "empty-style-div",
|
|
3631
|
-
category: "flatten/empty-style-div",
|
|
3957
|
+
category: "flatten/wrapper/empty-style-div",
|
|
3632
3958
|
safety: 1,
|
|
3633
3959
|
doc: {
|
|
3634
3960
|
title: "Flatten empty-style div wrapper",
|
|
@@ -3667,376 +3993,104 @@ var emptyStyleDiv = definePattern({
|
|
|
3667
3993
|
}
|
|
3668
3994
|
});
|
|
3669
3995
|
|
|
3670
|
-
// ../patterns/src/library/
|
|
3996
|
+
// ../patterns/src/library/wrapper/inherited-only-wrapper.pattern.ts
|
|
3671
3997
|
init_cjs_shims();
|
|
3672
|
-
var
|
|
3673
|
-
|
|
3674
|
-
|
|
3998
|
+
var INERT_HOST_TAGS = /* @__PURE__ */ new Set(["div", "span"]);
|
|
3999
|
+
var isInertHostTag = (node) => {
|
|
4000
|
+
const n = node;
|
|
4001
|
+
if (n.kind !== "element") return false;
|
|
4002
|
+
return INERT_HOST_TAGS.has(String(n.tag).toLowerCase());
|
|
4003
|
+
};
|
|
4004
|
+
var isComponentNode2 = (node) => {
|
|
4005
|
+
const n = node;
|
|
4006
|
+
return n.kind === "element" ? n.meta.isComponent : false;
|
|
4007
|
+
};
|
|
4008
|
+
var hasOnlyInheritedStyle = (node, ctx) => {
|
|
4009
|
+
const sm = normalizer.normalizeStyleMap(ctx.computed());
|
|
4010
|
+
let sawAny = false;
|
|
4011
|
+
for (const block of sm.blocks.values()) {
|
|
4012
|
+
for (const decl of block.decls.values()) {
|
|
4013
|
+
sawAny = true;
|
|
4014
|
+
const inherited = decl.inherited || normalizer.inherited.isInherited(decl.property);
|
|
4015
|
+
if (!inherited) return false;
|
|
4016
|
+
}
|
|
4017
|
+
}
|
|
4018
|
+
return sawAny;
|
|
4019
|
+
};
|
|
4020
|
+
var inheritedOnlyWrapper = definePattern({
|
|
4021
|
+
name: "inherited-only-wrapper",
|
|
4022
|
+
category: "flatten/wrapper/inherited-only-wrapper",
|
|
3675
4023
|
safety: 2,
|
|
3676
4024
|
doc: {
|
|
3677
|
-
title: "Flatten
|
|
3678
|
-
summary: "A
|
|
3679
|
-
before: '<div style="
|
|
3680
|
-
after: '<Child style="
|
|
3681
|
-
safetyRationale: "
|
|
4025
|
+
title: "Flatten inherited-only styling wrapper",
|
|
4026
|
+
summary: "A paint-free wrapper whose only own declarations are inherited properties (text-align, color, font-*, \u2026) is removed; its inherited style is folded onto the sole child, which keeps the same inherited values for the whole subtree.",
|
|
4027
|
+
before: '<div style="text-align:center"><Child/></div>',
|
|
4028
|
+
after: '<Child style="text-align:center"/>',
|
|
4029
|
+
safetyRationale: "Inherited properties reach descendants purely through inheritance, so folding them onto the child and removing the box is render-identical. The wrapper carries nothing non-inherited, establishes no box/formatting/stacking context, and is guarded by the auto-applied opacity-barrier + selector-safety set."
|
|
3682
4030
|
},
|
|
3683
4031
|
match: {
|
|
3684
|
-
tag: "div",
|
|
3685
|
-
style: { display: "flex", alignItems: "center", justifyContent: "center" },
|
|
3686
4032
|
onlyChild: "element",
|
|
3687
|
-
paintsNothing: true
|
|
3688
|
-
|
|
3689
|
-
rewrite: {
|
|
3690
|
-
flattenInto: "child",
|
|
3691
|
-
childGains: { placeSelf: "center" }
|
|
4033
|
+
paintsNothing: true,
|
|
4034
|
+
where: [isInertHostTag, not(isComponentNode2), hasOnlyInheritedStyle]
|
|
3692
4035
|
},
|
|
3693
|
-
|
|
3694
|
-
// when the child's NEW parent is a statically-known GRID that lets the wrapper fill its area (there
|
|
3695
|
-
// `place-self`'s align-self AND justify-self both take effect). Under that ONE context the flatten is
|
|
3696
|
-
// classified `provably-safe` and commits; under a flex/block/unknown parent — or when the wrapper
|
|
3697
|
-
// drops any own style — it stays `needs-verification` and the conservative production gate PRESERVES
|
|
3698
|
-
// it. Op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is additionally
|
|
3699
|
-
// asserted by the invariant suite over every pattern.
|
|
4036
|
+
rewrite: { flattenInto: "child" },
|
|
3700
4037
|
test: {
|
|
3701
4038
|
cases: [
|
|
3702
4039
|
{
|
|
3703
|
-
|
|
3704
|
-
before: '<div className="
|
|
3705
|
-
after: '<
|
|
4040
|
+
// `text-align:center` is inherited → folded onto the child; the paint-free wrapper is removed.
|
|
4041
|
+
before: '<div className="text-center"><p className="bg-red-200">x</p></div>',
|
|
4042
|
+
after: '<p className="bg-red-200 text-center">x</p>'
|
|
3706
4043
|
}
|
|
3707
4044
|
],
|
|
3708
4045
|
noMatch: [
|
|
3709
|
-
//
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
//
|
|
3714
|
-
'<
|
|
3715
|
-
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
3716
|
-
'<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
4046
|
+
// `p-4` is a NON-inherited padding: removing the box would drop it, so the flatten-safety gate
|
|
4047
|
+
// reverts the unwrap and the wrapper is left unchanged.
|
|
4048
|
+
'<div className="p-4"><p className="bg-red-200">x</p></div>',
|
|
4049
|
+
// A `<p>` wrapper is NOT an inert host box: its UA default display/margins are not captured in the
|
|
4050
|
+
// class-derived computed style, so removing it is not provably layout-neutral → left unchanged.
|
|
4051
|
+
'<p className="text-center"><span className="bg-red-200">x</span></p>'
|
|
3717
4052
|
]
|
|
3718
4053
|
}
|
|
3719
4054
|
});
|
|
3720
4055
|
|
|
3721
|
-
// ../patterns/src/library/
|
|
4056
|
+
// ../patterns/src/library/wrapper/passthrough-wrapper.pattern.ts
|
|
3722
4057
|
init_cjs_shims();
|
|
3723
|
-
|
|
3724
|
-
|
|
3725
|
-
|
|
4058
|
+
function metaOf2(node) {
|
|
4059
|
+
const n = node;
|
|
4060
|
+
return n.kind === "element" ? n.meta : null;
|
|
4061
|
+
}
|
|
4062
|
+
function elementOf(node) {
|
|
4063
|
+
const n = node;
|
|
4064
|
+
return n.kind === "element" ? n : null;
|
|
4065
|
+
}
|
|
4066
|
+
var establishesContext = (node) => {
|
|
4067
|
+
const m2 = metaOf2(node);
|
|
4068
|
+
if (!m2) return false;
|
|
4069
|
+
return m2.establishesBox || m2.establishesFormattingContext || m2.establishesStackingContext || m2.isContainingBlock || m2.declaresCustomProperties;
|
|
4070
|
+
};
|
|
4071
|
+
var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
|
|
4072
|
+
var isComponentNode3 = (node) => metaOf2(node)?.isComponent ?? false;
|
|
4073
|
+
var hasOwnAttrs2 = (node) => {
|
|
4074
|
+
const el = elementOf(node);
|
|
4075
|
+
if (!el) return false;
|
|
4076
|
+
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
4077
|
+
};
|
|
4078
|
+
var targetedByStructuralPseudo3 = (node, ctx) => {
|
|
4079
|
+
const el = elementOf(node);
|
|
4080
|
+
if (!el) return false;
|
|
4081
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
4082
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
4083
|
+
};
|
|
4084
|
+
var passthroughWrapper = definePattern({
|
|
4085
|
+
name: "passthrough-wrapper",
|
|
4086
|
+
category: "flatten/wrapper/passthrough-wrapper",
|
|
3726
4087
|
safety: 2,
|
|
3727
4088
|
doc: {
|
|
3728
|
-
title: "Flatten
|
|
3729
|
-
summary: "A div
|
|
3730
|
-
before:
|
|
3731
|
-
after:
|
|
3732
|
-
safetyRationale: "Wrapper paints nothing, carries no ref/handlers/dynamic
|
|
3733
|
-
},
|
|
3734
|
-
match: {
|
|
3735
|
-
tag: "div",
|
|
3736
|
-
style: { display: "inline-flex", alignItems: "center", justifyContent: "center" },
|
|
3737
|
-
onlyChild: "element",
|
|
3738
|
-
paintsNothing: true
|
|
3739
|
-
},
|
|
3740
|
-
rewrite: {
|
|
3741
|
-
flattenInto: "child",
|
|
3742
|
-
childGains: { placeSelf: "center" }
|
|
3743
|
-
},
|
|
3744
|
-
// Like its block-level sibling, this centering flatten is `needs-verification` (the wrapper's own
|
|
3745
|
-
// `display:inline-flex` establishes a formatting context, and place-self centering only holds under
|
|
3746
|
-
// a flex/grid parent), so the conservative production gate (`'provably-safe'`) REVERTS it — every
|
|
3747
|
-
// case here is a no-match. Op-level correctness is covered by the invariant suite.
|
|
3748
|
-
test: {
|
|
3749
|
-
noMatch: [
|
|
3750
|
-
// Even under a static flex/grid parent the centering flatten is not provably layout-neutral.
|
|
3751
|
-
'<div className="grid"><div className="inline-flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
|
|
3752
|
-
// Non-flex/grid parent (document root) → left unchanged.
|
|
3753
|
-
'<div className="inline-flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
|
|
3754
|
-
// onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
|
|
3755
|
-
'<div className="inline-flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
|
|
3756
|
-
]
|
|
3757
|
-
}
|
|
3758
|
-
});
|
|
3759
|
-
|
|
3760
|
-
// ../patterns/src/library/flatten/nested-flex-merge.pattern.ts
|
|
3761
|
-
init_cjs_shims();
|
|
3762
|
-
function baseConditionStyleMap(decls) {
|
|
3763
|
-
const map = /* @__PURE__ */ new Map();
|
|
3764
|
-
for (const [prop, value] of decls) {
|
|
3765
|
-
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
3766
|
-
map.set(decl.property, decl);
|
|
3767
|
-
}
|
|
3768
|
-
}
|
|
3769
|
-
const block = { condition: BASE_CONDITION, decls: map };
|
|
3770
|
-
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
3771
|
-
return { blocks };
|
|
3772
|
-
}
|
|
3773
|
-
var DISPLAY_FLEX = baseConditionStyleMap([["display", "flex"]]);
|
|
3774
|
-
var FLEX_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
3775
|
-
"display",
|
|
3776
|
-
"flex-direction",
|
|
3777
|
-
"flex-wrap",
|
|
3778
|
-
"justify-content",
|
|
3779
|
-
"align-items",
|
|
3780
|
-
"align-content",
|
|
3781
|
-
"place-content",
|
|
3782
|
-
"place-items",
|
|
3783
|
-
"row-gap",
|
|
3784
|
-
"column-gap"
|
|
3785
|
-
]);
|
|
3786
|
-
function outerMergeSafe(sm) {
|
|
3787
|
-
const norm = normalizer.normalizeStyleMap(sm);
|
|
3788
|
-
for (const block of norm.blocks.values()) {
|
|
3789
|
-
for (const decl of block.decls.values()) {
|
|
3790
|
-
if (FLEX_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
3791
|
-
if (decl.inherited) continue;
|
|
3792
|
-
return false;
|
|
3793
|
-
}
|
|
3794
|
-
}
|
|
3795
|
-
return true;
|
|
3796
|
-
}
|
|
3797
|
-
function flexConflict(outer, inner) {
|
|
3798
|
-
const a = normalizer.normalizeStyleMap(outer);
|
|
3799
|
-
const b3 = normalizer.normalizeStyleMap(inner);
|
|
3800
|
-
for (const [key, blockA] of a.blocks) {
|
|
3801
|
-
const blockB = b3.blocks.get(key);
|
|
3802
|
-
if (!blockB) continue;
|
|
3803
|
-
for (const [prop, declA] of blockA.decls) {
|
|
3804
|
-
if (!FLEX_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
3805
|
-
const declB = blockB.decls.get(prop);
|
|
3806
|
-
if (declB && declB.value !== declA.value) return true;
|
|
3807
|
-
}
|
|
3808
|
-
}
|
|
3809
|
-
return false;
|
|
3810
|
-
}
|
|
3811
|
-
function extractFlexStyle(sm) {
|
|
3812
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3813
|
-
for (const [key, block] of sm.blocks) {
|
|
3814
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3815
|
-
for (const [prop, decl] of block.decls) {
|
|
3816
|
-
if (FLEX_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
3817
|
-
}
|
|
3818
|
-
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
3819
|
-
}
|
|
3820
|
-
return { blocks };
|
|
3821
|
-
}
|
|
3822
|
-
var isInnerFlex = and(
|
|
3823
|
-
isElement("div"),
|
|
3824
|
-
computed(DISPLAY_FLEX),
|
|
3825
|
-
not(targetedByCombinator)
|
|
3826
|
-
);
|
|
3827
|
-
var nestedFlexMerge = definePattern({
|
|
3828
|
-
name: "nested-flex-merge",
|
|
3829
|
-
category: "flatten/nested-flex-merge",
|
|
3830
|
-
safety: 2,
|
|
3831
|
-
doc: {
|
|
3832
|
-
title: "Merge nested flex containers",
|
|
3833
|
-
summary: "A flex container whose only child is itself a flex container with non-conflicting flex properties is collapsed into one; the wrapper is removed and its flex declarations merge onto the surviving child.",
|
|
3834
|
-
before: '<div style="display:flex;align-items:center;gap:8px"><div style="display:flex;flex-direction:column"/></div>',
|
|
3835
|
-
after: '<div style="display:flex;flex-direction:column;align-items:center;gap:8px"/>',
|
|
3836
|
-
safetyRationale: "The wrapper paints nothing, declares only flex-container/inheritable properties, carries no ref/handlers/dynamic children, and is not a combinator subject; the two containers do not conflict on any flex property, so the union is unambiguous and lossless."
|
|
3837
|
-
},
|
|
3838
|
-
match: {
|
|
3839
|
-
tag: "div",
|
|
3840
|
-
style: { display: "flex" },
|
|
3841
|
-
onlyChild: "element",
|
|
3842
|
-
paintsNothing: true
|
|
3843
|
-
},
|
|
3844
|
-
rewrite: (ctx, rw) => {
|
|
3845
|
-
const outer = ctx.node;
|
|
3846
|
-
const inner = ctx.onlyElementChild();
|
|
3847
|
-
if (!inner) return null;
|
|
3848
|
-
if (!isInnerFlex(inner, ctx)) return null;
|
|
3849
|
-
const outerStyle = ctx.computed();
|
|
3850
|
-
const innerStyle = ctx.computedOf(inner);
|
|
3851
|
-
if (!outerMergeSafe(outerStyle)) return null;
|
|
3852
|
-
if (flexConflict(outerStyle, innerStyle)) return null;
|
|
3853
|
-
return [
|
|
3854
|
-
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
3855
|
-
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
3856
|
-
// 2. Transfer the wrapper's flex-container declarations onto the child (target-wins keeps the
|
|
3857
|
-
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
3858
|
-
rw.mergeStyle(inner, null, extractFlexStyle(outerStyle), "target-wins"),
|
|
3859
|
-
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
3860
|
-
rw.unwrap(outer)
|
|
3861
|
-
];
|
|
3862
|
-
},
|
|
3863
|
-
// Merging the outer flex container into the inner removes the outer's box, but a `display:flex`
|
|
3864
|
-
// wrapper establishes a formatting context, so this is a `needs-verification` flatten that the
|
|
3865
|
-
// conservative production gate (`'provably-safe'`) REVERTS — every case here is a no-match. The
|
|
3866
|
-
// merge's op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is asserted
|
|
3867
|
-
// by the invariant suite over every pattern.
|
|
3868
|
-
test: {
|
|
3869
|
-
noMatch: [
|
|
3870
|
-
// The merge is real but not provably layout-neutral (the wrapper establishes a flex context),
|
|
3871
|
-
// so under the conservative gate the nested containers are left in place.
|
|
3872
|
-
'<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
|
|
3873
|
-
// A non-flex wrapper does not match the flex-container signature → left unchanged anyway.
|
|
3874
|
-
'<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
|
|
3875
|
-
]
|
|
3876
|
-
}
|
|
3877
|
-
});
|
|
3878
|
-
|
|
3879
|
-
// ../patterns/src/library/flatten/nested-grid-merge.pattern.ts
|
|
3880
|
-
init_cjs_shims();
|
|
3881
|
-
function baseConditionStyleMap2(decls) {
|
|
3882
|
-
const map = /* @__PURE__ */ new Map();
|
|
3883
|
-
for (const [prop, value] of decls) {
|
|
3884
|
-
for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
|
|
3885
|
-
map.set(decl.property, decl);
|
|
3886
|
-
}
|
|
3887
|
-
}
|
|
3888
|
-
const block = { condition: BASE_CONDITION, decls: map };
|
|
3889
|
-
const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
|
|
3890
|
-
return { blocks };
|
|
3891
|
-
}
|
|
3892
|
-
var DISPLAY_GRID = baseConditionStyleMap2([["display", "grid"]]);
|
|
3893
|
-
var GRID_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
|
|
3894
|
-
"display",
|
|
3895
|
-
"grid-template-columns",
|
|
3896
|
-
"grid-template-rows",
|
|
3897
|
-
"grid-template-areas",
|
|
3898
|
-
"grid-auto-columns",
|
|
3899
|
-
"grid-auto-rows",
|
|
3900
|
-
"grid-auto-flow",
|
|
3901
|
-
"justify-content",
|
|
3902
|
-
"align-content",
|
|
3903
|
-
"place-content",
|
|
3904
|
-
"justify-items",
|
|
3905
|
-
"align-items",
|
|
3906
|
-
"place-items",
|
|
3907
|
-
"row-gap",
|
|
3908
|
-
"column-gap"
|
|
3909
|
-
]);
|
|
3910
|
-
function outerMergeSafe2(sm) {
|
|
3911
|
-
const norm = normalizer.normalizeStyleMap(sm);
|
|
3912
|
-
for (const block of norm.blocks.values()) {
|
|
3913
|
-
for (const decl of block.decls.values()) {
|
|
3914
|
-
if (GRID_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
|
|
3915
|
-
if (decl.inherited) continue;
|
|
3916
|
-
return false;
|
|
3917
|
-
}
|
|
3918
|
-
}
|
|
3919
|
-
return true;
|
|
3920
|
-
}
|
|
3921
|
-
function gridConflict(outer, inner) {
|
|
3922
|
-
const a = normalizer.normalizeStyleMap(outer);
|
|
3923
|
-
const b3 = normalizer.normalizeStyleMap(inner);
|
|
3924
|
-
for (const [key, blockA] of a.blocks) {
|
|
3925
|
-
const blockB = b3.blocks.get(key);
|
|
3926
|
-
if (!blockB) continue;
|
|
3927
|
-
for (const [prop, declA] of blockA.decls) {
|
|
3928
|
-
if (!GRID_CONTAINER_PROPERTIES.has(String(prop))) continue;
|
|
3929
|
-
const declB = blockB.decls.get(prop);
|
|
3930
|
-
if (declB && declB.value !== declA.value) return true;
|
|
3931
|
-
}
|
|
3932
|
-
}
|
|
3933
|
-
return false;
|
|
3934
|
-
}
|
|
3935
|
-
function extractGridStyle(sm) {
|
|
3936
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
3937
|
-
for (const [key, block] of sm.blocks) {
|
|
3938
|
-
const decls = /* @__PURE__ */ new Map();
|
|
3939
|
-
for (const [prop, decl] of block.decls) {
|
|
3940
|
-
if (GRID_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
|
|
3941
|
-
}
|
|
3942
|
-
if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
|
|
3943
|
-
}
|
|
3944
|
-
return { blocks };
|
|
3945
|
-
}
|
|
3946
|
-
var isInnerGrid = and(
|
|
3947
|
-
isElement("div"),
|
|
3948
|
-
computed(DISPLAY_GRID),
|
|
3949
|
-
not(targetedByCombinator)
|
|
3950
|
-
);
|
|
3951
|
-
var nestedGridMerge = definePattern({
|
|
3952
|
-
name: "nested-grid-merge",
|
|
3953
|
-
category: "flatten/nested-grid-merge",
|
|
3954
|
-
safety: 2,
|
|
3955
|
-
doc: {
|
|
3956
|
-
title: "Merge nested grid containers",
|
|
3957
|
-
summary: "A grid container whose only child is itself a grid container with non-conflicting grid properties is collapsed into one; the wrapper is removed and its grid declarations merge onto the surviving child.",
|
|
3958
|
-
before: '<div style="display:grid;gap:8px"><div style="display:grid;grid-template-columns:1fr 1fr"/></div>',
|
|
3959
|
-
after: '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"/>',
|
|
3960
|
-
safetyRationale: "The wrapper paints nothing, declares only grid-container/inheritable properties, carries no ref/handlers/dynamic children, and is not a combinator subject; the two containers do not conflict on any grid property, so the union is unambiguous and lossless."
|
|
3961
|
-
},
|
|
3962
|
-
match: {
|
|
3963
|
-
tag: "div",
|
|
3964
|
-
style: { display: "grid" },
|
|
3965
|
-
onlyChild: "element",
|
|
3966
|
-
paintsNothing: true
|
|
3967
|
-
},
|
|
3968
|
-
rewrite: (ctx, rw) => {
|
|
3969
|
-
const outer = ctx.node;
|
|
3970
|
-
const inner = ctx.onlyElementChild();
|
|
3971
|
-
if (!inner) return null;
|
|
3972
|
-
if (!isInnerGrid(inner, ctx)) return null;
|
|
3973
|
-
const outerStyle = ctx.computed();
|
|
3974
|
-
const innerStyle = ctx.computedOf(inner);
|
|
3975
|
-
if (!outerMergeSafe2(outerStyle)) return null;
|
|
3976
|
-
if (gridConflict(outerStyle, innerStyle)) return null;
|
|
3977
|
-
return [
|
|
3978
|
-
// 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
|
|
3979
|
-
rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
|
|
3980
|
-
// 2. Transfer the wrapper's grid-container declarations onto the child (target-wins keeps the
|
|
3981
|
-
// child's value for any shared property — identical anyway, we proved non-conflict).
|
|
3982
|
-
rw.mergeStyle(inner, null, extractGridStyle(outerStyle), "target-wins"),
|
|
3983
|
-
// 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
|
|
3984
|
-
rw.unwrap(outer)
|
|
3985
|
-
];
|
|
3986
|
-
},
|
|
3987
|
-
// Like its flex sibling, this merge removes the outer container's box, but a `display:grid` wrapper
|
|
3988
|
-
// establishes a formatting context, so it is a `needs-verification` flatten that the conservative
|
|
3989
|
-
// production gate (`'provably-safe'`) REVERTS — every case here is a no-match. Op-level correctness
|
|
3990
|
-
// is asserted by the invariant suite over every pattern.
|
|
3991
|
-
test: {
|
|
3992
|
-
noMatch: [
|
|
3993
|
-
// The merge is real but not provably layout-neutral (the wrapper establishes a grid context),
|
|
3994
|
-
// so under the conservative gate the nested containers are left in place.
|
|
3995
|
-
'<div className="grid gap-2" data-x="1"><div className="grid grid-cols-2">X</div></div>',
|
|
3996
|
-
// A non-grid wrapper does not match the grid-container signature → left unchanged anyway.
|
|
3997
|
-
'<div className="block bg-blue-500"><div className="grid grid-cols-2">X</div></div>'
|
|
3998
|
-
]
|
|
3999
|
-
}
|
|
4000
|
-
});
|
|
4001
|
-
|
|
4002
|
-
// ../patterns/src/library/flatten/passthrough-wrapper.pattern.ts
|
|
4003
|
-
init_cjs_shims();
|
|
4004
|
-
function metaOf2(node) {
|
|
4005
|
-
const n = node;
|
|
4006
|
-
return n.kind === "element" ? n.meta : null;
|
|
4007
|
-
}
|
|
4008
|
-
function elementOf(node) {
|
|
4009
|
-
const n = node;
|
|
4010
|
-
return n.kind === "element" ? n : null;
|
|
4011
|
-
}
|
|
4012
|
-
var establishesContext = (node) => {
|
|
4013
|
-
const m2 = metaOf2(node);
|
|
4014
|
-
if (!m2) return false;
|
|
4015
|
-
return m2.establishesBox || m2.establishesFormattingContext || m2.establishesStackingContext || m2.isContainingBlock || m2.declaresCustomProperties;
|
|
4016
|
-
};
|
|
4017
|
-
var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
|
|
4018
|
-
var isComponentNode2 = (node) => metaOf2(node)?.isComponent ?? false;
|
|
4019
|
-
var hasOwnAttrs2 = (node) => {
|
|
4020
|
-
const el = elementOf(node);
|
|
4021
|
-
if (!el) return false;
|
|
4022
|
-
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
4023
|
-
};
|
|
4024
|
-
var targetedByStructuralPseudo3 = (node, ctx) => {
|
|
4025
|
-
const el = elementOf(node);
|
|
4026
|
-
if (!el) return false;
|
|
4027
|
-
if (el.meta.targetedByStructuralPseudo) return true;
|
|
4028
|
-
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
4029
|
-
};
|
|
4030
|
-
var passthroughWrapper = definePattern({
|
|
4031
|
-
name: "passthrough-wrapper",
|
|
4032
|
-
category: "flatten/passthrough-wrapper",
|
|
4033
|
-
safety: 2,
|
|
4034
|
-
doc: {
|
|
4035
|
-
title: "Flatten passthrough wrapper",
|
|
4036
|
-
summary: "A div with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
4037
|
-
before: "<div><Child/></div>",
|
|
4038
|
-
after: "<Child/>",
|
|
4039
|
-
safetyRationale: "Wrapper paints nothing and establishes no layout/paint/var context, carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject (reparenting changes no match-set); inheritable styles are folded onto the child before removal."
|
|
4089
|
+
title: "Flatten passthrough wrapper",
|
|
4090
|
+
summary: "A div with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
4091
|
+
before: "<div><Child/></div>",
|
|
4092
|
+
after: "<Child/>",
|
|
4093
|
+
safetyRationale: "Wrapper paints nothing and establishes no layout/paint/var context, carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject (reparenting changes no match-set); inheritable styles are folded onto the child before removal."
|
|
4040
4094
|
},
|
|
4041
4095
|
match: {
|
|
4042
4096
|
tag: "div",
|
|
@@ -4047,1244 +4101,124 @@ var passthroughWrapper = definePattern({
|
|
|
4047
4101
|
not(hasOwnAttrs2),
|
|
4048
4102
|
not(hasDynamicClasses),
|
|
4049
4103
|
not(hasSpreadAttrs2),
|
|
4050
|
-
not(isComponentNode2),
|
|
4051
|
-
not(targetedByStructuralPseudo3)
|
|
4052
|
-
]
|
|
4053
|
-
},
|
|
4054
|
-
rewrite: { flattenInto: "child" },
|
|
4055
|
-
test: {
|
|
4056
|
-
cases: [
|
|
4057
|
-
{
|
|
4058
|
-
// A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
|
|
4059
|
-
// flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
|
|
4060
|
-
before: '<div><a className="bg-red-200">Link</a></div>',
|
|
4061
|
-
after: '<a className="bg-red-200">Link</a>'
|
|
4062
|
-
}
|
|
4063
|
-
],
|
|
4064
|
-
noMatch: [
|
|
4065
|
-
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
4066
|
-
'<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
|
|
4067
|
-
// A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
|
|
4068
|
-
// provably layout-neutral → the conservative gate leaves it in place.
|
|
4069
|
-
'<div className="flex"><a className="bg-red-200">Link</a></div>'
|
|
4070
|
-
]
|
|
4071
|
-
}
|
|
4072
|
-
});
|
|
4073
|
-
|
|
4074
|
-
// ../patterns/src/library/flatten/redundant-fragment.pattern.ts
|
|
4075
|
-
init_cjs_shims();
|
|
4076
|
-
function parentIsRedundantFragment(node, ctx) {
|
|
4077
|
-
const el = node;
|
|
4078
|
-
if (el.kind !== "element") return false;
|
|
4079
|
-
const parentId = el.parent;
|
|
4080
|
-
if (parentId == null) return false;
|
|
4081
|
-
const parent = ctx.doc.nodes.get(parentId);
|
|
4082
|
-
if (!parent || parent.kind !== "fragment") return false;
|
|
4083
|
-
if (parent.parent == null) return false;
|
|
4084
|
-
if (parent.children.length !== 1) return false;
|
|
4085
|
-
const m2 = parent.meta;
|
|
4086
|
-
if (m2.hasKey || m2.hasRef || m2.hasEventHandlers || m2.hasDynamicChildren || m2.hasDangerousHtml || m2.hasSpreadAttrs || m2.isComponent) {
|
|
4087
|
-
return false;
|
|
4088
|
-
}
|
|
4089
|
-
if (m2.targetedByCombinator || m2.targetedByStructuralPseudo) return false;
|
|
4090
|
-
const fid = parentId;
|
|
4091
|
-
if (ctx.selectors.targetedByCombinator(fid) || ctx.selectors.targetedByStructuralPseudo(fid)) {
|
|
4092
|
-
return false;
|
|
4093
|
-
}
|
|
4094
|
-
if (ctx.selectors.reparentImpact(fid).size > 0) return false;
|
|
4095
|
-
return true;
|
|
4096
|
-
}
|
|
4097
|
-
var redundantFragment = definePattern({
|
|
4098
|
-
name: "redundant-fragment",
|
|
4099
|
-
category: "flatten/redundant-fragment",
|
|
4100
|
-
safety: 1,
|
|
4101
|
-
doc: {
|
|
4102
|
-
title: "Flatten redundant single-child fragment",
|
|
4103
|
-
summary: "A fragment whose only child is a single node is removed; the child is spliced up into the fragment's slot, preserving its IRNodeId, siblings, attributes and the CSS cascade.",
|
|
4104
|
-
before: "<><Child/></>",
|
|
4105
|
-
after: "<Child/>",
|
|
4106
|
-
safetyRationale: "A fragment paints nothing and renders no box; with exactly one child its removal changes no sibling/structural-pseudo match-set. Keyed fragments and fragments carrying ref/handlers/dynamic-children/raw-html/spread are excluded as opacity barriers."
|
|
4107
|
-
},
|
|
4108
|
-
match: parentIsRedundantFragment,
|
|
4109
|
-
rewrite: (ctx, rw) => {
|
|
4110
|
-
const parentId = ctx.node.parent;
|
|
4111
|
-
if (parentId == null) return null;
|
|
4112
|
-
const fragment = ctx.doc.nodes.get(parentId);
|
|
4113
|
-
if (!fragment || fragment.kind !== "fragment") return null;
|
|
4114
|
-
return [rw.unwrap(fragment)];
|
|
4115
|
-
},
|
|
4116
|
-
test: {
|
|
4117
|
-
cases: [
|
|
4118
|
-
{
|
|
4119
|
-
// A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
|
|
4120
|
-
// → a provably-safe flatten: the child is spliced up into the fragment's slot.
|
|
4121
|
-
before: '<><span className="bg-red-200">Hi</span></>',
|
|
4122
|
-
after: '<span className="bg-red-200">Hi</span>'
|
|
4123
|
-
}
|
|
4124
|
-
],
|
|
4125
|
-
noMatch: [
|
|
4126
|
-
// Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
|
|
4127
|
-
'<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
|
|
4128
|
-
]
|
|
4129
|
-
}
|
|
4130
|
-
});
|
|
4131
|
-
|
|
4132
|
-
// ../patterns/src/library/flatten/redundant-inline-wrapper.pattern.ts
|
|
4133
|
-
init_cjs_shims();
|
|
4134
|
-
function asEl3(node) {
|
|
4135
|
-
const n = node;
|
|
4136
|
-
return n.kind === "element" ? n : null;
|
|
4137
|
-
}
|
|
4138
|
-
function metaOf3(node) {
|
|
4139
|
-
return asEl3(node)?.meta ?? null;
|
|
4140
|
-
}
|
|
4141
|
-
var establishesContext2 = (node) => {
|
|
4142
|
-
const m2 = metaOf3(node);
|
|
4143
|
-
if (!m2) return false;
|
|
4144
|
-
return m2.establishesBox || m2.establishesFormattingContext || m2.establishesStackingContext || m2.isContainingBlock || m2.declaresCustomProperties;
|
|
4145
|
-
};
|
|
4146
|
-
var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
|
|
4147
|
-
var isComponentNode3 = (node) => metaOf3(node)?.isComponent ?? false;
|
|
4148
|
-
var hasOwnAttrs3 = (node) => {
|
|
4149
|
-
const el = asEl3(node);
|
|
4150
|
-
if (!el) return false;
|
|
4151
|
-
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
4152
|
-
};
|
|
4153
|
-
var targetedByStructuralPseudo4 = (node, ctx) => {
|
|
4154
|
-
const el = asEl3(node);
|
|
4155
|
-
if (!el) return false;
|
|
4156
|
-
if (el.meta.targetedByStructuralPseudo) return true;
|
|
4157
|
-
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
4158
|
-
};
|
|
4159
|
-
var DISPLAY3 = "display";
|
|
4160
|
-
var hasNonInlineDisplay = (node, ctx) => {
|
|
4161
|
-
const el = asEl3(node);
|
|
4162
|
-
if (!el) return false;
|
|
4163
|
-
const sm = ctx.computedOf(el) ?? el.computed;
|
|
4164
|
-
for (const block of sm.blocks.values()) {
|
|
4165
|
-
const decl = block.decls.get(DISPLAY3);
|
|
4166
|
-
if (decl && String(decl.value) !== "inline") return true;
|
|
4167
|
-
}
|
|
4168
|
-
return false;
|
|
4169
|
-
};
|
|
4170
|
-
var redundantInlineWrapper = definePattern({
|
|
4171
|
-
name: "redundant-inline-wrapper",
|
|
4172
|
-
category: "flatten/redundant-inline-wrapper",
|
|
4173
|
-
safety: 2,
|
|
4174
|
-
doc: {
|
|
4175
|
-
title: "Flatten redundant inline wrapper",
|
|
4176
|
-
summary: "An inline span with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
4177
|
-
before: "<span><Child/></span>",
|
|
4178
|
-
after: "<Child/>",
|
|
4179
|
-
safetyRationale: "An empty inline box paints nothing and establishes no layout/paint/var context; with the inline default display and a single element child its removal changes no paint and no flow. The span carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
|
|
4180
|
-
},
|
|
4181
|
-
match: {
|
|
4182
|
-
tag: "span",
|
|
4183
|
-
onlyChild: "element",
|
|
4184
|
-
paintsNothing: true,
|
|
4185
|
-
where: [
|
|
4186
|
-
not(hasNonInlineDisplay),
|
|
4187
|
-
not(establishesContext2),
|
|
4188
|
-
not(hasOwnAttrs3),
|
|
4189
|
-
not(hasDynamicClasses),
|
|
4190
|
-
not(hasSpreadAttrs3),
|
|
4191
4104
|
not(isComponentNode3),
|
|
4192
|
-
not(
|
|
4193
|
-
]
|
|
4194
|
-
},
|
|
4195
|
-
rewrite: { flattenInto: "child" },
|
|
4196
|
-
test: {
|
|
4197
|
-
cases: [
|
|
4198
|
-
{
|
|
4199
|
-
// An empty inline span paints nothing and establishes no context → a provably-safe flatten:
|
|
4200
|
-
// the span is removed and its sole child hoisted in place.
|
|
4201
|
-
before: '<span><a className="text-blue-500">Link</a></span>',
|
|
4202
|
-
after: '<a className="text-blue-500">Link</a>'
|
|
4203
|
-
}
|
|
4204
|
-
],
|
|
4205
|
-
noMatch: [
|
|
4206
|
-
// A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
|
|
4207
|
-
'<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
|
|
4208
|
-
// The span paints its own background (own visual style) → kept.
|
|
4209
|
-
'<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
|
|
4210
|
-
// Non-inline display (inline-block) participates in layout differently → kept.
|
|
4211
|
-
'<span className="inline-block"><a className="text-blue-500">Link</a></span>'
|
|
4212
|
-
]
|
|
4213
|
-
}
|
|
4214
|
-
});
|
|
4215
|
-
|
|
4216
|
-
// ../patterns/src/library/compress/border-radius-shorthand.pattern.ts
|
|
4217
|
-
init_cjs_shims();
|
|
4218
|
-
var CORNERS = [
|
|
4219
|
-
"border-top-left-radius",
|
|
4220
|
-
"border-top-right-radius",
|
|
4221
|
-
"border-bottom-right-radius",
|
|
4222
|
-
"border-bottom-left-radius"
|
|
4223
|
-
];
|
|
4224
|
-
var CORNER_SET = new Set(CORNERS);
|
|
4225
|
-
var BASE_KEY = conditionKey(BASE_CONDITION);
|
|
4226
|
-
var RADIUS = "border-radius";
|
|
4227
|
-
var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set([
|
|
4228
|
-
"initial",
|
|
4229
|
-
"inherit",
|
|
4230
|
-
"unset",
|
|
4231
|
-
"revert",
|
|
4232
|
-
"revert-layer"
|
|
4233
|
-
]);
|
|
4234
|
-
function analyzeRadius(sm) {
|
|
4235
|
-
const block = sm.blocks.get(BASE_KEY);
|
|
4236
|
-
if (!block) return null;
|
|
4237
|
-
const corners = [];
|
|
4238
|
-
for (const corner of CORNERS) {
|
|
4239
|
-
const decl = block.decls.get(corner);
|
|
4240
|
-
if (!decl) return null;
|
|
4241
|
-
corners.push(decl);
|
|
4242
|
-
}
|
|
4243
|
-
const important = corners[0].important;
|
|
4244
|
-
if (!corners.every((d3) => d3.important === important)) return null;
|
|
4245
|
-
const value = String(corners[0].value);
|
|
4246
|
-
if (NON_COLLAPSIBLE_VALUES.has(value)) return null;
|
|
4247
|
-
if (!corners.every((d3) => String(d3.value) === value)) return null;
|
|
4248
|
-
const relative4 = corners.some((d3) => d3.relativeToParent);
|
|
4249
|
-
return { value, important, relative: relative4 };
|
|
4250
|
-
}
|
|
4251
|
-
function withFoldedRadius(sm, fold) {
|
|
4252
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4253
|
-
for (const [key, block] of sm.blocks) {
|
|
4254
|
-
if (key !== BASE_KEY) {
|
|
4255
|
-
blocks.set(key, block);
|
|
4256
|
-
continue;
|
|
4257
|
-
}
|
|
4258
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4259
|
-
for (const [prop, decl] of block.decls) {
|
|
4260
|
-
if (CORNER_SET.has(String(prop))) continue;
|
|
4261
|
-
decls.set(prop, decl);
|
|
4262
|
-
}
|
|
4263
|
-
const shorthand = {
|
|
4264
|
-
property: RADIUS,
|
|
4265
|
-
value: fold.value,
|
|
4266
|
-
important: fold.important,
|
|
4267
|
-
relativeToParent: fold.relative,
|
|
4268
|
-
inherited: false
|
|
4269
|
-
// border-radius is never inherited
|
|
4270
|
-
};
|
|
4271
|
-
decls.set(shorthand.property, shorthand);
|
|
4272
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4273
|
-
}
|
|
4274
|
-
return { blocks };
|
|
4275
|
-
}
|
|
4276
|
-
var borderRadiusShorthand = definePattern({
|
|
4277
|
-
name: "border-radius-shorthand",
|
|
4278
|
-
category: "compress/border-radius-shorthand",
|
|
4279
|
-
safety: 1,
|
|
4280
|
-
doc: {
|
|
4281
|
-
title: "Collapse equal corner radii into border-radius",
|
|
4282
|
-
summary: "An element whose four corner radii (border-*-radius longhands) are all equal is rewritten to the single Tailwind rounded-* utility (border-radius === the four equal corners).",
|
|
4283
|
-
before: '<div class="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg"/>',
|
|
4284
|
-
after: '<div class="rounded-lg"/>',
|
|
4285
|
-
safetyRationale: "`border-radius` is value-identical to four equal corner radii \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4286
|
-
},
|
|
4287
|
-
rewrite: {
|
|
4288
|
-
rewriteClasses(computed2) {
|
|
4289
|
-
const fold = analyzeRadius(computed2);
|
|
4290
|
-
return fold ? withFoldedRadius(computed2, fold) : null;
|
|
4291
|
-
}
|
|
4292
|
-
},
|
|
4293
|
-
test: {
|
|
4294
|
-
cases: [
|
|
4295
|
-
{
|
|
4296
|
-
// The four equal corner longhands collapse to a `border-radius` decl at the IR level; the
|
|
4297
|
-
// minimizing reverse-emit then picks the single shortest utility (`rounded-lg`) that reproduces
|
|
4298
|
-
// it, replacing the four `rounded-{tl,tr,br,bl}-lg` tokens. `bg-red-200` is preserved.
|
|
4299
|
-
before: '<div className="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg bg-red-200">box</div>',
|
|
4300
|
-
after: '<div className="bg-red-200 rounded-lg">box</div>'
|
|
4301
|
-
}
|
|
4302
|
-
],
|
|
4303
|
-
// Corners differ (top corners vs bottom corners) → no all-equal collapse.
|
|
4304
|
-
noMatch: ['<div className="rounded-t-lg rounded-b-sm bg-red-200">box</div>']
|
|
4305
|
-
}
|
|
4306
|
-
});
|
|
4307
|
-
|
|
4308
|
-
// ../patterns/src/library/compress/border-shorthand.pattern.ts
|
|
4309
|
-
init_cjs_shims();
|
|
4310
|
-
var WIDTH_SIDES = [
|
|
4311
|
-
"border-top-width",
|
|
4312
|
-
"border-right-width",
|
|
4313
|
-
"border-bottom-width",
|
|
4314
|
-
"border-left-width"
|
|
4315
|
-
];
|
|
4316
|
-
var WIDTH_SIDE_SET = new Set(WIDTH_SIDES);
|
|
4317
|
-
var BASE_KEY2 = conditionKey(BASE_CONDITION);
|
|
4318
|
-
var BORDER_WIDTH = "border-width";
|
|
4319
|
-
function analyzeWidth(sm) {
|
|
4320
|
-
const block = sm.blocks.get(BASE_KEY2);
|
|
4321
|
-
if (!block) return null;
|
|
4322
|
-
const sides = [];
|
|
4323
|
-
for (const side of WIDTH_SIDES) {
|
|
4324
|
-
const decl = block.decls.get(side);
|
|
4325
|
-
if (!decl) return null;
|
|
4326
|
-
sides.push(decl);
|
|
4327
|
-
}
|
|
4328
|
-
const [top, right, bottom, left] = sides;
|
|
4329
|
-
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
4330
|
-
return null;
|
|
4331
|
-
}
|
|
4332
|
-
const tv = String(top.value);
|
|
4333
|
-
const rv = String(right.value);
|
|
4334
|
-
const bv = String(bottom.value);
|
|
4335
|
-
const lv = String(left.value);
|
|
4336
|
-
if (tv !== bv || lv !== rv) return null;
|
|
4337
|
-
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
4338
|
-
const relative4 = sides.some((d3) => d3.relativeToParent);
|
|
4339
|
-
return { value, important: top.important, relative: relative4 };
|
|
4340
|
-
}
|
|
4341
|
-
function withFoldedWidth(sm, fold) {
|
|
4342
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4343
|
-
for (const [key, block] of sm.blocks) {
|
|
4344
|
-
if (key !== BASE_KEY2) {
|
|
4345
|
-
blocks.set(key, block);
|
|
4346
|
-
continue;
|
|
4347
|
-
}
|
|
4348
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4349
|
-
for (const [prop, decl] of block.decls) {
|
|
4350
|
-
if (WIDTH_SIDE_SET.has(String(prop))) continue;
|
|
4351
|
-
decls.set(prop, decl);
|
|
4352
|
-
}
|
|
4353
|
-
const shorthand = {
|
|
4354
|
-
property: BORDER_WIDTH,
|
|
4355
|
-
value: fold.value,
|
|
4356
|
-
important: fold.important,
|
|
4357
|
-
relativeToParent: fold.relative,
|
|
4358
|
-
inherited: false
|
|
4359
|
-
// border-width is never inherited
|
|
4360
|
-
};
|
|
4361
|
-
decls.set(shorthand.property, shorthand);
|
|
4362
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4363
|
-
}
|
|
4364
|
-
return { blocks };
|
|
4365
|
-
}
|
|
4366
|
-
var borderShorthand = definePattern({
|
|
4367
|
-
name: "border-shorthand",
|
|
4368
|
-
category: "compress/border-shorthand",
|
|
4369
|
-
safety: 1,
|
|
4370
|
-
doc: {
|
|
4371
|
-
title: "Collapse border-width longhands to shorthand",
|
|
4372
|
-
summary: "Equal border width on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent border-width shorthand (border-* / border-x-* border-y-*).",
|
|
4373
|
-
before: '<div class="border-t-2 border-r-2 border-b-2 border-l-2"/>',
|
|
4374
|
-
after: '<div class="border-2"/>',
|
|
4375
|
-
safetyRationale: "A value-preserving re-serialization of the same computed border widths (style/color longhands untouched) \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4376
|
-
},
|
|
4377
|
-
rewrite: {
|
|
4378
|
-
rewriteClasses(computed2) {
|
|
4379
|
-
const fold = analyzeWidth(computed2);
|
|
4380
|
-
return fold ? withFoldedWidth(computed2, fold) : null;
|
|
4381
|
-
}
|
|
4382
|
-
},
|
|
4383
|
-
test: {
|
|
4384
|
-
cases: [
|
|
4385
|
-
{
|
|
4386
|
-
// The four equal width longhands collapse to a `border-width` shorthand at the IR level, and the
|
|
4387
|
-
// minimizing reverse-emit picks the single shortest utility (`border-2`) that reproduces it,
|
|
4388
|
-
// replacing the four `border-{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
4389
|
-
before: '<div className="border-t-2 border-r-2 border-b-2 border-l-2 bg-red-200">box</div>',
|
|
4390
|
-
after: '<div className="bg-red-200 border-2">box</div>'
|
|
4391
|
-
}
|
|
4392
|
-
],
|
|
4393
|
-
// Asymmetric widths (top != bottom) cannot fold into a shorthand.
|
|
4394
|
-
noMatch: ['<div className="border-t-2 border-r-4 border-b-8 border-l-4 bg-red-200">box</div>']
|
|
4395
|
-
}
|
|
4396
|
-
});
|
|
4397
|
-
|
|
4398
|
-
// ../patterns/src/library/compress/dedupe-classes.pattern.ts
|
|
4399
|
-
init_cjs_shims();
|
|
4400
|
-
function findRedundantClasses(computed2) {
|
|
4401
|
-
const winners = /* @__PURE__ */ new Set();
|
|
4402
|
-
const shadowed = /* @__PURE__ */ new Set();
|
|
4403
|
-
for (const block of computed2.blocks.values()) {
|
|
4404
|
-
for (const decl of block.decls.values()) {
|
|
4405
|
-
if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
|
|
4406
|
-
for (const o2 of decl.shadowed ?? []) {
|
|
4407
|
-
if (o2.kind === "class") shadowed.add(o2.className);
|
|
4408
|
-
}
|
|
4409
|
-
}
|
|
4410
|
-
}
|
|
4411
|
-
return { winners, shadowed };
|
|
4412
|
-
}
|
|
4413
|
-
var dedupeClasses = definePattern({
|
|
4414
|
-
name: "dedupe-classes",
|
|
4415
|
-
category: "compress/dedupe-classes",
|
|
4416
|
-
safety: 1,
|
|
4417
|
-
doc: {
|
|
4418
|
-
title: "Dedupe fully-overridden class tokens",
|
|
4419
|
-
summary: "Drops class tokens whose every declaration is overridden by a later token resolving to the same property; the surviving token set produces a byte-for-byte identical computed style.",
|
|
4420
|
-
before: '<p class="text-sm text-lg" />',
|
|
4421
|
-
after: '<p class="text-lg" />',
|
|
4422
|
-
safetyRationale: "A fully-overridden token contributes nothing to the computed style in any condition, so removing it changes no pixels \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4423
|
-
},
|
|
4424
|
-
rewrite: {
|
|
4425
|
-
dropClasses(computed2, ctx) {
|
|
4426
|
-
const { winners, shadowed } = findRedundantClasses(computed2);
|
|
4427
|
-
const drop = /* @__PURE__ */ new Set();
|
|
4428
|
-
for (const cls of shadowed) {
|
|
4429
|
-
if (winners.has(cls)) continue;
|
|
4430
|
-
if (!ctx.resolver.selectorUsage(cls).droppable) continue;
|
|
4431
|
-
drop.add(cls);
|
|
4432
|
-
}
|
|
4433
|
-
return drop;
|
|
4434
|
-
}
|
|
4435
|
-
},
|
|
4436
|
-
test: {
|
|
4437
|
-
cases: [
|
|
4438
|
-
{
|
|
4439
|
-
// `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
|
|
4440
|
-
// records that shadowing in provenance and reports the Tailwind utility as droppable, so the
|
|
4441
|
-
// pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
|
|
4442
|
-
before: '<p className="text-sm text-lg">Hi</p>',
|
|
4443
|
-
after: '<p className="text-lg">Hi</p>'
|
|
4444
|
-
}
|
|
4445
|
-
],
|
|
4446
|
-
// Both tokens win a distinct property (no full override) → nothing to dedupe.
|
|
4447
|
-
noMatch: ['<p className="text-lg font-bold">Hi</p>']
|
|
4448
|
-
}
|
|
4449
|
-
});
|
|
4450
|
-
|
|
4451
|
-
// ../patterns/src/library/compress/gap-shorthand.pattern.ts
|
|
4452
|
-
init_cjs_shims();
|
|
4453
|
-
var ROW_GAP = "row-gap";
|
|
4454
|
-
var COLUMN_GAP = "column-gap";
|
|
4455
|
-
var GAP = "gap";
|
|
4456
|
-
var BASE_KEY3 = conditionKey(BASE_CONDITION);
|
|
4457
|
-
function withGapShorthand(sm, gapDecl) {
|
|
4458
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4459
|
-
for (const [key, block] of sm.blocks) {
|
|
4460
|
-
if (key !== BASE_KEY3) {
|
|
4461
|
-
blocks.set(key, block);
|
|
4462
|
-
continue;
|
|
4463
|
-
}
|
|
4464
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4465
|
-
for (const [prop, decl] of block.decls) {
|
|
4466
|
-
if (prop === ROW_GAP || prop === COLUMN_GAP) continue;
|
|
4467
|
-
decls.set(prop, decl);
|
|
4468
|
-
}
|
|
4469
|
-
decls.set(gapDecl.property, gapDecl);
|
|
4470
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4471
|
-
}
|
|
4472
|
-
return { blocks };
|
|
4473
|
-
}
|
|
4474
|
-
var gapShorthand = definePattern({
|
|
4475
|
-
name: "gap-shorthand",
|
|
4476
|
-
category: "compress/gap-shorthand",
|
|
4477
|
-
safety: 1,
|
|
4478
|
-
doc: {
|
|
4479
|
-
title: "Collapse equal row/column gap into the `gap` shorthand",
|
|
4480
|
-
summary: "An element whose computed row-gap and column-gap are equal has the two axis longhands collapsed into a single-value `gap` shorthand (Tailwind gap-x-* gap-y-* \u2192 gap-*).",
|
|
4481
|
-
before: '<div style="row-gap:16px;column-gap:16px"/>',
|
|
4482
|
-
after: '<div style="gap:16px"/>',
|
|
4483
|
-
safetyRationale: "A single-value `gap` is value-identical to an equal row-gap+column-gap pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4484
|
-
},
|
|
4485
|
-
rewrite: {
|
|
4486
|
-
rewriteClasses(computed2) {
|
|
4487
|
-
const base = computed2.blocks.get(BASE_KEY3);
|
|
4488
|
-
if (!base) return null;
|
|
4489
|
-
const rowGap = base.decls.get(ROW_GAP);
|
|
4490
|
-
const colGap = base.decls.get(COLUMN_GAP);
|
|
4491
|
-
if (!rowGap || !colGap) return null;
|
|
4492
|
-
if (rowGap.important !== colGap.important) return null;
|
|
4493
|
-
if (rowGap.value !== colGap.value) return null;
|
|
4494
|
-
const gapDecl = {
|
|
4495
|
-
property: GAP,
|
|
4496
|
-
value: rowGap.value,
|
|
4497
|
-
important: rowGap.important,
|
|
4498
|
-
relativeToParent: rowGap.relativeToParent || colGap.relativeToParent,
|
|
4499
|
-
inherited: false
|
|
4500
|
-
// gap is not an inherited property
|
|
4501
|
-
};
|
|
4502
|
-
return withGapShorthand(computed2, gapDecl);
|
|
4503
|
-
}
|
|
4504
|
-
},
|
|
4505
|
-
test: {
|
|
4506
|
-
cases: [
|
|
4507
|
-
{
|
|
4508
|
-
// Equal row/column gap collapse to a `gap` decl at the IR level; the minimizing reverse-emit
|
|
4509
|
-
// re-expands `gap` to row-gap+column-gap and picks the single utility covering both (`gap-4`),
|
|
4510
|
-
// replacing the `gap-x-4`+`gap-y-4` pair. `bg-red-200` is preserved.
|
|
4511
|
-
before: '<div className="gap-x-4 gap-y-4 bg-red-200">box</div>',
|
|
4512
|
-
after: '<div className="bg-red-200 gap-4">box</div>'
|
|
4513
|
-
}
|
|
4514
|
-
],
|
|
4515
|
-
// Unequal axes (row-gap != column-gap) have no single-value `gap` equivalent → not collapsed.
|
|
4516
|
-
noMatch: ['<div className="gap-x-2 gap-y-4 bg-red-200">box</div>']
|
|
4517
|
-
}
|
|
4518
|
-
});
|
|
4519
|
-
|
|
4520
|
-
// ../patterns/src/library/compress/inset-shorthand.pattern.ts
|
|
4521
|
-
init_cjs_shims();
|
|
4522
|
-
var TOP = "top";
|
|
4523
|
-
var RIGHT = "right";
|
|
4524
|
-
var BOTTOM = "bottom";
|
|
4525
|
-
var LEFT = "left";
|
|
4526
|
-
var INSET = "inset";
|
|
4527
|
-
var INSET_BLOCK = "inset-block";
|
|
4528
|
-
var INSET_INLINE = "inset-inline";
|
|
4529
|
-
function sameSide(a, b3) {
|
|
4530
|
-
return a !== void 0 && b3 !== void 0 && a.value === b3.value && a.important === b3.important;
|
|
4531
|
-
}
|
|
4532
|
-
function asProperty(src, property) {
|
|
4533
|
-
return { ...src, property, inherited: normalizer.inherited.isInherited(property) };
|
|
4534
|
-
}
|
|
4535
|
-
function withBaseDecls(src, baseDecls) {
|
|
4536
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4537
|
-
for (const [key, block] of src.blocks) {
|
|
4538
|
-
const decls = key === BASE_CONDITION_KEY ? baseDecls : new Map(block.decls);
|
|
4539
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4540
|
-
}
|
|
4541
|
-
return { blocks };
|
|
4542
|
-
}
|
|
4543
|
-
var insetShorthand = definePattern({
|
|
4544
|
-
name: "inset-shorthand",
|
|
4545
|
-
category: "compress/inset-shorthand",
|
|
4546
|
-
safety: 2,
|
|
4547
|
-
doc: {
|
|
4548
|
-
title: "Compress inset longhands into a shorthand",
|
|
4549
|
-
summary: "top/right/bottom/left set to one value collapse to `inset`; a matching top/bottom or left/right pair collapses to `inset-block` / `inset-inline` (Tailwind inset-y / inset-x).",
|
|
4550
|
-
before: '<div style="top:10px;right:10px;bottom:10px;left:10px"/>',
|
|
4551
|
-
after: '<div style="inset:10px"/>',
|
|
4552
|
-
safetyRationale: "Meaning-preserving inset shorthand compaction \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4553
|
-
},
|
|
4554
|
-
rewrite: {
|
|
4555
|
-
rewriteClasses(computed2) {
|
|
4556
|
-
const base = computed2.blocks.get(BASE_CONDITION_KEY);
|
|
4557
|
-
if (!base) return null;
|
|
4558
|
-
const top = base.decls.get(TOP);
|
|
4559
|
-
const right = base.decls.get(RIGHT);
|
|
4560
|
-
const bottom = base.decls.get(BOTTOM);
|
|
4561
|
-
const left = base.decls.get(LEFT);
|
|
4562
|
-
const next = new Map(base.decls);
|
|
4563
|
-
if (top && sameSide(top, right) && sameSide(top, bottom) && sameSide(top, left)) {
|
|
4564
|
-
next.delete(TOP);
|
|
4565
|
-
next.delete(RIGHT);
|
|
4566
|
-
next.delete(BOTTOM);
|
|
4567
|
-
next.delete(LEFT);
|
|
4568
|
-
next.set(INSET, asProperty(top, INSET));
|
|
4569
|
-
} else {
|
|
4570
|
-
let collapsed = false;
|
|
4571
|
-
if (sameSide(top, bottom)) {
|
|
4572
|
-
next.delete(TOP);
|
|
4573
|
-
next.delete(BOTTOM);
|
|
4574
|
-
next.set(INSET_BLOCK, asProperty(top, INSET_BLOCK));
|
|
4575
|
-
collapsed = true;
|
|
4576
|
-
}
|
|
4577
|
-
if (sameSide(left, right)) {
|
|
4578
|
-
next.delete(LEFT);
|
|
4579
|
-
next.delete(RIGHT);
|
|
4580
|
-
next.set(INSET_INLINE, asProperty(left, INSET_INLINE));
|
|
4581
|
-
collapsed = true;
|
|
4582
|
-
}
|
|
4583
|
-
if (!collapsed) return null;
|
|
4584
|
-
}
|
|
4585
|
-
return withBaseDecls(computed2, next);
|
|
4586
|
-
}
|
|
4587
|
-
},
|
|
4588
|
-
test: {
|
|
4589
|
-
cases: [
|
|
4590
|
-
{
|
|
4591
|
-
// The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
|
|
4592
|
-
// minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
|
|
4593
|
-
// covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
|
|
4594
|
-
before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
|
|
4595
|
-
after: '<div className="bg-red-200 inset-0">box</div>'
|
|
4596
|
-
}
|
|
4597
|
-
],
|
|
4598
|
-
// No matching inset pair (all four distinct) → nothing collapses.
|
|
4599
|
-
noMatch: ['<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>']
|
|
4600
|
-
}
|
|
4601
|
-
});
|
|
4602
|
-
|
|
4603
|
-
// ../patterns/src/library/compress/margin-shorthand.pattern.ts
|
|
4604
|
-
init_cjs_shims();
|
|
4605
|
-
var MARGIN_SIDES = [
|
|
4606
|
-
"margin-top",
|
|
4607
|
-
"margin-right",
|
|
4608
|
-
"margin-bottom",
|
|
4609
|
-
"margin-left"
|
|
4610
|
-
];
|
|
4611
|
-
var MARGIN_SIDE_SET = new Set(MARGIN_SIDES);
|
|
4612
|
-
var BASE_KEY4 = conditionKey(BASE_CONDITION);
|
|
4613
|
-
function collapseMarginValue(top, right, bottom, left) {
|
|
4614
|
-
if (right === left) {
|
|
4615
|
-
if (top === bottom) {
|
|
4616
|
-
return top === right ? top : `${top} ${right}`;
|
|
4617
|
-
}
|
|
4618
|
-
return `${top} ${right} ${bottom}`;
|
|
4619
|
-
}
|
|
4620
|
-
return `${top} ${right} ${bottom} ${left}`;
|
|
4621
|
-
}
|
|
4622
|
-
function withFoldedMargin(sm, marginDecl) {
|
|
4623
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4624
|
-
for (const [key, block] of sm.blocks) {
|
|
4625
|
-
if (key !== BASE_KEY4) {
|
|
4626
|
-
blocks.set(key, block);
|
|
4627
|
-
continue;
|
|
4628
|
-
}
|
|
4629
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4630
|
-
for (const [prop, decl] of block.decls) {
|
|
4631
|
-
if (!MARGIN_SIDE_SET.has(String(prop))) decls.set(prop, decl);
|
|
4632
|
-
}
|
|
4633
|
-
decls.set(marginDecl.property, marginDecl);
|
|
4634
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4635
|
-
}
|
|
4636
|
-
return { blocks };
|
|
4637
|
-
}
|
|
4638
|
-
var marginShorthand = definePattern({
|
|
4639
|
-
name: "margin-shorthand",
|
|
4640
|
-
category: "compress/margin-shorthand",
|
|
4641
|
-
safety: 2,
|
|
4642
|
-
doc: {
|
|
4643
|
-
title: "Compress margin longhands into the `margin` shorthand",
|
|
4644
|
-
summary: "An element with margin-top/right/bottom/left all set has them collapsed into the shortest legal `margin` shorthand (the m / mx / my forms); meaning is preserved, declaration count drops.",
|
|
4645
|
-
before: '<div style="margin-top:8px;margin-right:8px;margin-bottom:8px;margin-left:8px"/>',
|
|
4646
|
-
after: '<div style="margin:8px"/>',
|
|
4647
|
-
safetyRationale: "A pure representation change of the same computed margins (no pixels move) \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4648
|
-
},
|
|
4649
|
-
rewrite: {
|
|
4650
|
-
rewriteClasses(computed2) {
|
|
4651
|
-
const base = computed2.blocks.get(BASE_KEY4);
|
|
4652
|
-
if (!base) return null;
|
|
4653
|
-
const sides = MARGIN_SIDES.map((p2) => base.decls.get(p2));
|
|
4654
|
-
if (sides.some((d3) => d3 === void 0)) return null;
|
|
4655
|
-
const [mt, mr, mb, ml] = sides;
|
|
4656
|
-
if (mt.important || mr.important || mb.important || ml.important) return null;
|
|
4657
|
-
const value = collapseMarginValue(
|
|
4658
|
-
String(mt.value),
|
|
4659
|
-
String(mr.value),
|
|
4660
|
-
String(mb.value),
|
|
4661
|
-
String(ml.value)
|
|
4662
|
-
);
|
|
4663
|
-
const marginDecl = {
|
|
4664
|
-
property: "margin",
|
|
4665
|
-
value,
|
|
4666
|
-
important: false,
|
|
4667
|
-
relativeToParent: mt.relativeToParent || mr.relativeToParent || mb.relativeToParent || ml.relativeToParent,
|
|
4668
|
-
inherited: false
|
|
4669
|
-
// margin is not an inherited property
|
|
4670
|
-
};
|
|
4671
|
-
return withFoldedMargin(computed2, marginDecl);
|
|
4672
|
-
}
|
|
4673
|
-
},
|
|
4674
|
-
test: {
|
|
4675
|
-
cases: [
|
|
4676
|
-
{
|
|
4677
|
-
// The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
|
|
4678
|
-
// minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
|
|
4679
|
-
// the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
|
|
4680
|
-
before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
|
|
4681
|
-
after: '<div className="bg-red-200 m-2">box</div>'
|
|
4682
|
-
}
|
|
4683
|
-
],
|
|
4684
|
-
// Only two margin sides set → the four-longhand `margin` collapse does not apply.
|
|
4685
|
-
noMatch: ['<div className="mt-2 mb-2 bg-red-200">box</div>']
|
|
4686
|
-
}
|
|
4687
|
-
});
|
|
4688
|
-
|
|
4689
|
-
// ../patterns/src/library/compress/overflow-shorthand.pattern.ts
|
|
4690
|
-
init_cjs_shims();
|
|
4691
|
-
var OVERFLOW_X = "overflow-x";
|
|
4692
|
-
var OVERFLOW_Y = "overflow-y";
|
|
4693
|
-
var OVERFLOW = "overflow";
|
|
4694
|
-
var BASE_KEY5 = conditionKey(BASE_CONDITION);
|
|
4695
|
-
function withOverflowShorthand(sm, overflowDecl) {
|
|
4696
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4697
|
-
for (const [key, block] of sm.blocks) {
|
|
4698
|
-
if (key !== BASE_KEY5) {
|
|
4699
|
-
blocks.set(key, block);
|
|
4700
|
-
continue;
|
|
4701
|
-
}
|
|
4702
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4703
|
-
for (const [prop, decl] of block.decls) {
|
|
4704
|
-
if (prop === OVERFLOW_X || prop === OVERFLOW_Y) continue;
|
|
4705
|
-
decls.set(prop, decl);
|
|
4706
|
-
}
|
|
4707
|
-
decls.set(overflowDecl.property, overflowDecl);
|
|
4708
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4709
|
-
}
|
|
4710
|
-
return { blocks };
|
|
4711
|
-
}
|
|
4712
|
-
var overflowShorthand = definePattern({
|
|
4713
|
-
name: "overflow-shorthand",
|
|
4714
|
-
category: "compress/overflow-shorthand",
|
|
4715
|
-
safety: 1,
|
|
4716
|
-
doc: {
|
|
4717
|
-
title: "Collapse equal overflow axes into the `overflow` shorthand",
|
|
4718
|
-
summary: "An element whose computed overflow-x and overflow-y are equal has the two axis longhands collapsed into a single `overflow` shorthand (Tailwind overflow-x-* overflow-y-* \u2192 overflow-*).",
|
|
4719
|
-
before: '<div style="overflow-x:auto;overflow-y:auto"/>',
|
|
4720
|
-
after: '<div style="overflow:auto"/>',
|
|
4721
|
-
safetyRationale: "A single-keyword `overflow` is value-identical to equal overflow-x+overflow-y \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4722
|
-
},
|
|
4723
|
-
rewrite: {
|
|
4724
|
-
rewriteClasses(computed2) {
|
|
4725
|
-
const base = computed2.blocks.get(BASE_KEY5);
|
|
4726
|
-
if (!base) return null;
|
|
4727
|
-
const overflowX = base.decls.get(OVERFLOW_X);
|
|
4728
|
-
const overflowY = base.decls.get(OVERFLOW_Y);
|
|
4729
|
-
if (!overflowX || !overflowY) return null;
|
|
4730
|
-
if (overflowX.important !== overflowY.important) return null;
|
|
4731
|
-
if (overflowX.value !== overflowY.value) return null;
|
|
4732
|
-
const overflowDecl = {
|
|
4733
|
-
property: OVERFLOW,
|
|
4734
|
-
value: overflowX.value,
|
|
4735
|
-
important: overflowX.important,
|
|
4736
|
-
relativeToParent: overflowX.relativeToParent || overflowY.relativeToParent,
|
|
4737
|
-
inherited: false
|
|
4738
|
-
// overflow is not an inherited property
|
|
4739
|
-
};
|
|
4740
|
-
return withOverflowShorthand(computed2, overflowDecl);
|
|
4741
|
-
}
|
|
4742
|
-
},
|
|
4743
|
-
test: {
|
|
4744
|
-
cases: [
|
|
4745
|
-
{
|
|
4746
|
-
// Equal overflow axes collapse to an `overflow` decl at the IR level; the minimizing
|
|
4747
|
-
// reverse-emit picks the single utility covering both (`overflow-auto`), replacing the
|
|
4748
|
-
// `overflow-x-auto`+`overflow-y-auto` pair. `bg-red-200` is preserved.
|
|
4749
|
-
before: '<div className="overflow-x-auto overflow-y-auto bg-red-200">box</div>',
|
|
4750
|
-
after: '<div className="bg-red-200 overflow-auto">box</div>'
|
|
4751
|
-
}
|
|
4752
|
-
],
|
|
4753
|
-
// Mismatched axes (overflow-x != overflow-y) have no single-keyword equivalent → not collapsed.
|
|
4754
|
-
noMatch: ['<div className="overflow-x-auto overflow-y-hidden bg-red-200">box</div>']
|
|
4755
|
-
}
|
|
4756
|
-
});
|
|
4757
|
-
|
|
4758
|
-
// ../patterns/src/library/compress/overscroll-behavior-shorthand.pattern.ts
|
|
4759
|
-
init_cjs_shims();
|
|
4760
|
-
var OVERSCROLL_X = "overscroll-behavior-x";
|
|
4761
|
-
var OVERSCROLL_Y = "overscroll-behavior-y";
|
|
4762
|
-
var OVERSCROLL = "overscroll-behavior";
|
|
4763
|
-
var BASE_KEY6 = conditionKey(BASE_CONDITION);
|
|
4764
|
-
var NON_COLLAPSIBLE_VALUES2 = /* @__PURE__ */ new Set([
|
|
4765
|
-
"initial",
|
|
4766
|
-
"inherit",
|
|
4767
|
-
"unset",
|
|
4768
|
-
"revert",
|
|
4769
|
-
"revert-layer"
|
|
4770
|
-
]);
|
|
4771
|
-
function withOverscrollShorthand(sm, shorthand) {
|
|
4772
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4773
|
-
for (const [key, block] of sm.blocks) {
|
|
4774
|
-
if (key !== BASE_KEY6) {
|
|
4775
|
-
blocks.set(key, block);
|
|
4776
|
-
continue;
|
|
4777
|
-
}
|
|
4778
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4779
|
-
for (const [prop, decl] of block.decls) {
|
|
4780
|
-
if (prop === OVERSCROLL_X || prop === OVERSCROLL_Y) continue;
|
|
4781
|
-
decls.set(prop, decl);
|
|
4782
|
-
}
|
|
4783
|
-
decls.set(shorthand.property, shorthand);
|
|
4784
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4785
|
-
}
|
|
4786
|
-
return { blocks };
|
|
4787
|
-
}
|
|
4788
|
-
var overscrollBehaviorShorthand = definePattern({
|
|
4789
|
-
name: "overscroll-behavior-shorthand",
|
|
4790
|
-
category: "compress/overscroll-behavior-shorthand",
|
|
4791
|
-
safety: 1,
|
|
4792
|
-
doc: {
|
|
4793
|
-
title: "Collapse equal overscroll-behavior axes into overscroll-behavior",
|
|
4794
|
-
summary: "An element whose computed overscroll-behavior-x and overscroll-behavior-y are equal has the two axis longhands collapsed into a single `overscroll-behavior` shorthand (Tailwind overscroll-x-* overscroll-y-* \u2192 overscroll-*).",
|
|
4795
|
-
before: '<div style="overscroll-behavior-x:contain;overscroll-behavior-y:contain"/>',
|
|
4796
|
-
after: '<div class="overscroll-contain"/>',
|
|
4797
|
-
safetyRationale: "`overscroll-behavior` is value-identical to an equal x+y axis pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4798
|
-
},
|
|
4799
|
-
rewrite: {
|
|
4800
|
-
rewriteClasses(computed2) {
|
|
4801
|
-
const base = computed2.blocks.get(BASE_KEY6);
|
|
4802
|
-
if (!base) return null;
|
|
4803
|
-
const x2 = base.decls.get(OVERSCROLL_X);
|
|
4804
|
-
const y3 = base.decls.get(OVERSCROLL_Y);
|
|
4805
|
-
if (!x2 || !y3) return null;
|
|
4806
|
-
if (x2.important !== y3.important) return null;
|
|
4807
|
-
const value = String(x2.value);
|
|
4808
|
-
if (NON_COLLAPSIBLE_VALUES2.has(value)) return null;
|
|
4809
|
-
if (value !== String(y3.value)) return null;
|
|
4810
|
-
const shorthand = {
|
|
4811
|
-
property: OVERSCROLL,
|
|
4812
|
-
value: x2.value,
|
|
4813
|
-
important: x2.important,
|
|
4814
|
-
relativeToParent: x2.relativeToParent || y3.relativeToParent,
|
|
4815
|
-
inherited: false
|
|
4816
|
-
// overscroll-behavior is not an inherited property
|
|
4817
|
-
};
|
|
4818
|
-
return withOverscrollShorthand(computed2, shorthand);
|
|
4819
|
-
}
|
|
4820
|
-
},
|
|
4821
|
-
test: {
|
|
4822
|
-
cases: [
|
|
4823
|
-
{
|
|
4824
|
-
// Equal x/y axes collapse to an `overscroll-behavior` decl at the IR level; the minimizing
|
|
4825
|
-
// reverse-emit picks the single utility covering both (`overscroll-contain`), replacing the
|
|
4826
|
-
// `overscroll-x-contain`+`overscroll-y-contain` pair. `bg-red-200` is preserved.
|
|
4827
|
-
before: '<div className="overscroll-x-contain overscroll-y-contain bg-red-200">box</div>',
|
|
4828
|
-
after: '<div className="bg-red-200 overscroll-contain">box</div>'
|
|
4829
|
-
}
|
|
4830
|
-
],
|
|
4831
|
-
// Axes differ (x != y) → no equal-axis collapse.
|
|
4832
|
-
noMatch: ['<div className="overscroll-x-contain overscroll-y-auto bg-red-200">box</div>']
|
|
4833
|
-
}
|
|
4834
|
-
});
|
|
4835
|
-
|
|
4836
|
-
// ../patterns/src/library/compress/padding-shorthand.pattern.ts
|
|
4837
|
-
init_cjs_shims();
|
|
4838
|
-
var PADDING_SIDES = [
|
|
4839
|
-
"padding-top",
|
|
4840
|
-
"padding-right",
|
|
4841
|
-
"padding-bottom",
|
|
4842
|
-
"padding-left"
|
|
4843
|
-
];
|
|
4844
|
-
var PADDING_SIDE_SET = new Set(PADDING_SIDES);
|
|
4845
|
-
var BASE_KEY7 = conditionKey(BASE_CONDITION);
|
|
4846
|
-
function analyzePadding(sm) {
|
|
4847
|
-
const block = sm.blocks.get(BASE_KEY7);
|
|
4848
|
-
if (!block) return null;
|
|
4849
|
-
const sides = [];
|
|
4850
|
-
for (const side of PADDING_SIDES) {
|
|
4851
|
-
const decl = block.decls.get(side);
|
|
4852
|
-
if (!decl) return null;
|
|
4853
|
-
sides.push(decl);
|
|
4854
|
-
}
|
|
4855
|
-
const [top, right, bottom, left] = sides;
|
|
4856
|
-
if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
|
|
4857
|
-
return null;
|
|
4858
|
-
}
|
|
4859
|
-
const tv = String(top.value);
|
|
4860
|
-
const rv = String(right.value);
|
|
4861
|
-
const bv = String(bottom.value);
|
|
4862
|
-
const lv = String(left.value);
|
|
4863
|
-
if (tv !== bv || lv !== rv) return null;
|
|
4864
|
-
const value = tv === lv ? tv : `${tv} ${lv}`;
|
|
4865
|
-
const relative4 = sides.some((d3) => d3.relativeToParent);
|
|
4866
|
-
return { value, important: top.important, relative: relative4 };
|
|
4867
|
-
}
|
|
4868
|
-
function withFoldedPadding(sm, fold) {
|
|
4869
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4870
|
-
for (const [key, block] of sm.blocks) {
|
|
4871
|
-
if (key !== BASE_KEY7) {
|
|
4872
|
-
blocks.set(key, block);
|
|
4873
|
-
continue;
|
|
4874
|
-
}
|
|
4875
|
-
const decls = /* @__PURE__ */ new Map();
|
|
4876
|
-
for (const [prop, decl] of block.decls) {
|
|
4877
|
-
if (PADDING_SIDE_SET.has(String(prop))) continue;
|
|
4878
|
-
decls.set(prop, decl);
|
|
4879
|
-
}
|
|
4880
|
-
const shorthand = {
|
|
4881
|
-
property: "padding",
|
|
4882
|
-
value: fold.value,
|
|
4883
|
-
important: fold.important,
|
|
4884
|
-
relativeToParent: fold.relative,
|
|
4885
|
-
inherited: false
|
|
4886
|
-
// padding is never inherited
|
|
4887
|
-
};
|
|
4888
|
-
decls.set(shorthand.property, shorthand);
|
|
4889
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4890
|
-
}
|
|
4891
|
-
return { blocks };
|
|
4892
|
-
}
|
|
4893
|
-
var paddingShorthand = definePattern({
|
|
4894
|
-
name: "padding-shorthand",
|
|
4895
|
-
category: "compress/padding-shorthand",
|
|
4896
|
-
safety: 1,
|
|
4897
|
-
doc: {
|
|
4898
|
-
title: "Collapse padding longhands to shorthand",
|
|
4899
|
-
summary: "Equal padding on all four sides (or matching x/y pairs) expressed as separate longhand declarations is collapsed to the shortest equivalent padding shorthand (p-* / px-* py-*).",
|
|
4900
|
-
before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
|
|
4901
|
-
after: '<div class="p-4"/>',
|
|
4902
|
-
safetyRationale: "A value-preserving re-serialization of the same computed padding on the same node \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4903
|
-
},
|
|
4904
|
-
rewrite: {
|
|
4905
|
-
rewriteClasses(computed2) {
|
|
4906
|
-
const fold = analyzePadding(computed2);
|
|
4907
|
-
return fold ? withFoldedPadding(computed2, fold) : null;
|
|
4908
|
-
}
|
|
4909
|
-
},
|
|
4910
|
-
test: {
|
|
4911
|
-
cases: [
|
|
4912
|
-
{
|
|
4913
|
-
// The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
|
|
4914
|
-
// minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
|
|
4915
|
-
// replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
|
|
4916
|
-
before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
|
|
4917
|
-
after: '<div className="bg-red-200 p-4">box</div>'
|
|
4918
|
-
},
|
|
4919
|
-
{
|
|
4920
|
-
// A dynamic `{x}` child no longer blocks compress: only the element's OWN class tokens are
|
|
4921
|
-
// rewritten (px-4 py-4 → p-4); the dynamic child is untouched by a class-only change. This is
|
|
4922
|
-
// the real-app common case (most elements have dynamic content).
|
|
4923
|
-
before: '<div className="px-4 py-4">{x}</div>',
|
|
4924
|
-
after: '<div className="p-4">{x}</div>'
|
|
4925
|
-
}
|
|
4926
|
-
],
|
|
4927
|
-
// Asymmetric padding (top != bottom) cannot fold into a shorthand → left unchanged.
|
|
4928
|
-
noMatch: ['<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>']
|
|
4929
|
-
}
|
|
4930
|
-
});
|
|
4931
|
-
|
|
4932
|
-
// ../patterns/src/library/compress/place-shorthand.pattern.ts
|
|
4933
|
-
init_cjs_shims();
|
|
4934
|
-
var ALIGN_ITEMS2 = "align-items";
|
|
4935
|
-
var JUSTIFY_ITEMS2 = "justify-items";
|
|
4936
|
-
var PLACE_ITEMS2 = "place-items";
|
|
4937
|
-
var ALIGN_CONTENT = "align-content";
|
|
4938
|
-
var JUSTIFY_CONTENT2 = "justify-content";
|
|
4939
|
-
var PLACE_CONTENT = "place-content";
|
|
4940
|
-
var BASE_KEY8 = conditionKey(BASE_CONDITION);
|
|
4941
|
-
function samePair(a, b3) {
|
|
4942
|
-
return a !== void 0 && b3 !== void 0 && a.value === b3.value && a.important === b3.important;
|
|
4943
|
-
}
|
|
4944
|
-
function placeDecl(property, align) {
|
|
4945
|
-
return {
|
|
4946
|
-
property,
|
|
4947
|
-
value: align.value,
|
|
4948
|
-
important: align.important,
|
|
4949
|
-
relativeToParent: false,
|
|
4950
|
-
// alignment keywords (center/start/stretch/…) are not length-relative
|
|
4951
|
-
inherited: false
|
|
4952
|
-
// none of the place-* alignment properties are inherited
|
|
4953
|
-
};
|
|
4954
|
-
}
|
|
4955
|
-
function withBaseDecls2(sm, baseDecls) {
|
|
4956
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
4957
|
-
for (const [key, block] of sm.blocks) {
|
|
4958
|
-
const decls = key === BASE_KEY8 ? new Map(baseDecls) : block.decls;
|
|
4959
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
4960
|
-
}
|
|
4961
|
-
return { blocks };
|
|
4962
|
-
}
|
|
4963
|
-
var placeShorthand = definePattern({
|
|
4964
|
-
name: "place-shorthand",
|
|
4965
|
-
category: "compress/place-shorthand",
|
|
4966
|
-
safety: 1,
|
|
4967
|
-
doc: {
|
|
4968
|
-
title: "Collapse matching alignment pairs into `place-*` shorthands",
|
|
4969
|
-
summary: "When align-items equals justify-items they collapse to `place-items`; when align-content equals justify-content they collapse to `place-content`. The two collapses are independent.",
|
|
4970
|
-
before: '<div style="align-items:center;justify-items:center"/>',
|
|
4971
|
-
after: '<div style="place-items:center"/>',
|
|
4972
|
-
safetyRationale: "A `place-*` shorthand is value-identical to its equal align/justify pair \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
4973
|
-
},
|
|
4974
|
-
rewrite: {
|
|
4975
|
-
rewriteClasses(computed2) {
|
|
4976
|
-
const base = computed2.blocks.get(BASE_KEY8);
|
|
4977
|
-
if (!base) return null;
|
|
4978
|
-
const alignItems = base.decls.get(ALIGN_ITEMS2);
|
|
4979
|
-
const justifyItems = base.decls.get(JUSTIFY_ITEMS2);
|
|
4980
|
-
const alignContent = base.decls.get(ALIGN_CONTENT);
|
|
4981
|
-
const justifyContent = base.decls.get(JUSTIFY_CONTENT2);
|
|
4982
|
-
const next = new Map(base.decls);
|
|
4983
|
-
let collapsed = false;
|
|
4984
|
-
if (samePair(alignItems, justifyItems)) {
|
|
4985
|
-
next.delete(ALIGN_ITEMS2);
|
|
4986
|
-
next.delete(JUSTIFY_ITEMS2);
|
|
4987
|
-
next.set(PLACE_ITEMS2, placeDecl(PLACE_ITEMS2, alignItems));
|
|
4988
|
-
collapsed = true;
|
|
4989
|
-
}
|
|
4990
|
-
if (samePair(alignContent, justifyContent)) {
|
|
4991
|
-
next.delete(ALIGN_CONTENT);
|
|
4992
|
-
next.delete(JUSTIFY_CONTENT2);
|
|
4993
|
-
next.set(PLACE_CONTENT, placeDecl(PLACE_CONTENT, alignContent));
|
|
4994
|
-
collapsed = true;
|
|
4995
|
-
}
|
|
4996
|
-
if (!collapsed) return null;
|
|
4997
|
-
return withBaseDecls2(computed2, next);
|
|
4998
|
-
}
|
|
4999
|
-
},
|
|
5000
|
-
test: {
|
|
5001
|
-
cases: [
|
|
5002
|
-
{
|
|
5003
|
-
// The matching items pair collapses to a `place-items` decl at the IR level; the minimizing
|
|
5004
|
-
// reverse-emit picks the single utility covering both (`place-items-center`), replacing the
|
|
5005
|
-
// `items-center`+`justify-items-center` pair. `bg-red-200` is preserved.
|
|
5006
|
-
before: '<div className="items-center justify-items-center bg-red-200">box</div>',
|
|
5007
|
-
after: '<div className="bg-red-200 place-items-center">box</div>'
|
|
5008
|
-
}
|
|
5009
|
-
],
|
|
5010
|
-
// Mismatched alignment (align-items != justify-items, no content pair) → nothing collapses.
|
|
5011
|
-
noMatch: ['<div className="items-center justify-items-start bg-red-200">box</div>']
|
|
5012
|
-
}
|
|
5013
|
-
});
|
|
5014
|
-
|
|
5015
|
-
// ../patterns/src/library/compress/scroll-margin-shorthand.pattern.ts
|
|
5016
|
-
init_cjs_shims();
|
|
5017
|
-
var SCROLL_MARGIN_SIDES = [
|
|
5018
|
-
"scroll-margin-top",
|
|
5019
|
-
"scroll-margin-right",
|
|
5020
|
-
"scroll-margin-bottom",
|
|
5021
|
-
"scroll-margin-left"
|
|
5022
|
-
];
|
|
5023
|
-
var SIDE_SET = new Set(SCROLL_MARGIN_SIDES);
|
|
5024
|
-
var BASE_KEY9 = conditionKey(BASE_CONDITION);
|
|
5025
|
-
var SCROLL_MARGIN = "scroll-margin";
|
|
5026
|
-
var NON_COLLAPSIBLE_VALUES3 = /* @__PURE__ */ new Set([
|
|
5027
|
-
"initial",
|
|
5028
|
-
"inherit",
|
|
5029
|
-
"unset",
|
|
5030
|
-
"revert",
|
|
5031
|
-
"revert-layer"
|
|
5032
|
-
]);
|
|
5033
|
-
function analyzeScrollMargin(sm) {
|
|
5034
|
-
const block = sm.blocks.get(BASE_KEY9);
|
|
5035
|
-
if (!block) return null;
|
|
5036
|
-
const sides = [];
|
|
5037
|
-
for (const side of SCROLL_MARGIN_SIDES) {
|
|
5038
|
-
const decl = block.decls.get(side);
|
|
5039
|
-
if (!decl) return null;
|
|
5040
|
-
sides.push(decl);
|
|
5041
|
-
}
|
|
5042
|
-
const important = sides[0].important;
|
|
5043
|
-
if (!sides.every((d3) => d3.important === important)) return null;
|
|
5044
|
-
const value = String(sides[0].value);
|
|
5045
|
-
if (NON_COLLAPSIBLE_VALUES3.has(value)) return null;
|
|
5046
|
-
if (!sides.every((d3) => String(d3.value) === value)) return null;
|
|
5047
|
-
const relative4 = sides.some((d3) => d3.relativeToParent);
|
|
5048
|
-
return { value, important, relative: relative4 };
|
|
5049
|
-
}
|
|
5050
|
-
function withFoldedScrollMargin(sm, fold) {
|
|
5051
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
5052
|
-
for (const [key, block] of sm.blocks) {
|
|
5053
|
-
if (key !== BASE_KEY9) {
|
|
5054
|
-
blocks.set(key, block);
|
|
5055
|
-
continue;
|
|
5056
|
-
}
|
|
5057
|
-
const decls = /* @__PURE__ */ new Map();
|
|
5058
|
-
for (const [prop, decl] of block.decls) {
|
|
5059
|
-
if (SIDE_SET.has(String(prop))) continue;
|
|
5060
|
-
decls.set(prop, decl);
|
|
5061
|
-
}
|
|
5062
|
-
const shorthand = {
|
|
5063
|
-
property: SCROLL_MARGIN,
|
|
5064
|
-
value: fold.value,
|
|
5065
|
-
important: fold.important,
|
|
5066
|
-
relativeToParent: fold.relative,
|
|
5067
|
-
inherited: false
|
|
5068
|
-
// scroll-margin is never inherited
|
|
5069
|
-
};
|
|
5070
|
-
decls.set(shorthand.property, shorthand);
|
|
5071
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
5072
|
-
}
|
|
5073
|
-
return { blocks };
|
|
5074
|
-
}
|
|
5075
|
-
var scrollMarginShorthand = definePattern({
|
|
5076
|
-
name: "scroll-margin-shorthand",
|
|
5077
|
-
category: "compress/scroll-margin-shorthand",
|
|
5078
|
-
safety: 1,
|
|
5079
|
-
doc: {
|
|
5080
|
-
title: "Collapse equal scroll-margin sides into scroll-margin",
|
|
5081
|
-
summary: "An element whose four scroll-margin sides are all equal is rewritten to the single Tailwind scroll-m-* utility (scroll-margin === the four equal sides).",
|
|
5082
|
-
before: '<div class="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4"/>',
|
|
5083
|
-
after: '<div class="scroll-m-4"/>',
|
|
5084
|
-
safetyRationale: "`scroll-margin` is value-identical to four equal scroll-margin sides \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
5085
|
-
},
|
|
5086
|
-
rewrite: {
|
|
5087
|
-
rewriteClasses(computed2) {
|
|
5088
|
-
const fold = analyzeScrollMargin(computed2);
|
|
5089
|
-
return fold ? withFoldedScrollMargin(computed2, fold) : null;
|
|
5090
|
-
}
|
|
5091
|
-
},
|
|
5092
|
-
test: {
|
|
5093
|
-
cases: [
|
|
5094
|
-
{
|
|
5095
|
-
// The four equal scroll-margin longhands collapse to a `scroll-margin` decl at the IR level; the
|
|
5096
|
-
// minimizing reverse-emit then picks the single shortest utility (`scroll-m-4`) that reproduces
|
|
5097
|
-
// it, replacing the four `scroll-m{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
|
|
5098
|
-
before: '<div className="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4 bg-red-200">box</div>',
|
|
5099
|
-
after: '<div className="bg-red-200 scroll-m-4">box</div>'
|
|
5100
|
-
}
|
|
5101
|
-
],
|
|
5102
|
-
// Sides differ (top != bottom) → no all-equal collapse.
|
|
5103
|
-
noMatch: ['<div className="scroll-mt-2 scroll-mr-4 scroll-mb-8 scroll-ml-4 bg-red-200">box</div>']
|
|
5104
|
-
}
|
|
5105
|
-
});
|
|
5106
|
-
|
|
5107
|
-
// ../patterns/src/library/compress/scroll-padding-shorthand.pattern.ts
|
|
5108
|
-
init_cjs_shims();
|
|
5109
|
-
var SCROLL_PADDING_SIDES = [
|
|
5110
|
-
"scroll-padding-top",
|
|
5111
|
-
"scroll-padding-right",
|
|
5112
|
-
"scroll-padding-bottom",
|
|
5113
|
-
"scroll-padding-left"
|
|
5114
|
-
];
|
|
5115
|
-
var SIDE_SET2 = new Set(SCROLL_PADDING_SIDES);
|
|
5116
|
-
var BASE_KEY10 = conditionKey(BASE_CONDITION);
|
|
5117
|
-
var SCROLL_PADDING = "scroll-padding";
|
|
5118
|
-
var NON_COLLAPSIBLE_VALUES4 = /* @__PURE__ */ new Set([
|
|
5119
|
-
"initial",
|
|
5120
|
-
"inherit",
|
|
5121
|
-
"unset",
|
|
5122
|
-
"revert",
|
|
5123
|
-
"revert-layer"
|
|
5124
|
-
]);
|
|
5125
|
-
function analyzeScrollPadding(sm) {
|
|
5126
|
-
const block = sm.blocks.get(BASE_KEY10);
|
|
5127
|
-
if (!block) return null;
|
|
5128
|
-
const sides = [];
|
|
5129
|
-
for (const side of SCROLL_PADDING_SIDES) {
|
|
5130
|
-
const decl = block.decls.get(side);
|
|
5131
|
-
if (!decl) return null;
|
|
5132
|
-
sides.push(decl);
|
|
5133
|
-
}
|
|
5134
|
-
const important = sides[0].important;
|
|
5135
|
-
if (!sides.every((d3) => d3.important === important)) return null;
|
|
5136
|
-
const value = String(sides[0].value);
|
|
5137
|
-
if (NON_COLLAPSIBLE_VALUES4.has(value)) return null;
|
|
5138
|
-
if (!sides.every((d3) => String(d3.value) === value)) return null;
|
|
5139
|
-
const relative4 = sides.some((d3) => d3.relativeToParent);
|
|
5140
|
-
return { value, important, relative: relative4 };
|
|
5141
|
-
}
|
|
5142
|
-
function withFoldedScrollPadding(sm, fold) {
|
|
5143
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
5144
|
-
for (const [key, block] of sm.blocks) {
|
|
5145
|
-
if (key !== BASE_KEY10) {
|
|
5146
|
-
blocks.set(key, block);
|
|
5147
|
-
continue;
|
|
5148
|
-
}
|
|
5149
|
-
const decls = /* @__PURE__ */ new Map();
|
|
5150
|
-
for (const [prop, decl] of block.decls) {
|
|
5151
|
-
if (SIDE_SET2.has(String(prop))) continue;
|
|
5152
|
-
decls.set(prop, decl);
|
|
5153
|
-
}
|
|
5154
|
-
const shorthand = {
|
|
5155
|
-
property: SCROLL_PADDING,
|
|
5156
|
-
value: fold.value,
|
|
5157
|
-
important: fold.important,
|
|
5158
|
-
relativeToParent: fold.relative,
|
|
5159
|
-
inherited: false
|
|
5160
|
-
// scroll-padding is never inherited
|
|
5161
|
-
};
|
|
5162
|
-
decls.set(shorthand.property, shorthand);
|
|
5163
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
5164
|
-
}
|
|
5165
|
-
return { blocks };
|
|
5166
|
-
}
|
|
5167
|
-
var scrollPaddingShorthand = definePattern({
|
|
5168
|
-
name: "scroll-padding-shorthand",
|
|
5169
|
-
category: "compress/scroll-padding-shorthand",
|
|
5170
|
-
safety: 1,
|
|
5171
|
-
doc: {
|
|
5172
|
-
title: "Collapse equal scroll-padding sides into scroll-padding",
|
|
5173
|
-
summary: "An element whose four scroll-padding sides are all equal is rewritten to the single Tailwind scroll-p-* utility (scroll-padding === the four equal sides).",
|
|
5174
|
-
before: '<div class="scroll-pt-4 scroll-pr-4 scroll-pb-4 scroll-pl-4"/>',
|
|
5175
|
-
after: '<div class="scroll-p-4"/>',
|
|
5176
|
-
safetyRationale: "`scroll-padding` is value-identical to four equal scroll-padding sides \u2014 a class-only change. It is safe even on an element with a ref, event handler, dynamic child, or dangerouslySetInnerHTML \u2014 a className rewrite touches none of them; only a dynamic/opaque class list or a combinator-subject class is excluded, so no behaviour or project selector is disturbed."
|
|
5177
|
-
},
|
|
5178
|
-
rewrite: {
|
|
5179
|
-
rewriteClasses(computed2) {
|
|
5180
|
-
const fold = analyzeScrollPadding(computed2);
|
|
5181
|
-
return fold ? withFoldedScrollPadding(computed2, fold) : null;
|
|
5182
|
-
}
|
|
4105
|
+
not(targetedByStructuralPseudo3)
|
|
4106
|
+
]
|
|
5183
4107
|
},
|
|
4108
|
+
rewrite: { flattenInto: "child" },
|
|
5184
4109
|
test: {
|
|
5185
4110
|
cases: [
|
|
5186
4111
|
{
|
|
5187
|
-
//
|
|
5188
|
-
//
|
|
5189
|
-
|
|
5190
|
-
|
|
5191
|
-
after: '<div className="bg-red-200 scroll-p-4">box</div>'
|
|
4112
|
+
// A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
|
|
4113
|
+
// flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
|
|
4114
|
+
before: '<div><a className="bg-red-200">Link</a></div>',
|
|
4115
|
+
after: '<a className="bg-red-200">Link</a>'
|
|
5192
4116
|
}
|
|
5193
4117
|
],
|
|
5194
|
-
|
|
5195
|
-
|
|
4118
|
+
noMatch: [
|
|
4119
|
+
// A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
|
|
4120
|
+
'<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
|
|
4121
|
+
// A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
|
|
4122
|
+
// provably layout-neutral → the conservative gate leaves it in place.
|
|
4123
|
+
'<div className="flex"><a className="bg-red-200">Link</a></div>'
|
|
4124
|
+
]
|
|
5196
4125
|
}
|
|
5197
4126
|
});
|
|
5198
4127
|
|
|
5199
|
-
// ../patterns/src/library/
|
|
4128
|
+
// ../patterns/src/library/wrapper/redundant-inline-wrapper.pattern.ts
|
|
5200
4129
|
init_cjs_shims();
|
|
5201
|
-
|
|
5202
|
-
|
|
5203
|
-
|
|
5204
|
-
|
|
5205
|
-
function
|
|
5206
|
-
return
|
|
5207
|
-
}
|
|
5208
|
-
function withSizeShorthand(sm, value, important) {
|
|
5209
|
-
const baseKey = conditionKey(BASE_CONDITION);
|
|
5210
|
-
const blocks = /* @__PURE__ */ new Map();
|
|
5211
|
-
for (const [key, block] of sm.blocks) {
|
|
5212
|
-
if (key !== baseKey) {
|
|
5213
|
-
blocks.set(key, block);
|
|
5214
|
-
continue;
|
|
5215
|
-
}
|
|
5216
|
-
const decls = new Map(block.decls);
|
|
5217
|
-
decls.delete(WIDTH);
|
|
5218
|
-
decls.delete(HEIGHT);
|
|
5219
|
-
for (const decl of normalizer.normalizeDeclaration(String(SIZE), value, important)) {
|
|
5220
|
-
decls.set(decl.property, decl);
|
|
5221
|
-
}
|
|
5222
|
-
blocks.set(key, { condition: block.condition, decls });
|
|
5223
|
-
}
|
|
5224
|
-
return { blocks };
|
|
4130
|
+
function asEl3(node) {
|
|
4131
|
+
const n = node;
|
|
4132
|
+
return n.kind === "element" ? n : null;
|
|
4133
|
+
}
|
|
4134
|
+
function metaOf3(node) {
|
|
4135
|
+
return asEl3(node)?.meta ?? null;
|
|
5225
4136
|
}
|
|
5226
|
-
var
|
|
5227
|
-
|
|
5228
|
-
|
|
4137
|
+
var establishesContext2 = (node) => {
|
|
4138
|
+
const m2 = metaOf3(node);
|
|
4139
|
+
if (!m2) return false;
|
|
4140
|
+
return m2.establishesBox || m2.establishesFormattingContext || m2.establishesStackingContext || m2.isContainingBlock || m2.declaresCustomProperties;
|
|
4141
|
+
};
|
|
4142
|
+
var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
|
|
4143
|
+
var isComponentNode4 = (node) => metaOf3(node)?.isComponent ?? false;
|
|
4144
|
+
var hasOwnAttrs3 = (node) => {
|
|
4145
|
+
const el = asEl3(node);
|
|
4146
|
+
if (!el) return false;
|
|
4147
|
+
return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
|
|
4148
|
+
};
|
|
4149
|
+
var targetedByStructuralPseudo4 = (node, ctx) => {
|
|
4150
|
+
const el = asEl3(node);
|
|
4151
|
+
if (!el) return false;
|
|
4152
|
+
if (el.meta.targetedByStructuralPseudo) return true;
|
|
4153
|
+
return ctx.selectors.targetedByStructuralPseudo(el.id);
|
|
4154
|
+
};
|
|
4155
|
+
var DISPLAY3 = "display";
|
|
4156
|
+
var hasNonInlineDisplay = (node, ctx) => {
|
|
4157
|
+
const el = asEl3(node);
|
|
4158
|
+
if (!el) return false;
|
|
4159
|
+
const sm = ctx.computedOf(el) ?? el.computed;
|
|
4160
|
+
for (const block of sm.blocks.values()) {
|
|
4161
|
+
const decl = block.decls.get(DISPLAY3);
|
|
4162
|
+
if (decl && String(decl.value) !== "inline") return true;
|
|
4163
|
+
}
|
|
4164
|
+
return false;
|
|
4165
|
+
};
|
|
4166
|
+
var redundantInlineWrapper = definePattern({
|
|
4167
|
+
name: "redundant-inline-wrapper",
|
|
4168
|
+
category: "flatten/wrapper/redundant-inline-wrapper",
|
|
5229
4169
|
safety: 2,
|
|
5230
4170
|
doc: {
|
|
5231
|
-
title: "
|
|
5232
|
-
summary: "An
|
|
5233
|
-
before:
|
|
5234
|
-
after:
|
|
5235
|
-
safetyRationale: "
|
|
4171
|
+
title: "Flatten redundant inline wrapper",
|
|
4172
|
+
summary: "An inline span with no own visual/box style, no attributes beyond an inert class, exactly one element child, and no opacity barriers is removed; its sole child is hoisted in its place.",
|
|
4173
|
+
before: "<span><Child/></span>",
|
|
4174
|
+
after: "<Child/>",
|
|
4175
|
+
safetyRationale: "An empty inline box paints nothing and establishes no layout/paint/var context; with the inline default display and a single element child its removal changes no paint and no flow. The span carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
|
|
5236
4176
|
},
|
|
5237
|
-
|
|
5238
|
-
|
|
5239
|
-
|
|
5240
|
-
|
|
5241
|
-
|
|
5242
|
-
|
|
5243
|
-
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
4177
|
+
match: {
|
|
4178
|
+
tag: "span",
|
|
4179
|
+
onlyChild: "element",
|
|
4180
|
+
paintsNothing: true,
|
|
4181
|
+
where: [
|
|
4182
|
+
not(hasNonInlineDisplay),
|
|
4183
|
+
not(establishesContext2),
|
|
4184
|
+
not(hasOwnAttrs3),
|
|
4185
|
+
not(hasDynamicClasses),
|
|
4186
|
+
not(hasSpreadAttrs3),
|
|
4187
|
+
not(isComponentNode4),
|
|
4188
|
+
not(targetedByStructuralPseudo4)
|
|
4189
|
+
]
|
|
5248
4190
|
},
|
|
4191
|
+
rewrite: { flattenInto: "child" },
|
|
5249
4192
|
test: {
|
|
5250
4193
|
cases: [
|
|
5251
4194
|
{
|
|
5252
|
-
//
|
|
5253
|
-
//
|
|
5254
|
-
|
|
5255
|
-
|
|
5256
|
-
after: '<div className="bg-red-200 size-10">box</div>'
|
|
4195
|
+
// An empty inline span paints nothing and establishes no context → a provably-safe flatten:
|
|
4196
|
+
// the span is removed and its sole child hoisted in place.
|
|
4197
|
+
before: '<span><a className="text-blue-500">Link</a></span>',
|
|
4198
|
+
after: '<a className="text-blue-500">Link</a>'
|
|
5257
4199
|
}
|
|
5258
4200
|
],
|
|
5259
|
-
|
|
5260
|
-
|
|
4201
|
+
noMatch: [
|
|
4202
|
+
// A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
|
|
4203
|
+
'<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
|
|
4204
|
+
// The span paints its own background (own visual style) → kept.
|
|
4205
|
+
'<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
|
|
4206
|
+
// Non-inline display (inline-block) participates in layout differently → kept.
|
|
4207
|
+
'<span className="inline-block"><a className="text-blue-500">Link</a></span>'
|
|
4208
|
+
]
|
|
5261
4209
|
}
|
|
5262
4210
|
});
|
|
5263
4211
|
|
|
5264
4212
|
// ../patterns/src/_registry.generated.ts
|
|
5265
4213
|
var builtinPatterns = [
|
|
4214
|
+
flexCenterWrapper,
|
|
4215
|
+
redundantFragment,
|
|
4216
|
+
gridCenterWrapper,
|
|
5266
4217
|
displayContentsWrapper,
|
|
5267
4218
|
emptyStyleDiv,
|
|
5268
|
-
|
|
5269
|
-
inlineFlexCenterWrapper,
|
|
5270
|
-
nestedFlexMerge,
|
|
5271
|
-
nestedGridMerge,
|
|
4219
|
+
inheritedOnlyWrapper,
|
|
5272
4220
|
passthroughWrapper,
|
|
5273
|
-
|
|
5274
|
-
redundantInlineWrapper,
|
|
5275
|
-
borderRadiusShorthand,
|
|
5276
|
-
borderShorthand,
|
|
5277
|
-
dedupeClasses,
|
|
5278
|
-
gapShorthand,
|
|
5279
|
-
insetShorthand,
|
|
5280
|
-
marginShorthand,
|
|
5281
|
-
overflowShorthand,
|
|
5282
|
-
overscrollBehaviorShorthand,
|
|
5283
|
-
paddingShorthand,
|
|
5284
|
-
placeShorthand,
|
|
5285
|
-
scrollMarginShorthand,
|
|
5286
|
-
scrollPaddingShorthand,
|
|
5287
|
-
sizeShorthand
|
|
4221
|
+
redundantInlineWrapper
|
|
5288
4222
|
];
|
|
5289
4223
|
|
|
5290
4224
|
// ../resolver-css/src/index.ts
|
|
@@ -5393,11 +4327,11 @@ var import_node_fs2 = require("fs");
|
|
|
5393
4327
|
function isPlainClassToken(token) {
|
|
5394
4328
|
return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
|
|
5395
4329
|
}
|
|
5396
|
-
function readCssPath(
|
|
4330
|
+
function readCssPath(path12) {
|
|
5397
4331
|
try {
|
|
5398
|
-
return { id:
|
|
4332
|
+
return { id: path12, css: (0, import_node_fs2.readFileSync)(path12, "utf8") };
|
|
5399
4333
|
} catch (cause) {
|
|
5400
|
-
throw new Error(`resolver-css: cannot read CSS file "${
|
|
4334
|
+
throw new Error(`resolver-css: cannot read CSS file "${path12}"`, { cause });
|
|
5401
4335
|
}
|
|
5402
4336
|
}
|
|
5403
4337
|
function deriveFingerprint(provider, files) {
|
|
@@ -5453,6 +4387,8 @@ var CustomCSSResolver = class {
|
|
|
5453
4387
|
/** Distinct COMPLEX selectors (combinator or structural pseudo), sorted. */
|
|
5454
4388
|
#complex;
|
|
5455
4389
|
#reverse = null;
|
|
4390
|
+
/** Lazily built cover vocabulary (full condition-keyed tuple sets) for the exact-cover engine. */
|
|
4391
|
+
#coverVocab = null;
|
|
5456
4392
|
constructor(cssFiles = [], options = {}) {
|
|
5457
4393
|
ensurePostcss(options.projectRoot);
|
|
5458
4394
|
const fromDisk = (options.files ?? []).map(readCssPath);
|
|
@@ -5485,8 +4421,23 @@ var CustomCSSResolver = class {
|
|
|
5485
4421
|
}
|
|
5486
4422
|
emit(styles, ctx) {
|
|
5487
4423
|
const norm = ctx.normalizer ?? normalizer;
|
|
4424
|
+
const normalized = norm.normalizeStyleMap(styles);
|
|
4425
|
+
const universe = styleMapTuples(normalized, norm);
|
|
4426
|
+
if (universe.length === 0) return { classes: [], exact: true, warnings: [] };
|
|
4427
|
+
const chosen = minStringCover(universe, this.#buildCoverVocab());
|
|
4428
|
+
if (chosen && chosen.length > 0) {
|
|
4429
|
+
const reTuples = new Set(styleMapTuples(this.resolve({ classes: chosen }).styles, norm));
|
|
4430
|
+
let ok = reTuples.size === universe.length;
|
|
4431
|
+
if (ok) {
|
|
4432
|
+
for (const t of universe) if (!reTuples.has(t)) {
|
|
4433
|
+
ok = false;
|
|
4434
|
+
break;
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
if (ok) return { classes: chosen, exact: true, warnings: [] };
|
|
4438
|
+
}
|
|
5488
4439
|
const remaining = /* @__PURE__ */ new Map();
|
|
5489
|
-
for (const [ck, block] of
|
|
4440
|
+
for (const [ck, block] of normalized.blocks) {
|
|
5490
4441
|
for (const [prop, decl] of block.decls) {
|
|
5491
4442
|
remaining.set(`${ck} ${prop}`, String(decl.value));
|
|
5492
4443
|
}
|
|
@@ -5696,7 +4647,23 @@ var CustomCSSResolver = class {
|
|
|
5696
4647
|
if (rawBlocks.size === 0) return emptyStyleMap();
|
|
5697
4648
|
return normalizer.normalizeStyleMap({ blocks: rawBlocks });
|
|
5698
4649
|
}
|
|
5699
|
-
/**
|
|
4650
|
+
/**
|
|
4651
|
+
* Build (once) the cover vocabulary for the exact-cover engine: every forward-resolvable class
|
|
4652
|
+
* mapped to the {@link styleMapTuples} of its full (condition-keyed, `!important`-aware) declaration
|
|
4653
|
+
* set. Unlike {@link #reverseIndex} this carries ALL style conditions and the important flag, so the
|
|
4654
|
+
* engine can pick a custom class covering hover/media declarations too.
|
|
4655
|
+
*/
|
|
4656
|
+
#buildCoverVocab() {
|
|
4657
|
+
if (this.#coverVocab) return this.#coverVocab;
|
|
4658
|
+
const out = [];
|
|
4659
|
+
for (const token of this.#classIndex.keys()) {
|
|
4660
|
+
const tuples = styleMapTuples(this.#resolveTokens([token], [token]), normalizer);
|
|
4661
|
+
if (tuples.length > 0) out.push({ token, tuples });
|
|
4662
|
+
}
|
|
4663
|
+
this.#coverVocab = out;
|
|
4664
|
+
return out;
|
|
4665
|
+
}
|
|
4666
|
+
/** Build (once) the reverse index used by the greedy {@link emit} fallback. */
|
|
5700
4667
|
#reverseIndex() {
|
|
5701
4668
|
if (this.#reverse) return this.#reverse;
|
|
5702
4669
|
const out = [];
|
|
@@ -5776,14 +4743,315 @@ function synthesizeResidual(remaining, ctx) {
|
|
|
5776
4743
|
// ../resolver-tailwind/src/tailwind/engine.ts
|
|
5777
4744
|
init_cjs_shims();
|
|
5778
4745
|
var import_node_module3 = require("module");
|
|
4746
|
+
var path6 = __toESM(require("path"), 1);
|
|
4747
|
+
|
|
4748
|
+
// ../resolver-tailwind/src/tailwind/engine-v4.ts
|
|
4749
|
+
init_cjs_shims();
|
|
4750
|
+
var import_node_fs4 = require("fs");
|
|
4751
|
+
var path5 = __toESM(require("path"), 1);
|
|
4752
|
+
|
|
4753
|
+
// ../resolver-tailwind/src/tailwind/v4-bridge.ts
|
|
4754
|
+
init_cjs_shims();
|
|
4755
|
+
var import_node_child_process2 = require("child_process");
|
|
4756
|
+
var import_node_fs3 = require("fs");
|
|
4757
|
+
var import_node_os = require("os");
|
|
5779
4758
|
var path4 = __toESM(require("path"), 1);
|
|
4759
|
+
var CHILD_SOURCE = String.raw`
|
|
4760
|
+
import { createRequire } from 'node:module';
|
|
4761
|
+
import { pathToFileURL } from 'node:url';
|
|
4762
|
+
import * as fs from 'node:fs';
|
|
4763
|
+
import * as path from 'node:path';
|
|
4764
|
+
|
|
4765
|
+
function out(obj) { process.stdout.write(JSON.stringify(obj)); process.exit(0); }
|
|
4766
|
+
|
|
4767
|
+
let payload;
|
|
4768
|
+
try { payload = JSON.parse(fs.readFileSync(process.argv[2], 'utf8')); }
|
|
4769
|
+
catch { out({ ok: false }); }
|
|
4770
|
+
|
|
4771
|
+
const projectRoot = payload.projectRoot;
|
|
4772
|
+
const entries = payload.entries || [];
|
|
4773
|
+
const req = createRequire(path.join(projectRoot, '__domflax_tw4__.js'));
|
|
4774
|
+
|
|
4775
|
+
async function importFrom(id) {
|
|
4776
|
+
const resolved = req.resolve(id);
|
|
4777
|
+
return import(pathToFileURL(resolved).href);
|
|
4778
|
+
}
|
|
4779
|
+
|
|
4780
|
+
// Primary loader: @tailwindcss/node (the companion every v4 build tool installs). It resolves
|
|
4781
|
+
// '@import "tailwindcss"' and @theme against the project on disk.
|
|
4782
|
+
async function loadViaNode() {
|
|
4783
|
+
let mod;
|
|
4784
|
+
try { mod = await importFrom('@tailwindcss/node'); } catch { return null; }
|
|
4785
|
+
if (!mod || typeof mod.__unstable__loadDesignSystem !== 'function') return null;
|
|
4786
|
+
for (const e of entries) {
|
|
4787
|
+
try { return await mod.__unstable__loadDesignSystem(e.css, { base: e.base }); } catch {}
|
|
4788
|
+
}
|
|
4789
|
+
return null;
|
|
4790
|
+
}
|
|
4791
|
+
|
|
4792
|
+
// Secondary loader: bare 'tailwindcss' with a filesystem stylesheet resolver (best-effort).
|
|
4793
|
+
async function loadViaCore() {
|
|
4794
|
+
let tw;
|
|
4795
|
+
try { tw = await importFrom('tailwindcss'); } catch { return null; }
|
|
4796
|
+
if (!tw || typeof tw.__unstable__loadDesignSystem !== 'function') return null;
|
|
4797
|
+
const loadStylesheet = async (id, base) => {
|
|
4798
|
+
const r = createRequire(path.join(base, '__domflax_tw4__.js'));
|
|
4799
|
+
let p;
|
|
4800
|
+
const tries = id === 'tailwindcss' ? ['tailwindcss/index.css', 'tailwindcss'] : [id, id + '/index.css'];
|
|
4801
|
+
for (const t of tries) { try { p = r.resolve(t); break; } catch {} }
|
|
4802
|
+
if (!p) p = path.resolve(base, id);
|
|
4803
|
+
return { path: p, base: path.dirname(p), content: fs.readFileSync(p, 'utf8') };
|
|
4804
|
+
};
|
|
4805
|
+
const loadModule = async (id, base) => {
|
|
4806
|
+
const r = createRequire(path.join(base, '__domflax_tw4__.js'));
|
|
4807
|
+
const p = r.resolve(id);
|
|
4808
|
+
return { path: p, base: path.dirname(p), module: (await import(pathToFileURL(p).href)).default };
|
|
4809
|
+
};
|
|
4810
|
+
for (const e of entries) {
|
|
4811
|
+
try { return await tw.__unstable__loadDesignSystem(e.css, { base: e.base, loadStylesheet, loadModule }); } catch {}
|
|
4812
|
+
}
|
|
4813
|
+
return null;
|
|
4814
|
+
}
|
|
4815
|
+
|
|
4816
|
+
const ds = (await loadViaNode()) || (await loadViaCore());
|
|
4817
|
+
if (!ds) out({ ok: false });
|
|
4818
|
+
|
|
4819
|
+
let names = [];
|
|
4820
|
+
try {
|
|
4821
|
+
names = ds.getClassList().map((e) => (Array.isArray(e) ? e[0] : e)).filter((n) => typeof n === 'string');
|
|
4822
|
+
} catch { out({ ok: false }); }
|
|
4823
|
+
|
|
4824
|
+
let css = [];
|
|
4825
|
+
try { css = ds.candidatesToCss(names); } catch { out({ ok: false }); }
|
|
4826
|
+
|
|
4827
|
+
const result = [];
|
|
4828
|
+
for (let i = 0; i < names.length; i += 1) {
|
|
4829
|
+
const c = css[i];
|
|
4830
|
+
if (typeof c === 'string' && c.length > 0) result.push([names[i], c]);
|
|
4831
|
+
}
|
|
4832
|
+
out({ ok: true, entries: result });
|
|
4833
|
+
`;
|
|
4834
|
+
function runV4Bridge(payload) {
|
|
4835
|
+
let dir = null;
|
|
4836
|
+
try {
|
|
4837
|
+
dir = (0, import_node_fs3.mkdtempSync)(path4.join((0, import_node_os.tmpdir)(), "domflax-tw4-"));
|
|
4838
|
+
const scriptPath = path4.join(dir, "bridge.mjs");
|
|
4839
|
+
const payloadPath = path4.join(dir, "payload.json");
|
|
4840
|
+
(0, import_node_fs3.writeFileSync)(scriptPath, CHILD_SOURCE, "utf8");
|
|
4841
|
+
(0, import_node_fs3.writeFileSync)(payloadPath, JSON.stringify(payload), "utf8");
|
|
4842
|
+
const stdout = (0, import_node_child_process2.execFileSync)(process.execPath, [scriptPath, payloadPath], {
|
|
4843
|
+
cwd: payload.projectRoot,
|
|
4844
|
+
encoding: "utf8",
|
|
4845
|
+
timeout: 9e4,
|
|
4846
|
+
maxBuffer: 256 * 1024 * 1024,
|
|
4847
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
4848
|
+
});
|
|
4849
|
+
const parsed = JSON.parse(stdout);
|
|
4850
|
+
if (!parsed.ok || !Array.isArray(parsed.entries) || parsed.entries.length === 0) return null;
|
|
4851
|
+
const entries = parsed.entries.filter(
|
|
4852
|
+
(e2) => Array.isArray(e2) && typeof e2[0] === "string" && typeof e2[1] === "string"
|
|
4853
|
+
);
|
|
4854
|
+
return entries.length > 0 ? { entries } : null;
|
|
4855
|
+
} catch {
|
|
4856
|
+
return null;
|
|
4857
|
+
} finally {
|
|
4858
|
+
if (dir) {
|
|
4859
|
+
try {
|
|
4860
|
+
(0, import_node_fs3.rmSync)(dir, { recursive: true, force: true });
|
|
4861
|
+
} catch {
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
}
|
|
4865
|
+
}
|
|
4866
|
+
|
|
4867
|
+
// ../resolver-tailwind/src/tailwind/v4-css.ts
|
|
4868
|
+
init_cjs_shims();
|
|
4869
|
+
function stripComments(src) {
|
|
4870
|
+
return src.replace(/\/\*[\s\S]*?\*\//g, "");
|
|
4871
|
+
}
|
|
4872
|
+
function toDecl(buffer) {
|
|
4873
|
+
const buf = buffer.trim();
|
|
4874
|
+
if (buf.length === 0 || buf[0] === "@") return null;
|
|
4875
|
+
const colon = buf.indexOf(":");
|
|
4876
|
+
if (colon <= 0) return null;
|
|
4877
|
+
const prop = buf.slice(0, colon).trim();
|
|
4878
|
+
let value = buf.slice(colon + 1).trim();
|
|
4879
|
+
if (prop.length === 0 || value.length === 0) return null;
|
|
4880
|
+
let important = false;
|
|
4881
|
+
const bang = /!\s*important\s*$/i.exec(value);
|
|
4882
|
+
if (bang) {
|
|
4883
|
+
important = true;
|
|
4884
|
+
value = value.slice(0, bang.index).trim();
|
|
4885
|
+
}
|
|
4886
|
+
return { type: "decl", prop, value, important };
|
|
4887
|
+
}
|
|
4888
|
+
function splitAtRule(prelude) {
|
|
4889
|
+
const m2 = /^@([A-Za-z-]+)\s*([\s\S]*)$/.exec(prelude);
|
|
4890
|
+
if (!m2) return { name: prelude.slice(1).trim(), params: "" };
|
|
4891
|
+
return { name: m2[1].toLowerCase(), params: m2[2].trim() };
|
|
4892
|
+
}
|
|
4893
|
+
function parseBlock(src, start) {
|
|
4894
|
+
const nodes = [];
|
|
4895
|
+
let buf = "";
|
|
4896
|
+
let i = start;
|
|
4897
|
+
while (i < src.length) {
|
|
4898
|
+
const c = src[i];
|
|
4899
|
+
if (c === "{") {
|
|
4900
|
+
const prelude = buf.trim();
|
|
4901
|
+
buf = "";
|
|
4902
|
+
const inner = parseBlock(src, i + 1);
|
|
4903
|
+
i = inner.next;
|
|
4904
|
+
if (prelude.startsWith("@")) {
|
|
4905
|
+
const { name, params } = splitAtRule(prelude);
|
|
4906
|
+
nodes.push({ type: "atrule", name, params, nodes: inner.nodes });
|
|
4907
|
+
} else if (prelude.length > 0) {
|
|
4908
|
+
nodes.push({ type: "rule", selector: prelude, nodes: inner.nodes });
|
|
4909
|
+
}
|
|
4910
|
+
} else if (c === "}") {
|
|
4911
|
+
const d3 = toDecl(buf);
|
|
4912
|
+
if (d3) nodes.push(d3);
|
|
4913
|
+
return { nodes, next: i + 1 };
|
|
4914
|
+
} else if (c === ";") {
|
|
4915
|
+
const d3 = toDecl(buf);
|
|
4916
|
+
if (d3) nodes.push(d3);
|
|
4917
|
+
buf = "";
|
|
4918
|
+
i += 1;
|
|
4919
|
+
} else {
|
|
4920
|
+
buf += c;
|
|
4921
|
+
i += 1;
|
|
4922
|
+
}
|
|
4923
|
+
}
|
|
4924
|
+
const tail = toDecl(buf);
|
|
4925
|
+
if (tail) nodes.push(tail);
|
|
4926
|
+
return { nodes, next: i };
|
|
4927
|
+
}
|
|
4928
|
+
function resolveNesting(child, parent) {
|
|
4929
|
+
const c = child.trim();
|
|
4930
|
+
if (parent.length === 0) return c;
|
|
4931
|
+
if (c.includes("&")) return c.split("&").join(parent);
|
|
4932
|
+
return `${parent} ${c}`;
|
|
4933
|
+
}
|
|
4934
|
+
var DROP_ATRULES = /* @__PURE__ */ new Set(["property", "keyframes", "font-face", "charset", "import"]);
|
|
4935
|
+
function flattenNodes(nodes, selector, at, out) {
|
|
4936
|
+
const own = [];
|
|
4937
|
+
for (const n of nodes) if (n.type === "decl") own.push(n);
|
|
4938
|
+
if (own.length > 0 && selector.length > 0) out.push({ selector, at: [...at], decls: own });
|
|
4939
|
+
for (const n of nodes) {
|
|
4940
|
+
if (n.type === "rule") {
|
|
4941
|
+
flattenNodes(n.nodes, resolveNesting(n.selector, selector), at, out);
|
|
4942
|
+
} else if (n.type === "atrule") {
|
|
4943
|
+
if (n.name === "media") {
|
|
4944
|
+
flattenNodes(n.nodes, selector, [...at, { name: "media", params: n.params }], out);
|
|
4945
|
+
} else if (n.name === "layer") {
|
|
4946
|
+
flattenNodes(n.nodes, selector, at, out);
|
|
4947
|
+
} else if (!DROP_ATRULES.has(n.name)) {
|
|
4948
|
+
flattenNodes(n.nodes, selector, [...at, { name: n.name, params: n.params }], out);
|
|
4949
|
+
}
|
|
4950
|
+
}
|
|
4951
|
+
}
|
|
4952
|
+
}
|
|
4953
|
+
function leafToNode(leaf) {
|
|
4954
|
+
const declNodes = leaf.decls.map((d3) => ({
|
|
4955
|
+
type: "decl",
|
|
4956
|
+
prop: d3.prop,
|
|
4957
|
+
value: d3.value,
|
|
4958
|
+
important: d3.important
|
|
4959
|
+
}));
|
|
4960
|
+
let node = { type: "rule", selector: leaf.selector, nodes: declNodes };
|
|
4961
|
+
for (let i = leaf.at.length - 1; i >= 0; i -= 1) {
|
|
4962
|
+
node = { type: "atrule", name: leaf.at[i].name, params: leaf.at[i].params, nodes: [node] };
|
|
4963
|
+
}
|
|
4964
|
+
return node;
|
|
4965
|
+
}
|
|
4966
|
+
function parseUtilityCss(css) {
|
|
4967
|
+
try {
|
|
4968
|
+
const { nodes } = parseBlock(stripComments(css), 0);
|
|
4969
|
+
const leaves = [];
|
|
4970
|
+
flattenNodes(nodes, "", [], leaves);
|
|
4971
|
+
return leaves.map(leafToNode);
|
|
4972
|
+
} catch {
|
|
4973
|
+
return [];
|
|
4974
|
+
}
|
|
4975
|
+
}
|
|
4976
|
+
|
|
4977
|
+
// ../resolver-tailwind/src/tailwind/engine-v4.ts
|
|
4978
|
+
var SEARCH_DIRS = ["", "src", "app", "styles", "src/styles", "src/app", "app/styles", "assets/css", "css"];
|
|
4979
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", "out", ".next", "coverage"]);
|
|
4980
|
+
var ENTRY_RE = /@import\s+["']tailwindcss["']|@tailwind\b|@theme\b/;
|
|
4981
|
+
function scanDir(dir) {
|
|
4982
|
+
let names;
|
|
4983
|
+
try {
|
|
4984
|
+
names = (0, import_node_fs4.readdirSync)(dir);
|
|
4985
|
+
} catch {
|
|
4986
|
+
return null;
|
|
4987
|
+
}
|
|
4988
|
+
for (const name of names) {
|
|
4989
|
+
if (!name.toLowerCase().endsWith(".css")) continue;
|
|
4990
|
+
const file = path5.join(dir, name);
|
|
4991
|
+
try {
|
|
4992
|
+
if (!(0, import_node_fs4.statSync)(file).isFile()) continue;
|
|
4993
|
+
const css = (0, import_node_fs4.readFileSync)(file, "utf8");
|
|
4994
|
+
if (ENTRY_RE.test(css)) return { css, base: path5.dirname(file) };
|
|
4995
|
+
} catch {
|
|
4996
|
+
}
|
|
4997
|
+
}
|
|
4998
|
+
return null;
|
|
4999
|
+
}
|
|
5000
|
+
function findCssEntries(projectRoot) {
|
|
5001
|
+
const out = [];
|
|
5002
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5003
|
+
for (const rel of SEARCH_DIRS) {
|
|
5004
|
+
const dir = path5.resolve(projectRoot, rel);
|
|
5005
|
+
if (seen.has(dir) || [...SKIP_DIRS].some((s) => dir.includes(`${path5.sep}${s}`))) continue;
|
|
5006
|
+
seen.add(dir);
|
|
5007
|
+
const hit = scanDir(dir);
|
|
5008
|
+
if (hit) {
|
|
5009
|
+
out.push(hit);
|
|
5010
|
+
break;
|
|
5011
|
+
}
|
|
5012
|
+
}
|
|
5013
|
+
out.push({ css: '@import "tailwindcss";', base: projectRoot });
|
|
5014
|
+
return out;
|
|
5015
|
+
}
|
|
5016
|
+
function makeV4Engine(entries, version) {
|
|
5017
|
+
const cssByClass = new Map(entries.map(([name, css]) => [name, css]));
|
|
5018
|
+
const nodeCache = /* @__PURE__ */ new Map();
|
|
5019
|
+
const nodesFor = (token) => {
|
|
5020
|
+
let cached = nodeCache.get(token);
|
|
5021
|
+
if (!cached) {
|
|
5022
|
+
const css = cssByClass.get(token);
|
|
5023
|
+
cached = css ? parseUtilityCss(css) : [];
|
|
5024
|
+
nodeCache.set(token, cached);
|
|
5025
|
+
}
|
|
5026
|
+
return cached;
|
|
5027
|
+
};
|
|
5028
|
+
return {
|
|
5029
|
+
version,
|
|
5030
|
+
context: {
|
|
5031
|
+
// The resolver keeps only string entries; we hand it the concrete class names directly.
|
|
5032
|
+
getClassList: () => [...cssByClass.keys()]
|
|
5033
|
+
},
|
|
5034
|
+
generate(candidates) {
|
|
5035
|
+
const out = [];
|
|
5036
|
+
for (const c of candidates) for (const n of nodesFor(c)) out.push(n);
|
|
5037
|
+
return out;
|
|
5038
|
+
}
|
|
5039
|
+
};
|
|
5040
|
+
}
|
|
5041
|
+
function loadV4Engine(projectRoot, version) {
|
|
5042
|
+
const snapshot = runV4Bridge({ projectRoot, entries: findCssEntries(projectRoot) });
|
|
5043
|
+
if (!snapshot) return null;
|
|
5044
|
+
return makeV4Engine(snapshot.entries, version);
|
|
5045
|
+
}
|
|
5046
|
+
|
|
5047
|
+
// ../resolver-tailwind/src/tailwind/engine.ts
|
|
5780
5048
|
function moduleBase2() {
|
|
5781
5049
|
return typeof __filename === "string" ? __filename : importMetaUrl;
|
|
5782
5050
|
}
|
|
5783
5051
|
function projectRequire(projectRoot) {
|
|
5784
5052
|
const bases = [];
|
|
5785
|
-
if (projectRoot) bases.push(
|
|
5786
|
-
bases.push(
|
|
5053
|
+
if (projectRoot) bases.push(path6.join(projectRoot, "__domflax__.js"));
|
|
5054
|
+
bases.push(path6.join(process.cwd(), "__domflax__.js"));
|
|
5787
5055
|
bases.push(moduleBase2());
|
|
5788
5056
|
for (const base of bases) {
|
|
5789
5057
|
try {
|
|
@@ -5795,14 +5063,36 @@ function projectRequire(projectRoot) {
|
|
|
5795
5063
|
}
|
|
5796
5064
|
return null;
|
|
5797
5065
|
}
|
|
5066
|
+
var FIRST_UNSUPPORTED_MAJOR = 4;
|
|
5067
|
+
function majorOf(version) {
|
|
5068
|
+
const m2 = /^\s*(\d+)/.exec(version);
|
|
5069
|
+
return m2 ? Number(m2[1]) : null;
|
|
5070
|
+
}
|
|
5798
5071
|
function loadEngine(options) {
|
|
5799
5072
|
const req = projectRequire(options.projectRoot);
|
|
5800
|
-
if (!req) return null;
|
|
5073
|
+
if (!req) return { engine: null, version: null, unsupportedMajor: null };
|
|
5074
|
+
let version = null;
|
|
5075
|
+
try {
|
|
5076
|
+
version = req("tailwindcss/package.json").version;
|
|
5077
|
+
} catch {
|
|
5078
|
+
return { engine: null, version: null, unsupportedMajor: null };
|
|
5079
|
+
}
|
|
5080
|
+
const major = majorOf(version);
|
|
5081
|
+
if (major !== null && major >= FIRST_UNSUPPORTED_MAJOR) {
|
|
5082
|
+
const projectRoot = options.projectRoot ?? process.cwd();
|
|
5083
|
+
let v4 = null;
|
|
5084
|
+
try {
|
|
5085
|
+
v4 = loadV4Engine(projectRoot, version);
|
|
5086
|
+
} catch {
|
|
5087
|
+
v4 = null;
|
|
5088
|
+
}
|
|
5089
|
+
if (v4) return { engine: v4, version, unsupportedMajor: null };
|
|
5090
|
+
return { engine: null, version, unsupportedMajor: major };
|
|
5091
|
+
}
|
|
5801
5092
|
try {
|
|
5802
5093
|
const resolveConfig = req("tailwindcss/resolveConfig.js");
|
|
5803
5094
|
const { createContext } = req("tailwindcss/lib/lib/setupContextUtils.js");
|
|
5804
5095
|
const { generateRules } = req("tailwindcss/lib/lib/generateRules.js");
|
|
5805
|
-
const pkg = req("tailwindcss/package.json");
|
|
5806
5096
|
let userConfig = options.config ?? { content: [{ raw: "" }] };
|
|
5807
5097
|
if (options.configPath !== void 0) {
|
|
5808
5098
|
const loadConfig = req("tailwindcss/loadConfig.js");
|
|
@@ -5811,15 +5101,19 @@ function loadEngine(options) {
|
|
|
5811
5101
|
const resolved = resolveConfig(userConfig);
|
|
5812
5102
|
const context = createContext(resolved);
|
|
5813
5103
|
return {
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5104
|
+
engine: {
|
|
5105
|
+
version,
|
|
5106
|
+
context,
|
|
5107
|
+
generate(candidates) {
|
|
5108
|
+
const rules = generateRules(new Set(candidates), context);
|
|
5109
|
+
return rules.map(([, node]) => node);
|
|
5110
|
+
}
|
|
5111
|
+
},
|
|
5112
|
+
version,
|
|
5113
|
+
unsupportedMajor: null
|
|
5820
5114
|
};
|
|
5821
5115
|
} catch {
|
|
5822
|
-
return null;
|
|
5116
|
+
return { engine: null, version, unsupportedMajor: null };
|
|
5823
5117
|
}
|
|
5824
5118
|
}
|
|
5825
5119
|
|
|
@@ -6005,22 +5299,39 @@ var DROPPABLE_USAGE = {
|
|
|
6005
5299
|
};
|
|
6006
5300
|
|
|
6007
5301
|
// ../resolver-tailwind/src/tailwind/resolver.ts
|
|
5302
|
+
var warnedUnsupported = /* @__PURE__ */ new Set();
|
|
6008
5303
|
var TailwindResolver = class {
|
|
6009
5304
|
id = "tailwind";
|
|
6010
5305
|
provider;
|
|
6011
5306
|
fingerprint;
|
|
5307
|
+
/**
|
|
5308
|
+
* SAFETY (Layer 1): the detected Tailwind MAJOR when the project's version is one this resolver
|
|
5309
|
+
* cannot drive (v4+), else `null`. When set, {@link resolve} reports every token as unknown, so
|
|
5310
|
+
* downstream files are left unchanged (never mis-optimized). Exposed for diagnostics/tests.
|
|
5311
|
+
*/
|
|
5312
|
+
unsupportedMajor;
|
|
6012
5313
|
#engine;
|
|
6013
5314
|
/** Per-token extraction cache (engine output is pure for a fixed config). */
|
|
6014
5315
|
#tokenCache = /* @__PURE__ */ new Map();
|
|
6015
5316
|
/** Per-class-set forward-resolution cache. */
|
|
6016
5317
|
#resolveCache = /* @__PURE__ */ new Map();
|
|
6017
|
-
/** Lazily built reverse index for {@link emit}. */
|
|
5318
|
+
/** Lazily built reverse index for the greedy {@link emit} fallback. */
|
|
6018
5319
|
#reverseIndex = null;
|
|
5320
|
+
/** Lazily built cover vocabulary (base-condition tuple sets) for the exact-cover engine. */
|
|
5321
|
+
#coverVocab = null;
|
|
6019
5322
|
constructor(config = {}) {
|
|
6020
|
-
|
|
6021
|
-
this
|
|
5323
|
+
const loaded = loadEngine(config);
|
|
5324
|
+
this.#engine = loaded.engine;
|
|
5325
|
+
this.unsupportedMajor = loaded.unsupportedMajor;
|
|
5326
|
+
this.provider = config.provider ?? (loaded.version ? `tailwindcss@${loaded.version}` : "tailwindcss");
|
|
6022
5327
|
const seed = JSON.stringify(config.config ?? {}) + (config.configPath ?? "");
|
|
6023
5328
|
this.fingerprint = config.fingerprint ?? `${this.provider}/${fnv1a(seed)}`;
|
|
5329
|
+
if (this.unsupportedMajor !== null && !warnedUnsupported.has(this.provider)) {
|
|
5330
|
+
warnedUnsupported.add(this.provider);
|
|
5331
|
+
console.warn(
|
|
5332
|
+
`domflax: detected Tailwind v${this.unsupportedMajor} (${this.provider}) but could not load its design system (is @tailwindcss/node installed?); classes cannot be resolved, so files are left unchanged to avoid unsafe edits.`
|
|
5333
|
+
);
|
|
5334
|
+
}
|
|
6024
5335
|
}
|
|
6025
5336
|
/** Engine-backed, cached single-token extraction. */
|
|
6026
5337
|
#extract(token) {
|
|
@@ -6129,9 +5440,75 @@ var TailwindResolver = class {
|
|
|
6129
5440
|
this.#reverseIndex = index;
|
|
6130
5441
|
return index;
|
|
6131
5442
|
}
|
|
5443
|
+
/**
|
|
5444
|
+
* The cover vocabulary: every base-condition, plain-subject utility mapped to the {@link tupleKey}s
|
|
5445
|
+
* of its full normalized-longhand declaration set. Built once from a SINGLE engine `generate` over
|
|
5446
|
+
* the enumerable class list (grouped by selector), so it is the same cost as {@link #buildReverseIndex}.
|
|
5447
|
+
* This is what the provider-uniform exact-cover engine searches; the element's own droppable tokens
|
|
5448
|
+
* are members of it, guaranteeing feasibility. Variant / combinator / pseudo utilities are excluded
|
|
5449
|
+
* (their effect is not the element's own base box), so a target carrying such conditions simply finds
|
|
5450
|
+
* no cover and falls back to the greedy emit.
|
|
5451
|
+
*/
|
|
5452
|
+
#buildCoverVocab() {
|
|
5453
|
+
if (this.#coverVocab) return this.#coverVocab;
|
|
5454
|
+
const baseCk = String(conditionKey(BASE_CONDITION));
|
|
5455
|
+
const out = [];
|
|
5456
|
+
if (this.#engine) {
|
|
5457
|
+
try {
|
|
5458
|
+
const classes = this.#engine.context.getClassList().filter((c) => typeof c === "string");
|
|
5459
|
+
const nodes = this.#engine.generate(classes);
|
|
5460
|
+
for (const node of nodes) {
|
|
5461
|
+
if (node.type !== "rule") continue;
|
|
5462
|
+
const rule = node;
|
|
5463
|
+
const parsed = parseSelector(rule.selector);
|
|
5464
|
+
if (parsed.kind !== "simple" || parsed.states.length > 0 || parsed.pseudoElement !== "") {
|
|
5465
|
+
continue;
|
|
5466
|
+
}
|
|
5467
|
+
const className = unescapeClass(rule.selector);
|
|
5468
|
+
if (className === null) continue;
|
|
5469
|
+
const tuples = [];
|
|
5470
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5471
|
+
for (const child of rule.nodes ?? []) {
|
|
5472
|
+
if (child.type !== "decl") continue;
|
|
5473
|
+
const d3 = child;
|
|
5474
|
+
if (typeof d3.value !== "string") continue;
|
|
5475
|
+
for (const decl of normalizer.normalizeDeclaration(d3.prop, d3.value, d3.important === true)) {
|
|
5476
|
+
const k3 = tupleKey(baseCk, String(decl.property), String(decl.value), decl.important);
|
|
5477
|
+
if (!seen.has(k3)) {
|
|
5478
|
+
seen.add(k3);
|
|
5479
|
+
tuples.push(k3);
|
|
5480
|
+
}
|
|
5481
|
+
}
|
|
5482
|
+
}
|
|
5483
|
+
if (tuples.length > 0) out.push({ token: className, tuples });
|
|
5484
|
+
}
|
|
5485
|
+
} catch {
|
|
5486
|
+
}
|
|
5487
|
+
}
|
|
5488
|
+
this.#coverVocab = out;
|
|
5489
|
+
return out;
|
|
5490
|
+
}
|
|
5491
|
+
/**
|
|
5492
|
+
* Try the minimal-string exact-cover engine over the WHOLE utility vocabulary. On success the chosen
|
|
5493
|
+
* set is verified by the mandatory CORRECTNESS BACKSTOP — re-resolve it and assert it reproduces the
|
|
5494
|
+
* target's tuples EXACTLY — before it is returned; any mismatch (or no cover / oversize universe)
|
|
5495
|
+
* yields `null` so {@link emit} uses its greedy fallback. Never returns a set that misrepresents `U`.
|
|
5496
|
+
*/
|
|
5497
|
+
#tryCover(normalized, norm) {
|
|
5498
|
+
const universe = styleMapTuples(normalized, norm);
|
|
5499
|
+
if (universe.length === 0) return { classes: [], exact: true, warnings: [] };
|
|
5500
|
+
const chosen = minStringCover(universe, this.#buildCoverVocab());
|
|
5501
|
+
if (!chosen || chosen.length === 0) return null;
|
|
5502
|
+
const reTuples = new Set(styleMapTuples(this.resolve({ classes: chosen }).styles, norm));
|
|
5503
|
+
if (reTuples.size !== universe.length) return null;
|
|
5504
|
+
for (const t of universe) if (!reTuples.has(t)) return null;
|
|
5505
|
+
return { classes: chosen, exact: true, warnings: [] };
|
|
5506
|
+
}
|
|
6132
5507
|
emit(styles, ctx) {
|
|
6133
5508
|
const norm = ctx.normalizer ?? normalizer;
|
|
6134
5509
|
const normalized = norm.normalizeStyleMap(styles);
|
|
5510
|
+
const cover = this.#tryCover(normalized, norm);
|
|
5511
|
+
if (cover) return cover;
|
|
6135
5512
|
const base = normalized.blocks.get(conditionKey(BASE_CONDITION));
|
|
6136
5513
|
if (!base || base.decls.size === 0) return { classes: [], exact: true, warnings: [] };
|
|
6137
5514
|
const hasNonBase = normalized.blocks.size > 1;
|
|
@@ -6161,13 +5538,13 @@ var TailwindResolver = class {
|
|
|
6161
5538
|
let bestCover = 0;
|
|
6162
5539
|
for (const entry of candidates) {
|
|
6163
5540
|
const [token, declMap] = entry;
|
|
6164
|
-
let
|
|
6165
|
-
for (const prop of declMap.keys()) if (remaining.has(prop))
|
|
6166
|
-
if (
|
|
6167
|
-
const better = best === null ||
|
|
5541
|
+
let cover2 = 0;
|
|
5542
|
+
for (const prop of declMap.keys()) if (remaining.has(prop)) cover2 += 1;
|
|
5543
|
+
if (cover2 === 0) continue;
|
|
5544
|
+
const better = best === null || cover2 > bestCover || cover2 === bestCover && declMap.size < best[1].size || cover2 === bestCover && declMap.size === best[1].size && token < best[0];
|
|
6168
5545
|
if (better) {
|
|
6169
5546
|
best = entry;
|
|
6170
|
-
bestCover =
|
|
5547
|
+
bestCover = cover2;
|
|
6171
5548
|
}
|
|
6172
5549
|
}
|
|
6173
5550
|
if (!best) break;
|
|
@@ -6207,13 +5584,13 @@ function createTailwindResolver(config) {
|
|
|
6207
5584
|
}
|
|
6208
5585
|
|
|
6209
5586
|
// ../cli/src/transform.ts
|
|
6210
|
-
var
|
|
5587
|
+
var path8 = __toESM(require("path"), 1);
|
|
6211
5588
|
|
|
6212
5589
|
// ../cli/src/html-css.ts
|
|
6213
5590
|
init_cjs_shims();
|
|
6214
5591
|
var import_node_crypto = require("crypto");
|
|
6215
|
-
var
|
|
6216
|
-
var
|
|
5592
|
+
var import_node_fs5 = require("fs");
|
|
5593
|
+
var path7 = __toESM(require("path"), 1);
|
|
6217
5594
|
function isRemoteHref(href) {
|
|
6218
5595
|
const h2 = href.trim();
|
|
6219
5596
|
return h2.startsWith("//") || /^[a-z][a-z0-9+.-]*:/i.test(h2);
|
|
@@ -6250,16 +5627,16 @@ function extractInlineStyles(html) {
|
|
|
6250
5627
|
return out;
|
|
6251
5628
|
}
|
|
6252
5629
|
function extractHtmlStylesheets(htmlCode, htmlAbsPath) {
|
|
6253
|
-
const dir =
|
|
5630
|
+
const dir = path7.dirname(path7.resolve(htmlAbsPath));
|
|
6254
5631
|
const files = [];
|
|
6255
5632
|
const seen = /* @__PURE__ */ new Set();
|
|
6256
5633
|
for (const rawHref of extractLinkHrefs(htmlCode)) {
|
|
6257
5634
|
const href = (rawHref.split(/[?#]/, 1)[0] ?? "").trim();
|
|
6258
5635
|
if (!href || isRemoteHref(href)) continue;
|
|
6259
|
-
const abs =
|
|
5636
|
+
const abs = path7.resolve(dir, href);
|
|
6260
5637
|
if (seen.has(abs)) continue;
|
|
6261
5638
|
seen.add(abs);
|
|
6262
|
-
if ((0,
|
|
5639
|
+
if ((0, import_node_fs5.existsSync)(abs)) files.push(abs);
|
|
6263
5640
|
}
|
|
6264
5641
|
return { files, inline: extractInlineStyles(htmlCode) };
|
|
6265
5642
|
}
|
|
@@ -6354,7 +5731,7 @@ function createTransform(options) {
|
|
|
6354
5731
|
if (options.provider !== "custom" || htmlKindOf(id) === null) return globalResolver;
|
|
6355
5732
|
const { files: localFiles, inline } = extractHtmlStylesheets(code, id);
|
|
6356
5733
|
if (localFiles.length === 0 && inline.length === 0) return globalResolver;
|
|
6357
|
-
const globalPaths = options.css.map((p2) =>
|
|
5734
|
+
const globalPaths = options.css.map((p2) => path8.resolve(p2));
|
|
6358
5735
|
const sortedPaths = [.../* @__PURE__ */ new Set([...globalPaths, ...localFiles])].sort();
|
|
6359
5736
|
const key = cssSetKey(sortedPaths, inline);
|
|
6360
5737
|
let resolver = resolverCache.get(key);
|
|
@@ -6466,9 +5843,9 @@ function createTransform(options) {
|
|
|
6466
5843
|
// ../cli/src/walk.ts
|
|
6467
5844
|
init_cjs_shims();
|
|
6468
5845
|
var fs = __toESM(require("fs"), 1);
|
|
6469
|
-
var
|
|
5846
|
+
var path9 = __toESM(require("path"), 1);
|
|
6470
5847
|
var SUPPORTED_EXTS = [".jsx", ".tsx", ".html", ".htm"];
|
|
6471
|
-
var
|
|
5848
|
+
var SKIP_DIRS2 = /* @__PURE__ */ new Set(["node_modules", ".git", "domflax-out"]);
|
|
6472
5849
|
function isSupported(file) {
|
|
6473
5850
|
const lower = file.toLowerCase();
|
|
6474
5851
|
return SUPPORTED_EXTS.some((ext) => lower.endsWith(ext));
|
|
@@ -6484,9 +5861,9 @@ function walkDir(dir, out) {
|
|
|
6484
5861
|
return;
|
|
6485
5862
|
}
|
|
6486
5863
|
for (const entry of entries) {
|
|
6487
|
-
const full =
|
|
5864
|
+
const full = path9.join(dir, entry.name);
|
|
6488
5865
|
if (entry.isDirectory()) {
|
|
6489
|
-
if (
|
|
5866
|
+
if (SKIP_DIRS2.has(entry.name)) continue;
|
|
6490
5867
|
walkDir(full, out);
|
|
6491
5868
|
} else if (entry.isFile()) {
|
|
6492
5869
|
if (isSupported(entry.name)) out.push(full);
|
|
@@ -6502,7 +5879,7 @@ function discoverInputs(paths) {
|
|
|
6502
5879
|
const warnings = [];
|
|
6503
5880
|
const seen = /* @__PURE__ */ new Set();
|
|
6504
5881
|
const push = (f) => {
|
|
6505
|
-
const abs =
|
|
5882
|
+
const abs = path9.resolve(f);
|
|
6506
5883
|
if (!seen.has(abs)) {
|
|
6507
5884
|
seen.add(abs);
|
|
6508
5885
|
files.push(abs);
|
|
@@ -6511,7 +5888,7 @@ function discoverInputs(paths) {
|
|
|
6511
5888
|
let inputRoot = process.cwd();
|
|
6512
5889
|
if (paths.length === 1) {
|
|
6513
5890
|
try {
|
|
6514
|
-
if (fs.statSync(paths[0]).isDirectory()) inputRoot =
|
|
5891
|
+
if (fs.statSync(paths[0]).isDirectory()) inputRoot = path9.resolve(paths[0]);
|
|
6515
5892
|
} catch {
|
|
6516
5893
|
}
|
|
6517
5894
|
}
|
|
@@ -6523,7 +5900,7 @@ function discoverInputs(paths) {
|
|
|
6523
5900
|
stat = null;
|
|
6524
5901
|
}
|
|
6525
5902
|
if (stat?.isDirectory()) {
|
|
6526
|
-
walkDir(
|
|
5903
|
+
walkDir(path9.resolve(p2), files);
|
|
6527
5904
|
continue;
|
|
6528
5905
|
}
|
|
6529
5906
|
if (stat?.isFile()) {
|
|
@@ -6548,7 +5925,7 @@ function discoverInputs(paths) {
|
|
|
6548
5925
|
const deduped = [];
|
|
6549
5926
|
const finalSeen = /* @__PURE__ */ new Set();
|
|
6550
5927
|
for (const f of files) {
|
|
6551
|
-
const abs =
|
|
5928
|
+
const abs = path9.resolve(f);
|
|
6552
5929
|
if (!finalSeen.has(abs)) {
|
|
6553
5930
|
finalSeen.add(abs);
|
|
6554
5931
|
deduped.push(abs);
|
|
@@ -7141,8 +6518,8 @@ var J2 = `${import_picocolors2.default.gray(o)} `;
|
|
|
7141
6518
|
// ../cli/src/detect.ts
|
|
7142
6519
|
init_cjs_shims();
|
|
7143
6520
|
var fs2 = __toESM(require("fs"), 1);
|
|
7144
|
-
var
|
|
7145
|
-
var
|
|
6521
|
+
var path10 = __toESM(require("path"), 1);
|
|
6522
|
+
var SKIP_DIRS3 = /* @__PURE__ */ new Set([
|
|
7146
6523
|
"node_modules",
|
|
7147
6524
|
"dist",
|
|
7148
6525
|
"build",
|
|
@@ -7156,11 +6533,11 @@ var COMMON_INPUT_DIRS = ["src", "app", "components", "pages", "lib", "ui", "publ
|
|
|
7156
6533
|
var CSS_FILE_CAP = 200;
|
|
7157
6534
|
var DEFAULT_CSS_DEPTH = 10;
|
|
7158
6535
|
function toRelative(root, abs) {
|
|
7159
|
-
const rel =
|
|
7160
|
-
return rel.split(
|
|
6536
|
+
const rel = path10.relative(root, abs);
|
|
6537
|
+
return rel.split(path10.sep).join("/");
|
|
7161
6538
|
}
|
|
7162
6539
|
function detectCssFiles(root, scanRoots = [], maxDepth = DEFAULT_CSS_DEPTH) {
|
|
7163
|
-
const base =
|
|
6540
|
+
const base = path10.resolve(root);
|
|
7164
6541
|
const found = /* @__PURE__ */ new Map();
|
|
7165
6542
|
let capped = false;
|
|
7166
6543
|
const walk = (dir, depth) => {
|
|
@@ -7172,13 +6549,13 @@ function detectCssFiles(root, scanRoots = [], maxDepth = DEFAULT_CSS_DEPTH) {
|
|
|
7172
6549
|
return;
|
|
7173
6550
|
}
|
|
7174
6551
|
for (const entry of entries) {
|
|
7175
|
-
const full =
|
|
6552
|
+
const full = path10.join(dir, entry.name);
|
|
7176
6553
|
if (entry.isDirectory()) {
|
|
7177
|
-
if (
|
|
6554
|
+
if (SKIP_DIRS3.has(entry.name)) continue;
|
|
7178
6555
|
walk(full, depth + 1);
|
|
7179
6556
|
if (capped) return;
|
|
7180
6557
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".css")) {
|
|
7181
|
-
const abs =
|
|
6558
|
+
const abs = path10.resolve(full);
|
|
7182
6559
|
if (!found.has(abs)) {
|
|
7183
6560
|
found.set(abs, toRelative(base, abs));
|
|
7184
6561
|
if (found.size >= CSS_FILE_CAP) {
|
|
@@ -7192,7 +6569,7 @@ function detectCssFiles(root, scanRoots = [], maxDepth = DEFAULT_CSS_DEPTH) {
|
|
|
7192
6569
|
walk(base, 0);
|
|
7193
6570
|
for (const r2 of scanRoots) {
|
|
7194
6571
|
if (capped) break;
|
|
7195
|
-
const abs =
|
|
6572
|
+
const abs = path10.resolve(r2);
|
|
7196
6573
|
if (abs !== base) walk(abs, 0);
|
|
7197
6574
|
}
|
|
7198
6575
|
const list = [...found.values()].sort((a, b3) => a.localeCompare(b3));
|
|
@@ -7202,10 +6579,10 @@ function detectCssFiles(root, scanRoots = [], maxDepth = DEFAULT_CSS_DEPTH) {
|
|
|
7202
6579
|
return list;
|
|
7203
6580
|
}
|
|
7204
6581
|
function detectInputDirs(root) {
|
|
7205
|
-
const resolved =
|
|
6582
|
+
const resolved = path10.resolve(root);
|
|
7206
6583
|
return COMMON_INPUT_DIRS.filter((name) => {
|
|
7207
6584
|
try {
|
|
7208
|
-
return fs2.statSync(
|
|
6585
|
+
return fs2.statSync(path10.join(resolved, name)).isDirectory();
|
|
7209
6586
|
} catch {
|
|
7210
6587
|
return false;
|
|
7211
6588
|
}
|
|
@@ -7331,6 +6708,9 @@ async function runWizard(base) {
|
|
|
7331
6708
|
}
|
|
7332
6709
|
|
|
7333
6710
|
// ../cli/src/index.ts
|
|
6711
|
+
function fileDetail(stats) {
|
|
6712
|
+
return `${stats.nodesRemoved} nodes, ${stats.classesSaved} classes, ${stats.bytesSaved} bytes`;
|
|
6713
|
+
}
|
|
7334
6714
|
function printReport(totals) {
|
|
7335
6715
|
console.log("");
|
|
7336
6716
|
console.log("domflax report");
|
|
@@ -7346,7 +6726,7 @@ function runInline(files, options, inputRoot, plan, totals) {
|
|
|
7346
6726
|
for (const file of files) {
|
|
7347
6727
|
let code;
|
|
7348
6728
|
try {
|
|
7349
|
-
code = (0,
|
|
6729
|
+
code = (0, import_node_fs6.readFileSync)(file, "utf8");
|
|
7350
6730
|
} catch (err) {
|
|
7351
6731
|
console.error(`domflax: cannot read ${file}: ${String(err?.message ?? err)}`);
|
|
7352
6732
|
failures += 1;
|
|
@@ -7355,9 +6735,14 @@ function runInline(files, options, inputRoot, plan, totals) {
|
|
|
7355
6735
|
const result = transform.transformFile(code, file);
|
|
7356
6736
|
addStats(totals, result.stats, result.changed);
|
|
7357
6737
|
if (options.dryRun) {
|
|
7358
|
-
const rel =
|
|
7359
|
-
if (result.changed)
|
|
7360
|
-
|
|
6738
|
+
const rel = path11.relative(inputRoot, file) || path11.basename(file);
|
|
6739
|
+
if (result.changed) {
|
|
6740
|
+
console.log(
|
|
6741
|
+
options.details ? `domflax: ${rel} \u2014 ${fileDetail(result.stats)}` : unifiedDiff(code, result.code, rel)
|
|
6742
|
+
);
|
|
6743
|
+
} else if (!options.report && !options.details) {
|
|
6744
|
+
console.log(` (unchanged) ${rel}`);
|
|
6745
|
+
}
|
|
7361
6746
|
continue;
|
|
7362
6747
|
}
|
|
7363
6748
|
if (!result.changed) continue;
|
|
@@ -7368,9 +6753,10 @@ function runInline(files, options, inputRoot, plan, totals) {
|
|
|
7368
6753
|
continue;
|
|
7369
6754
|
}
|
|
7370
6755
|
try {
|
|
7371
|
-
(0,
|
|
7372
|
-
(0,
|
|
7373
|
-
|
|
6756
|
+
(0, import_node_fs6.mkdirSync)(path11.dirname(target.value), { recursive: true });
|
|
6757
|
+
(0, import_node_fs6.writeFileSync)(target.value, result.code, "utf8");
|
|
6758
|
+
const rel = path11.relative(process.cwd(), target.value) || target.value;
|
|
6759
|
+
console.log(options.details ? `domflax: wrote ${rel} \u2014 ${fileDetail(result.stats)}` : `domflax: wrote ${rel}`);
|
|
7374
6760
|
} catch (err) {
|
|
7375
6761
|
console.error(`domflax: cannot write ${target.value}: ${String(err?.message ?? err)}`);
|
|
7376
6762
|
failures += 1;
|
|
@@ -7407,10 +6793,16 @@ async function execute(options) {
|
|
|
7407
6793
|
Object.assign(totals, outcome.totals);
|
|
7408
6794
|
failures = outcome.failures;
|
|
7409
6795
|
for (const { path: p2, error } of outcome.errors) {
|
|
7410
|
-
console.error(`domflax: failed ${
|
|
6796
|
+
console.error(`domflax: failed ${path11.relative(process.cwd(), p2) || p2}: ${error}`);
|
|
7411
6797
|
}
|
|
7412
|
-
|
|
7413
|
-
|
|
6798
|
+
if (options.details) {
|
|
6799
|
+
for (const { dest, stats } of [...outcome.changedFiles].sort((a, b3) => a.dest.localeCompare(b3.dest))) {
|
|
6800
|
+
console.log(`domflax: wrote ${path11.relative(process.cwd(), dest) || dest} \u2014 ${fileDetail(stats)}`);
|
|
6801
|
+
}
|
|
6802
|
+
} else {
|
|
6803
|
+
for (const dest of [...outcome.wrote].sort()) {
|
|
6804
|
+
console.log(`domflax: wrote ${path11.relative(process.cwd(), dest) || dest}`);
|
|
6805
|
+
}
|
|
7414
6806
|
}
|
|
7415
6807
|
} else {
|
|
7416
6808
|
failures += runInline(files, options, inputRoot, plan, totals);
|