domflax 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +159 -0
  2. package/dist/{chunk-4HHISSMR.js → chunk-DNHOGPYV.js} +2675 -1503
  3. package/dist/chunk-DNHOGPYV.js.map +1 -0
  4. package/dist/{chunk-ZJ2S36GY.js → chunk-DOQEBGWB.js} +33 -20
  5. package/dist/chunk-DOQEBGWB.js.map +1 -0
  6. package/dist/{chunk-77SLHRN6.js → chunk-DWLB7FRR.js} +341 -176
  7. package/dist/chunk-DWLB7FRR.js.map +1 -0
  8. package/dist/cli.cjs +2169 -760
  9. package/dist/cli.cjs.map +1 -1
  10. package/dist/cli.js +183 -91
  11. package/dist/cli.js.map +1 -1
  12. package/dist/index.cjs +3021 -1699
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +477 -54
  15. package/dist/index.d.ts +477 -54
  16. package/dist/index.js +49 -3
  17. package/dist/pattern-CV607P87.d.ts +547 -0
  18. package/dist/pattern-F5xBtIE-.d.cts +547 -0
  19. package/dist/pattern-kit.cjs +60 -39
  20. package/dist/pattern-kit.cjs.map +1 -1
  21. package/dist/pattern-kit.d.cts +3 -18
  22. package/dist/pattern-kit.d.ts +3 -18
  23. package/dist/pattern-kit.js +3 -1
  24. package/dist/pattern-kit.js.map +1 -1
  25. package/dist/{types-BQ7l6dVe.d.ts → resolve-ops-DIwEelH-.d.cts} +26 -251
  26. package/dist/{types-BQ7l6dVe.d.cts → resolve-ops-DIwEelH-.d.ts} +26 -251
  27. package/dist/verify.d.cts +1 -1
  28. package/dist/verify.d.ts +1 -1
  29. package/dist/webpack-loader.cjs +2975 -1699
  30. package/dist/webpack-loader.cjs.map +1 -1
  31. package/dist/webpack-loader.d.cts +2 -2
  32. package/dist/webpack-loader.d.ts +2 -2
  33. package/dist/webpack-loader.js +3 -3
  34. package/package.json +3 -6
  35. package/dist/chunk-4HHISSMR.js.map +0 -1
  36. package/dist/chunk-77SLHRN6.js.map +0 -1
  37. package/dist/chunk-ZJ2S36GY.js.map +0 -1
  38. package/dist/pattern-CX6iBzTD.d.ts +0 -237
  39. package/dist/pattern-P4FIKAUB.d.cts +0 -237
@@ -10,46 +10,108 @@ import {
10
10
  createFragment,
11
11
  createText,
12
12
  defaultMeta,
13
+ definePattern,
13
14
  emptyClassList,
14
15
  emptyStyleMap,
15
- hasDynamicChildren,
16
16
  hasDynamicClasses,
17
- hasEventHandlers,
18
- hasRef,
19
17
  isElement,
20
18
  normalizer,
21
19
  not,
22
- pattern,
23
20
  targetedByCombinator
24
- } from "./chunk-77SLHRN6.js";
21
+ } from "./chunk-DWLB7FRR.js";
25
22
  import {
26
23
  __filename,
27
24
  init_esm_shims
28
25
  } from "./chunk-6WVVF6AD.js";
29
26
 
30
- // ../patterns/src/flatten/empty-style-div.pattern.ts
27
+ // ../patterns/src/library/flatten/display-contents-wrapper.pattern.ts
31
28
  init_esm_shims();
32
29
  function asEl(node) {
33
30
  const n = node;
34
31
  return n.kind === "element" ? n : null;
35
32
  }
33
+ function metaOf(node) {
34
+ return asEl(node)?.meta ?? null;
35
+ }
36
+ var declaresCustomProperties = (node) => metaOf(node)?.declaresCustomProperties ?? false;
37
+ var hasSpreadAttrs = (node) => metaOf(node)?.hasSpreadAttrs ?? false;
38
+ var isComponentNode = (node) => metaOf(node)?.isComponent ?? false;
39
+ var hasOwnAttrs = (node) => {
40
+ const el = asEl(node);
41
+ if (!el) return false;
42
+ return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
43
+ };
44
+ var targetedByStructuralPseudo = (node, ctx) => {
45
+ const el = asEl(node);
46
+ if (!el) return false;
47
+ if (el.meta.targetedByStructuralPseudo) return true;
48
+ return ctx.selectors.targetedByStructuralPseudo(el.id);
49
+ };
50
+ var displayContentsWrapper = definePattern({
51
+ name: "display-contents-wrapper",
52
+ category: "flatten/display-contents-wrapper",
53
+ safety: 2,
54
+ doc: {
55
+ title: "Flatten display:contents wrapper",
56
+ summary: "A div with display:contents (which generates no box) wrapping a single element child, with no own visual style, no attributes beyond an inert class, and no opacity barriers, is removed; its sole child is hoisted in its place.",
57
+ before: '<div style="display:contents"><Child/></div>',
58
+ after: "<Child/>",
59
+ safetyRationale: "A display:contents element generates no box at all, so its children already render as direct children of its parent; removing it is layout-identical. It paints nothing, establishes no formatting/stacking/box context, is no containing block, carries no ref/handlers/dynamic-children/html/spread/component identity, owns no targetable attrs / custom-property coupling, and is not a combinator/structural-pseudo subject; inheritable styles are folded onto the child before removal."
60
+ },
61
+ match: {
62
+ tag: "div",
63
+ style: { display: "contents" },
64
+ onlyChild: "element",
65
+ paintsNothing: true,
66
+ where: [
67
+ not(declaresCustomProperties),
68
+ not(hasOwnAttrs),
69
+ not(hasDynamicClasses),
70
+ not(hasSpreadAttrs),
71
+ not(isComponentNode),
72
+ not(targetedByStructuralPseudo)
73
+ ]
74
+ },
75
+ rewrite: { flattenInto: "child" },
76
+ test: {
77
+ cases: [
78
+ {
79
+ // `display:contents` generates no box, so removing it is provably layout-identical → the
80
+ // wrapper is flattened even under the conservative gate; the child is hoisted.
81
+ before: '<div className="contents"><a className="text-blue-500">Link</a></div>',
82
+ after: '<a className="text-blue-500">Link</a>'
83
+ }
84
+ ],
85
+ noMatch: [
86
+ // A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
87
+ '<div className="contents" ref={rootRef}><a className="text-blue-500">Link</a></div>'
88
+ ]
89
+ }
90
+ });
91
+
92
+ // ../patterns/src/library/flatten/empty-style-div.pattern.ts
93
+ init_esm_shims();
94
+ function asEl2(node) {
95
+ const n = node;
96
+ return n.kind === "element" ? n : null;
97
+ }
36
98
  function metaFlag(flag) {
37
- return (node) => Boolean(asEl(node)?.meta[flag]);
99
+ return (node) => Boolean(asEl2(node)?.meta[flag]);
38
100
  }
39
101
  var establishesBox = metaFlag("establishesBox");
40
102
  var establishesFormattingContext = metaFlag("establishesFormattingContext");
41
103
  var establishesStackingContext = metaFlag("establishesStackingContext");
42
104
  var isContainingBlock = metaFlag("isContainingBlock");
43
- var declaresCustomProperties = metaFlag("declaresCustomProperties");
44
- var targetedByStructuralPseudo = (node, ctx) => {
45
- const el = asEl(node);
105
+ var declaresCustomProperties2 = metaFlag("declaresCustomProperties");
106
+ var targetedByStructuralPseudo2 = (node, ctx) => {
107
+ const el = asEl2(node);
46
108
  if (!el) return false;
47
109
  if (el.meta.targetedByStructuralPseudo) return true;
48
110
  return ctx.selectors.targetedByStructuralPseudo(el.id);
49
111
  };
50
112
  var DISPLAY = "display";
51
113
  var hasNonBlockDisplay = (node, ctx) => {
52
- const el = asEl(node);
114
+ const el = asEl2(node);
53
115
  if (!el) return false;
54
116
  const sm = ctx.computedOf(el) ?? el.computed;
55
117
  for (const block of sm.blocks.values()) {
@@ -58,7 +120,7 @@ var hasNonBlockDisplay = (node, ctx) => {
58
120
  }
59
121
  return false;
60
122
  };
61
- var emptyStyleDiv = pattern({
123
+ var emptyStyleDiv = definePattern({
62
124
  name: "empty-style-div",
63
125
  category: "flatten/empty-style-div",
64
126
  safety: 1,
@@ -79,26 +141,29 @@ var emptyStyleDiv = pattern({
79
141
  not(establishesFormattingContext),
80
142
  not(establishesStackingContext),
81
143
  not(isContainingBlock),
82
- not(declaresCustomProperties),
83
- not(targetedByStructuralPseudo)
144
+ not(declaresCustomProperties2),
145
+ not(targetedByStructuralPseudo2)
84
146
  ]
85
147
  },
86
148
  rewrite: { flattenInto: "child" },
87
- examples: [
88
- {
89
- before: '<div><span className="bg-red-200">Hi</span></div>',
90
- after: '<span className="bg-red-200">Hi</span>'
91
- },
92
- {
149
+ test: {
150
+ cases: [
151
+ {
152
+ // A layout-neutral, style-free block div is a provably-safe flatten → removed, child hoisted.
153
+ before: '<div><span className="bg-red-200">Hi</span></div>',
154
+ after: '<span className="bg-red-200">Hi</span>'
155
+ }
156
+ ],
157
+ noMatch: [
93
158
  // The wrapper paints its own background (own visual style) → not layout-neutral, kept.
94
- noMatch: '<div className="bg-blue-500"><span className="bg-red-200">Hi</span></div>'
95
- }
96
- ]
159
+ '<div className="bg-blue-500"><span className="bg-red-200">Hi</span></div>'
160
+ ]
161
+ }
97
162
  });
98
163
 
99
- // ../patterns/src/flatten/flex-center-wrapper.pattern.ts
164
+ // ../patterns/src/library/flatten/flex-center-wrapper.pattern.ts
100
165
  init_esm_shims();
101
- var flexCenterWrapper = pattern({
166
+ var flexCenterWrapper = definePattern({
102
167
  name: "flex-center-wrapper",
103
168
  category: "flatten/flex-center-wrapper",
104
169
  safety: 2,
@@ -119,21 +184,65 @@ var flexCenterWrapper = pattern({
119
184
  flattenInto: "child",
120
185
  childGains: { placeSelf: "center" }
121
186
  },
122
- examples: [
123
- {
124
- // The wrapper is removed; the surviving child gains `place-self-center` (reverse-emitted
125
- // from the folded computed style by the resolver).
126
- before: '<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
127
- after: '<div className="bg-red-200 place-self-center">Hello</div>'
128
- },
129
- {
130
- // onClick is a hard opacity barrier the wrapper is load-bearing, no flatten.
131
- noMatch: '<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
132
- }
133
- ]
187
+ // Collapsing a flex-centering wrapper to `place-self:center` on the child only stays centered when
188
+ // the child's NEW parent is flex/grid; moreover the wrapper's own `display:flex` establishes a
189
+ // formatting context. Both make this a `needs-verification` flatten, which the conservative
190
+ // production gate (`'provably-safe'`, used by the harness) intentionally REVERTS — so every case
191
+ // here is a no-match: the wrapper is preserved. Op-level rewrite correctness (purity, id-preserving
192
+ // unwrap, opacity-barrier safety) is still asserted by the invariant suite over every pattern.
193
+ test: {
194
+ noMatch: [
195
+ // Even under a static flex/grid parent the centering flatten is not provably layout-neutral
196
+ // (the wrapper itself establishes a flex formatting context) left unchanged.
197
+ '<div className="grid"><div className="flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
198
+ // Non-flex/grid parent (document root): place-self centering would not hold → left unchanged.
199
+ '<div className="flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
200
+ // onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
201
+ '<div className="flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
202
+ ]
203
+ }
204
+ });
205
+
206
+ // ../patterns/src/library/flatten/inline-flex-center-wrapper.pattern.ts
207
+ init_esm_shims();
208
+ var inlineFlexCenterWrapper = definePattern({
209
+ name: "inline-flex-center-wrapper",
210
+ category: "flatten/inline-flex-center-wrapper",
211
+ safety: 2,
212
+ doc: {
213
+ title: "Flatten inline-flex-centering wrapper",
214
+ summary: "A div that only centers a single child (display:inline-flex; align-items:center; justify-content:center) is removed; the child gains place-self:center.",
215
+ before: '<div style="display:inline-flex;align-items:center;justify-content:center"><Child/></div>',
216
+ after: '<Child style="place-self:center"/>',
217
+ 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."
218
+ },
219
+ match: {
220
+ tag: "div",
221
+ style: { display: "inline-flex", alignItems: "center", justifyContent: "center" },
222
+ onlyChild: "element",
223
+ paintsNothing: true
224
+ },
225
+ rewrite: {
226
+ flattenInto: "child",
227
+ childGains: { placeSelf: "center" }
228
+ },
229
+ // Like its block-level sibling, this centering flatten is `needs-verification` (the wrapper's own
230
+ // `display:inline-flex` establishes a formatting context, and place-self centering only holds under
231
+ // a flex/grid parent), so the conservative production gate (`'provably-safe'`) REVERTS it — every
232
+ // case here is a no-match. Op-level correctness is covered by the invariant suite.
233
+ test: {
234
+ noMatch: [
235
+ // Even under a static flex/grid parent the centering flatten is not provably layout-neutral.
236
+ '<div className="grid"><div className="inline-flex items-center justify-center"><span className="bg-red-200">x</span></div></div>',
237
+ // Non-flex/grid parent (document root) → left unchanged.
238
+ '<div className="inline-flex justify-center items-center"><div className="bg-red-200">Hello</div></div>',
239
+ // onClick is a hard opacity barrier → the wrapper is load-bearing regardless of the gate.
240
+ '<div className="inline-flex justify-center items-center" onClick={handleClick}><div className="bg-red-200">Hello</div></div>'
241
+ ]
242
+ }
134
243
  });
135
244
 
136
- // ../patterns/src/flatten/nested-flex-merge.pattern.ts
245
+ // ../patterns/src/library/flatten/nested-flex-merge.pattern.ts
137
246
  init_esm_shims();
138
247
  function baseConditionStyleMap(decls) {
139
248
  const map = /* @__PURE__ */ new Map();
@@ -200,7 +309,7 @@ var isInnerFlex = and(
200
309
  computed(DISPLAY_FLEX),
201
310
  not(targetedByCombinator)
202
311
  );
203
- var nestedFlexMerge = pattern({
312
+ var nestedFlexMerge = definePattern({
204
313
  name: "nested-flex-merge",
205
314
  category: "flatten/nested-flex-merge",
206
315
  safety: 2,
@@ -236,24 +345,148 @@ var nestedFlexMerge = pattern({
236
345
  rw.unwrap(outer)
237
346
  ];
238
347
  },
239
- examples: [
240
- {
241
- // The wrapper's flex declarations (align-items / gap) merge onto the inner flex container,
242
- // then the wrapper is removed (its own `data-x` here just blocks the more aggressive
243
- // passthrough-wrapper so this merge is the one that fires).
244
- before: '<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
245
- after: '<div className="flex flex-col gap-2 items-center">X</div>'
246
- },
247
- {
248
- // A non-flex wrapper does not match the flex-container signature → left unchanged.
249
- noMatch: '<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
348
+ // Merging the outer flex container into the inner removes the outer's box, but a `display:flex`
349
+ // wrapper establishes a formatting context, so this is a `needs-verification` flatten that the
350
+ // conservative production gate (`'provably-safe'`) REVERTS every case here is a no-match. The
351
+ // merge's op-level correctness (purity, id-preserving unwrap, opacity-barrier safety) is asserted
352
+ // by the invariant suite over every pattern.
353
+ test: {
354
+ noMatch: [
355
+ // The merge is real but not provably layout-neutral (the wrapper establishes a flex context),
356
+ // so under the conservative gate the nested containers are left in place.
357
+ '<div className="flex items-center gap-2" data-x="1"><div className="flex flex-col">X</div></div>',
358
+ // A non-flex wrapper does not match the flex-container signature → left unchanged anyway.
359
+ '<div className="block bg-blue-500"><div className="flex flex-col">X</div></div>'
360
+ ]
361
+ }
362
+ });
363
+
364
+ // ../patterns/src/library/flatten/nested-grid-merge.pattern.ts
365
+ init_esm_shims();
366
+ function baseConditionStyleMap2(decls) {
367
+ const map = /* @__PURE__ */ new Map();
368
+ for (const [prop, value] of decls) {
369
+ for (const decl of normalizer.normalizeDeclaration(prop, value, false)) {
370
+ map.set(decl.property, decl);
371
+ }
372
+ }
373
+ const block = { condition: BASE_CONDITION, decls: map };
374
+ const blocks = /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]);
375
+ return { blocks };
376
+ }
377
+ var DISPLAY_GRID = baseConditionStyleMap2([["display", "grid"]]);
378
+ var GRID_CONTAINER_PROPERTIES = /* @__PURE__ */ new Set([
379
+ "display",
380
+ "grid-template-columns",
381
+ "grid-template-rows",
382
+ "grid-template-areas",
383
+ "grid-auto-columns",
384
+ "grid-auto-rows",
385
+ "grid-auto-flow",
386
+ "justify-content",
387
+ "align-content",
388
+ "place-content",
389
+ "justify-items",
390
+ "align-items",
391
+ "place-items",
392
+ "row-gap",
393
+ "column-gap"
394
+ ]);
395
+ function outerMergeSafe2(sm) {
396
+ const norm = normalizer.normalizeStyleMap(sm);
397
+ for (const block of norm.blocks.values()) {
398
+ for (const decl of block.decls.values()) {
399
+ if (GRID_CONTAINER_PROPERTIES.has(String(decl.property))) continue;
400
+ if (decl.inherited) continue;
401
+ return false;
402
+ }
403
+ }
404
+ return true;
405
+ }
406
+ function gridConflict(outer, inner) {
407
+ const a = normalizer.normalizeStyleMap(outer);
408
+ const b = normalizer.normalizeStyleMap(inner);
409
+ for (const [key, blockA] of a.blocks) {
410
+ const blockB = b.blocks.get(key);
411
+ if (!blockB) continue;
412
+ for (const [prop, declA] of blockA.decls) {
413
+ if (!GRID_CONTAINER_PROPERTIES.has(String(prop))) continue;
414
+ const declB = blockB.decls.get(prop);
415
+ if (declB && declB.value !== declA.value) return true;
416
+ }
417
+ }
418
+ return false;
419
+ }
420
+ function extractGridStyle(sm) {
421
+ const blocks = /* @__PURE__ */ new Map();
422
+ for (const [key, block] of sm.blocks) {
423
+ const decls = /* @__PURE__ */ new Map();
424
+ for (const [prop, decl] of block.decls) {
425
+ if (GRID_CONTAINER_PROPERTIES.has(String(prop))) decls.set(prop, decl);
250
426
  }
251
- ]
427
+ if (decls.size > 0) blocks.set(key, { condition: block.condition, decls });
428
+ }
429
+ return { blocks };
430
+ }
431
+ var isInnerGrid = and(
432
+ isElement("div"),
433
+ computed(DISPLAY_GRID),
434
+ not(targetedByCombinator)
435
+ );
436
+ var nestedGridMerge = definePattern({
437
+ name: "nested-grid-merge",
438
+ category: "flatten/nested-grid-merge",
439
+ safety: 2,
440
+ doc: {
441
+ title: "Merge nested grid containers",
442
+ 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.",
443
+ before: '<div style="display:grid;gap:8px"><div style="display:grid;grid-template-columns:1fr 1fr"/></div>',
444
+ after: '<div style="display:grid;grid-template-columns:1fr 1fr;gap:8px"/>',
445
+ 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."
446
+ },
447
+ match: {
448
+ tag: "div",
449
+ style: { display: "grid" },
450
+ onlyChild: "element",
451
+ paintsNothing: true
452
+ },
453
+ rewrite: (ctx, rw) => {
454
+ const outer = ctx.node;
455
+ const inner = ctx.onlyElementChild();
456
+ if (!inner) return null;
457
+ if (!isInnerGrid(inner, ctx)) return null;
458
+ const outerStyle = ctx.computed();
459
+ const innerStyle = ctx.computedOf(inner);
460
+ if (!outerMergeSafe2(outerStyle)) return null;
461
+ if (gridConflict(outerStyle, innerStyle)) return null;
462
+ return [
463
+ // 1. Preserve inheritable values (color/font/…) by folding them onto the child first.
464
+ rw.foldInheritedStyles(outer, inner, { conditions: "all" }),
465
+ // 2. Transfer the wrapper's grid-container declarations onto the child (target-wins keeps the
466
+ // child's value for any shared property — identical anyway, we proved non-conflict).
467
+ rw.mergeStyle(inner, null, extractGridStyle(outerStyle), "target-wins"),
468
+ // 3. Remove the wrapper (structural-safe; hoists the child and preserves its IRNodeId).
469
+ rw.unwrap(outer)
470
+ ];
471
+ },
472
+ // Like its flex sibling, this merge removes the outer container's box, but a `display:grid` wrapper
473
+ // establishes a formatting context, so it is a `needs-verification` flatten that the conservative
474
+ // production gate (`'provably-safe'`) REVERTS — every case here is a no-match. Op-level correctness
475
+ // is asserted by the invariant suite over every pattern.
476
+ test: {
477
+ noMatch: [
478
+ // The merge is real but not provably layout-neutral (the wrapper establishes a grid context),
479
+ // so under the conservative gate the nested containers are left in place.
480
+ '<div className="grid gap-2" data-x="1"><div className="grid grid-cols-2">X</div></div>',
481
+ // A non-grid wrapper does not match the grid-container signature → left unchanged anyway.
482
+ '<div className="block bg-blue-500"><div className="grid grid-cols-2">X</div></div>'
483
+ ]
484
+ }
252
485
  });
253
486
 
254
- // ../patterns/src/flatten/passthrough-wrapper.pattern.ts
487
+ // ../patterns/src/library/flatten/passthrough-wrapper.pattern.ts
255
488
  init_esm_shims();
256
- function metaOf(node) {
489
+ function metaOf2(node) {
257
490
  const n = node;
258
491
  return n.kind === "element" ? n.meta : null;
259
492
  }
@@ -262,24 +495,24 @@ function elementOf(node) {
262
495
  return n.kind === "element" ? n : null;
263
496
  }
264
497
  var establishesContext = (node) => {
265
- const m = metaOf(node);
498
+ const m = metaOf2(node);
266
499
  if (!m) return false;
267
500
  return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
268
501
  };
269
- var hasSpreadAttrs = (node) => metaOf(node)?.hasSpreadAttrs ?? false;
270
- var isComponentNode = (node) => metaOf(node)?.isComponent ?? false;
271
- var hasOwnAttrs = (node) => {
502
+ var hasSpreadAttrs2 = (node) => metaOf2(node)?.hasSpreadAttrs ?? false;
503
+ var isComponentNode2 = (node) => metaOf2(node)?.isComponent ?? false;
504
+ var hasOwnAttrs2 = (node) => {
272
505
  const el = elementOf(node);
273
506
  if (!el) return false;
274
507
  return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
275
508
  };
276
- var targetedByStructuralPseudo2 = (node, ctx) => {
509
+ var targetedByStructuralPseudo3 = (node, ctx) => {
277
510
  const el = elementOf(node);
278
511
  if (!el) return false;
279
512
  if (el.meta.targetedByStructuralPseudo) return true;
280
513
  return ctx.selectors.targetedByStructuralPseudo(el.id);
281
514
  };
282
- var passthroughWrapper = pattern({
515
+ var passthroughWrapper = definePattern({
283
516
  name: "passthrough-wrapper",
284
517
  category: "flatten/passthrough-wrapper",
285
518
  safety: 2,
@@ -296,27 +529,34 @@ var passthroughWrapper = pattern({
296
529
  paintsNothing: true,
297
530
  where: [
298
531
  not(establishesContext),
299
- not(hasOwnAttrs),
532
+ not(hasOwnAttrs2),
300
533
  not(hasDynamicClasses),
301
- not(hasSpreadAttrs),
302
- not(isComponentNode),
303
- not(targetedByStructuralPseudo2)
534
+ not(hasSpreadAttrs2),
535
+ not(isComponentNode2),
536
+ not(targetedByStructuralPseudo3)
304
537
  ]
305
538
  },
306
539
  rewrite: { flattenInto: "child" },
307
- examples: [
308
- {
309
- before: '<div className="flex"><a className="bg-red-200">Link</a></div>',
310
- after: '<a className="bg-red-200">Link</a>'
311
- },
312
- {
540
+ test: {
541
+ cases: [
542
+ {
543
+ // A plain, style-free wrapper paints nothing and establishes no context → a provably-safe
544
+ // flatten under the conservative gate: the wrapper is removed and its sole child hoisted.
545
+ before: '<div><a className="bg-red-200">Link</a></div>',
546
+ after: '<a className="bg-red-200">Link</a>'
547
+ }
548
+ ],
549
+ noMatch: [
313
550
  // A ref pins the wrapper's element identity (a hard opacity barrier) → not a passthrough.
314
- noMatch: '<div ref={rootRef}><a className="bg-red-200">Link</a></div>'
315
- }
316
- ]
551
+ '<div ref={rootRef}><a className="bg-red-200">Link</a></div>',
552
+ // A `display:flex` wrapper establishes a formatting context, so removing its box is NOT
553
+ // provably layout-neutral → the conservative gate leaves it in place.
554
+ '<div className="flex"><a className="bg-red-200">Link</a></div>'
555
+ ]
556
+ }
317
557
  });
318
558
 
319
- // ../patterns/src/flatten/redundant-fragment.pattern.ts
559
+ // ../patterns/src/library/flatten/redundant-fragment.pattern.ts
320
560
  init_esm_shims();
321
561
  function parentIsRedundantFragment(node, ctx) {
322
562
  const el = node;
@@ -339,7 +579,7 @@ function parentIsRedundantFragment(node, ctx) {
339
579
  if (ctx.selectors.reparentImpact(fid).size > 0) return false;
340
580
  return true;
341
581
  }
342
- var redundantFragment = pattern({
582
+ var redundantFragment = definePattern({
343
583
  name: "redundant-fragment",
344
584
  category: "flatten/redundant-fragment",
345
585
  safety: 1,
@@ -358,116 +598,434 @@ var redundantFragment = pattern({
358
598
  if (!fragment || fragment.kind !== "fragment") return null;
359
599
  return [rw.unwrap(fragment)];
360
600
  },
361
- examples: [
362
- {
363
- before: '<><span className="bg-red-200">Hi</span></>',
364
- after: '<span className="bg-red-200">Hi</span>'
365
- },
366
- {
601
+ test: {
602
+ cases: [
603
+ {
604
+ // A fragment renders no box, so unwrapping a single-child fragment is always layout-identical
605
+ // → a provably-safe flatten: the child is spliced up into the fragment's slot.
606
+ before: '<><span className="bg-red-200">Hi</span></>',
607
+ after: '<span className="bg-red-200">Hi</span>'
608
+ }
609
+ ],
610
+ noMatch: [
367
611
  // Two children ⇒ not a single-child fragment, so the fragment is load-bearing and stays.
368
- noMatch: '<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
369
- }
370
- ]
612
+ '<><span className="bg-red-200">A</span><span className="bg-green-200">B</span></>'
613
+ ]
614
+ }
371
615
  });
372
616
 
373
- // ../patterns/src/compress/dedupe-classes.pattern.ts
617
+ // ../patterns/src/library/flatten/redundant-inline-wrapper.pattern.ts
374
618
  init_esm_shims();
375
- function elementOf2(node) {
619
+ function asEl3(node) {
376
620
  const n = node;
377
621
  return n.kind === "element" ? n : null;
378
622
  }
379
- var hasDangerousHtml = (node) => elementOf2(node)?.meta.hasDangerousHtml ?? false;
380
- var isOpaque = (node, ctx) => ctx.isOpaque(node);
381
- function findRedundantClasses(computed2) {
382
- const winners = /* @__PURE__ */ new Set();
383
- const shadowed = /* @__PURE__ */ new Set();
384
- for (const block of computed2.blocks.values()) {
385
- for (const decl of block.decls.values()) {
386
- if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
387
- for (const o of decl.shadowed ?? []) {
388
- if (o.kind === "class") shadowed.add(o.className);
389
- }
390
- }
391
- }
392
- return { winners, shadowed };
623
+ function metaOf3(node) {
624
+ return asEl3(node)?.meta ?? null;
393
625
  }
394
- var dedupeClasses = pattern({
395
- name: "dedupe-classes",
396
- category: "compress/dedupe-classes",
397
- safety: 1,
626
+ var establishesContext2 = (node) => {
627
+ const m = metaOf3(node);
628
+ if (!m) return false;
629
+ return m.establishesBox || m.establishesFormattingContext || m.establishesStackingContext || m.isContainingBlock || m.declaresCustomProperties;
630
+ };
631
+ var hasSpreadAttrs3 = (node) => metaOf3(node)?.hasSpreadAttrs ?? false;
632
+ var isComponentNode3 = (node) => metaOf3(node)?.isComponent ?? false;
633
+ var hasOwnAttrs3 = (node) => {
634
+ const el = asEl3(node);
635
+ if (!el) return false;
636
+ return el.attrs.entries.size > 0 || el.attrs.spreads.length > 0;
637
+ };
638
+ var targetedByStructuralPseudo4 = (node, ctx) => {
639
+ const el = asEl3(node);
640
+ if (!el) return false;
641
+ if (el.meta.targetedByStructuralPseudo) return true;
642
+ return ctx.selectors.targetedByStructuralPseudo(el.id);
643
+ };
644
+ var DISPLAY2 = "display";
645
+ var hasNonInlineDisplay = (node, ctx) => {
646
+ const el = asEl3(node);
647
+ if (!el) return false;
648
+ const sm = ctx.computedOf(el) ?? el.computed;
649
+ for (const block of sm.blocks.values()) {
650
+ const decl = block.decls.get(DISPLAY2);
651
+ if (decl && String(decl.value) !== "inline") return true;
652
+ }
653
+ return false;
654
+ };
655
+ var redundantInlineWrapper = definePattern({
656
+ name: "redundant-inline-wrapper",
657
+ category: "flatten/redundant-inline-wrapper",
658
+ safety: 2,
398
659
  doc: {
399
- title: "Dedupe fully-overridden class tokens",
400
- 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.",
401
- before: '<p class="text-sm text-lg" />',
402
- after: '<p class="text-lg" />',
403
- safetyRationale: "A fully-overridden token contributes nothing to the computed style in any condition, so removing it changes no pixels. Dynamic/opaque class lists, ref/handler/dynamic-children/raw-html barriers, combinator subjects, and selector-bound (non-droppable) tokens are excluded."
660
+ title: "Flatten redundant inline wrapper",
661
+ 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.",
662
+ before: "<span><Child/></span>",
663
+ after: "<Child/>",
664
+ 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."
404
665
  },
405
666
  match: {
667
+ tag: "span",
668
+ onlyChild: "element",
669
+ paintsNothing: true,
406
670
  where: [
407
- not(hasRef),
408
- not(hasEventHandlers),
409
- not(hasDynamicChildren),
410
- not(hasDangerousHtml),
671
+ not(hasNonInlineDisplay),
672
+ not(establishesContext2),
673
+ not(hasOwnAttrs3),
411
674
  not(hasDynamicClasses),
412
- not(isOpaque),
413
- not(targetedByCombinator)
675
+ not(hasSpreadAttrs3),
676
+ not(isComponentNode3),
677
+ not(targetedByStructuralPseudo4)
414
678
  ]
415
679
  },
416
- rewrite: {
417
- dropClasses(computed2, ctx) {
418
- const { winners, shadowed } = findRedundantClasses(computed2);
419
- const drop = /* @__PURE__ */ new Set();
420
- for (const cls of shadowed) {
421
- if (winners.has(cls)) continue;
422
- if (!ctx.resolver.selectorUsage(cls).droppable) continue;
423
- drop.add(cls);
680
+ rewrite: { flattenInto: "child" },
681
+ test: {
682
+ cases: [
683
+ {
684
+ // An empty inline span paints nothing and establishes no context → a provably-safe flatten:
685
+ // the span is removed and its sole child hoisted in place.
686
+ before: '<span><a className="text-blue-500">Link</a></span>',
687
+ after: '<a className="text-blue-500">Link</a>'
424
688
  }
425
- return drop;
426
- }
427
- },
428
- examples: [
429
- {
430
- // `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
431
- // records that shadowing in provenance and reports the Tailwind utility as droppable, so the
432
- // pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
433
- before: '<p className="text-sm text-lg">Hi</p>',
434
- after: '<p className="text-lg">Hi</p>'
435
- },
436
- {
437
- // Both tokens win a distinct property (no full override) → nothing to dedupe.
438
- noMatch: '<p className="text-lg font-bold">Hi</p>'
439
- }
440
- ]
689
+ ],
690
+ noMatch: [
691
+ // A ref pins the span's element identity (a hard opacity barrier) → not a passthrough.
692
+ '<span ref={spanRef}><a className="text-blue-500">Link</a></span>',
693
+ // The span paints its own background (own visual style) → kept.
694
+ '<span className="bg-green-200"><a className="text-blue-500">Link</a></span>',
695
+ // Non-inline display (inline-block) participates in layout differently kept.
696
+ '<span className="inline-block"><a className="text-blue-500">Link</a></span>'
697
+ ]
698
+ }
441
699
  });
442
700
 
443
- // ../patterns/src/compress/inset-shorthand.pattern.ts
701
+ // ../patterns/src/library/compress/border-radius-shorthand.pattern.ts
444
702
  init_esm_shims();
445
- var TOP = "top";
446
- var RIGHT = "right";
447
- var BOTTOM = "bottom";
448
- var LEFT = "left";
449
- var INSET = "inset";
450
- var INSET_BLOCK = "inset-block";
451
- var INSET_INLINE = "inset-inline";
452
- var hasRawHtml = (node) => {
453
- const n = node;
454
- return n.kind === "element" ? n.meta.hasDangerousHtml : false;
455
- };
456
- function sameSide(a, b) {
457
- return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
458
- }
459
- function asProperty(src, property) {
460
- return { ...src, property, inherited: normalizer.inherited.isInherited(property) };
703
+ var CORNERS = [
704
+ "border-top-left-radius",
705
+ "border-top-right-radius",
706
+ "border-bottom-right-radius",
707
+ "border-bottom-left-radius"
708
+ ];
709
+ var CORNER_SET = new Set(CORNERS);
710
+ var BASE_KEY = conditionKey(BASE_CONDITION);
711
+ var RADIUS = "border-radius";
712
+ var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set([
713
+ "initial",
714
+ "inherit",
715
+ "unset",
716
+ "revert",
717
+ "revert-layer"
718
+ ]);
719
+ function analyzeRadius(sm) {
720
+ const block = sm.blocks.get(BASE_KEY);
721
+ if (!block) return null;
722
+ const corners = [];
723
+ for (const corner of CORNERS) {
724
+ const decl = block.decls.get(corner);
725
+ if (!decl) return null;
726
+ corners.push(decl);
727
+ }
728
+ const important = corners[0].important;
729
+ if (!corners.every((d) => d.important === important)) return null;
730
+ const value = String(corners[0].value);
731
+ if (NON_COLLAPSIBLE_VALUES.has(value)) return null;
732
+ if (!corners.every((d) => String(d.value) === value)) return null;
733
+ const relative = corners.some((d) => d.relativeToParent);
734
+ return { value, important, relative };
461
735
  }
462
- function withBaseDecls(src, baseDecls) {
736
+ function withFoldedRadius(sm, fold) {
463
737
  const blocks = /* @__PURE__ */ new Map();
464
- for (const [key, block] of src.blocks) {
465
- const decls = key === BASE_CONDITION_KEY ? baseDecls : new Map(block.decls);
738
+ for (const [key, block] of sm.blocks) {
739
+ if (key !== BASE_KEY) {
740
+ blocks.set(key, block);
741
+ continue;
742
+ }
743
+ const decls = /* @__PURE__ */ new Map();
744
+ for (const [prop, decl] of block.decls) {
745
+ if (CORNER_SET.has(String(prop))) continue;
746
+ decls.set(prop, decl);
747
+ }
748
+ const shorthand = {
749
+ property: RADIUS,
750
+ value: fold.value,
751
+ important: fold.important,
752
+ relativeToParent: fold.relative,
753
+ inherited: false
754
+ // border-radius is never inherited
755
+ };
756
+ decls.set(shorthand.property, shorthand);
757
+ blocks.set(key, { condition: block.condition, decls });
758
+ }
759
+ return { blocks };
760
+ }
761
+ var borderRadiusShorthand = definePattern({
762
+ name: "border-radius-shorthand",
763
+ category: "compress/border-radius-shorthand",
764
+ safety: 1,
765
+ doc: {
766
+ title: "Collapse equal corner radii into border-radius",
767
+ 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).",
768
+ before: '<div class="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg"/>',
769
+ after: '<div class="rounded-lg"/>',
770
+ 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."
771
+ },
772
+ rewrite: {
773
+ rewriteClasses(computed2) {
774
+ const fold = analyzeRadius(computed2);
775
+ return fold ? withFoldedRadius(computed2, fold) : null;
776
+ }
777
+ },
778
+ test: {
779
+ cases: [
780
+ {
781
+ // The four equal corner longhands collapse to a `border-radius` decl at the IR level; the
782
+ // minimizing reverse-emit then picks the single shortest utility (`rounded-lg`) that reproduces
783
+ // it, replacing the four `rounded-{tl,tr,br,bl}-lg` tokens. `bg-red-200` is preserved.
784
+ before: '<div className="rounded-tl-lg rounded-tr-lg rounded-br-lg rounded-bl-lg bg-red-200">box</div>',
785
+ after: '<div className="bg-red-200 rounded-lg">box</div>'
786
+ }
787
+ ],
788
+ // Corners differ (top corners vs bottom corners) → no all-equal collapse.
789
+ noMatch: ['<div className="rounded-t-lg rounded-b-sm bg-red-200">box</div>']
790
+ }
791
+ });
792
+
793
+ // ../patterns/src/library/compress/border-shorthand.pattern.ts
794
+ init_esm_shims();
795
+ var WIDTH_SIDES = [
796
+ "border-top-width",
797
+ "border-right-width",
798
+ "border-bottom-width",
799
+ "border-left-width"
800
+ ];
801
+ var WIDTH_SIDE_SET = new Set(WIDTH_SIDES);
802
+ var BASE_KEY2 = conditionKey(BASE_CONDITION);
803
+ var BORDER_WIDTH = "border-width";
804
+ function analyzeWidth(sm) {
805
+ const block = sm.blocks.get(BASE_KEY2);
806
+ if (!block) return null;
807
+ const sides = [];
808
+ for (const side of WIDTH_SIDES) {
809
+ const decl = block.decls.get(side);
810
+ if (!decl) return null;
811
+ sides.push(decl);
812
+ }
813
+ const [top, right, bottom, left] = sides;
814
+ if (!(top.important === right.important && right.important === bottom.important && bottom.important === left.important)) {
815
+ return null;
816
+ }
817
+ const tv = String(top.value);
818
+ const rv = String(right.value);
819
+ const bv = String(bottom.value);
820
+ const lv = String(left.value);
821
+ if (tv !== bv || lv !== rv) return null;
822
+ const value = tv === lv ? tv : `${tv} ${lv}`;
823
+ const relative = sides.some((d) => d.relativeToParent);
824
+ return { value, important: top.important, relative };
825
+ }
826
+ function withFoldedWidth(sm, fold) {
827
+ const blocks = /* @__PURE__ */ new Map();
828
+ for (const [key, block] of sm.blocks) {
829
+ if (key !== BASE_KEY2) {
830
+ blocks.set(key, block);
831
+ continue;
832
+ }
833
+ const decls = /* @__PURE__ */ new Map();
834
+ for (const [prop, decl] of block.decls) {
835
+ if (WIDTH_SIDE_SET.has(String(prop))) continue;
836
+ decls.set(prop, decl);
837
+ }
838
+ const shorthand = {
839
+ property: BORDER_WIDTH,
840
+ value: fold.value,
841
+ important: fold.important,
842
+ relativeToParent: fold.relative,
843
+ inherited: false
844
+ // border-width is never inherited
845
+ };
846
+ decls.set(shorthand.property, shorthand);
847
+ blocks.set(key, { condition: block.condition, decls });
848
+ }
849
+ return { blocks };
850
+ }
851
+ var borderShorthand = definePattern({
852
+ name: "border-shorthand",
853
+ category: "compress/border-shorthand",
854
+ safety: 1,
855
+ doc: {
856
+ title: "Collapse border-width longhands to shorthand",
857
+ 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-*).",
858
+ before: '<div class="border-t-2 border-r-2 border-b-2 border-l-2"/>',
859
+ after: '<div class="border-2"/>',
860
+ 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."
861
+ },
862
+ rewrite: {
863
+ rewriteClasses(computed2) {
864
+ const fold = analyzeWidth(computed2);
865
+ return fold ? withFoldedWidth(computed2, fold) : null;
866
+ }
867
+ },
868
+ test: {
869
+ cases: [
870
+ {
871
+ // The four equal width longhands collapse to a `border-width` shorthand at the IR level, and the
872
+ // minimizing reverse-emit picks the single shortest utility (`border-2`) that reproduces it,
873
+ // replacing the four `border-{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
874
+ before: '<div className="border-t-2 border-r-2 border-b-2 border-l-2 bg-red-200">box</div>',
875
+ after: '<div className="bg-red-200 border-2">box</div>'
876
+ }
877
+ ],
878
+ // Asymmetric widths (top != bottom) cannot fold into a shorthand.
879
+ noMatch: ['<div className="border-t-2 border-r-4 border-b-8 border-l-4 bg-red-200">box</div>']
880
+ }
881
+ });
882
+
883
+ // ../patterns/src/library/compress/dedupe-classes.pattern.ts
884
+ init_esm_shims();
885
+ function findRedundantClasses(computed2) {
886
+ const winners = /* @__PURE__ */ new Set();
887
+ const shadowed = /* @__PURE__ */ new Set();
888
+ for (const block of computed2.blocks.values()) {
889
+ for (const decl of block.decls.values()) {
890
+ if (decl.origin && decl.origin.kind === "class") winners.add(decl.origin.className);
891
+ for (const o of decl.shadowed ?? []) {
892
+ if (o.kind === "class") shadowed.add(o.className);
893
+ }
894
+ }
895
+ }
896
+ return { winners, shadowed };
897
+ }
898
+ var dedupeClasses = definePattern({
899
+ name: "dedupe-classes",
900
+ category: "compress/dedupe-classes",
901
+ safety: 1,
902
+ doc: {
903
+ title: "Dedupe fully-overridden class tokens",
904
+ 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.",
905
+ before: '<p class="text-sm text-lg" />',
906
+ after: '<p class="text-lg" />',
907
+ 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."
908
+ },
909
+ rewrite: {
910
+ dropClasses(computed2, ctx) {
911
+ const { winners, shadowed } = findRedundantClasses(computed2);
912
+ const drop = /* @__PURE__ */ new Set();
913
+ for (const cls of shadowed) {
914
+ if (winners.has(cls)) continue;
915
+ if (!ctx.resolver.selectorUsage(cls).droppable) continue;
916
+ drop.add(cls);
917
+ }
918
+ return drop;
919
+ }
920
+ },
921
+ test: {
922
+ cases: [
923
+ {
924
+ // `text-sm` is fully overridden by `text-lg` (both set font-size + line-height). The resolver
925
+ // records that shadowing in provenance and reports the Tailwind utility as droppable, so the
926
+ // pattern drops `text-sm`; the reverse-emit then re-derives the minimal set (`text-lg`).
927
+ before: '<p className="text-sm text-lg">Hi</p>',
928
+ after: '<p className="text-lg">Hi</p>'
929
+ }
930
+ ],
931
+ // Both tokens win a distinct property (no full override) → nothing to dedupe.
932
+ noMatch: ['<p className="text-lg font-bold">Hi</p>']
933
+ }
934
+ });
935
+
936
+ // ../patterns/src/library/compress/gap-shorthand.pattern.ts
937
+ init_esm_shims();
938
+ var ROW_GAP = "row-gap";
939
+ var COLUMN_GAP = "column-gap";
940
+ var GAP = "gap";
941
+ var BASE_KEY3 = conditionKey(BASE_CONDITION);
942
+ function withGapShorthand(sm, gapDecl) {
943
+ const blocks = /* @__PURE__ */ new Map();
944
+ for (const [key, block] of sm.blocks) {
945
+ if (key !== BASE_KEY3) {
946
+ blocks.set(key, block);
947
+ continue;
948
+ }
949
+ const decls = /* @__PURE__ */ new Map();
950
+ for (const [prop, decl] of block.decls) {
951
+ if (prop === ROW_GAP || prop === COLUMN_GAP) continue;
952
+ decls.set(prop, decl);
953
+ }
954
+ decls.set(gapDecl.property, gapDecl);
955
+ blocks.set(key, { condition: block.condition, decls });
956
+ }
957
+ return { blocks };
958
+ }
959
+ var gapShorthand = definePattern({
960
+ name: "gap-shorthand",
961
+ category: "compress/gap-shorthand",
962
+ safety: 1,
963
+ doc: {
964
+ title: "Collapse equal row/column gap into the `gap` shorthand",
965
+ 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-*).",
966
+ before: '<div style="row-gap:16px;column-gap:16px"/>',
967
+ after: '<div style="gap:16px"/>',
968
+ 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."
969
+ },
970
+ rewrite: {
971
+ rewriteClasses(computed2) {
972
+ const base = computed2.blocks.get(BASE_KEY3);
973
+ if (!base) return null;
974
+ const rowGap = base.decls.get(ROW_GAP);
975
+ const colGap = base.decls.get(COLUMN_GAP);
976
+ if (!rowGap || !colGap) return null;
977
+ if (rowGap.important !== colGap.important) return null;
978
+ if (rowGap.value !== colGap.value) return null;
979
+ const gapDecl = {
980
+ property: GAP,
981
+ value: rowGap.value,
982
+ important: rowGap.important,
983
+ relativeToParent: rowGap.relativeToParent || colGap.relativeToParent,
984
+ inherited: false
985
+ // gap is not an inherited property
986
+ };
987
+ return withGapShorthand(computed2, gapDecl);
988
+ }
989
+ },
990
+ test: {
991
+ cases: [
992
+ {
993
+ // Equal row/column gap collapse to a `gap` decl at the IR level; the minimizing reverse-emit
994
+ // re-expands `gap` to row-gap+column-gap and picks the single utility covering both (`gap-4`),
995
+ // replacing the `gap-x-4`+`gap-y-4` pair. `bg-red-200` is preserved.
996
+ before: '<div className="gap-x-4 gap-y-4 bg-red-200">box</div>',
997
+ after: '<div className="bg-red-200 gap-4">box</div>'
998
+ }
999
+ ],
1000
+ // Unequal axes (row-gap != column-gap) have no single-value `gap` equivalent → not collapsed.
1001
+ noMatch: ['<div className="gap-x-2 gap-y-4 bg-red-200">box</div>']
1002
+ }
1003
+ });
1004
+
1005
+ // ../patterns/src/library/compress/inset-shorthand.pattern.ts
1006
+ init_esm_shims();
1007
+ var TOP = "top";
1008
+ var RIGHT = "right";
1009
+ var BOTTOM = "bottom";
1010
+ var LEFT = "left";
1011
+ var INSET = "inset";
1012
+ var INSET_BLOCK = "inset-block";
1013
+ var INSET_INLINE = "inset-inline";
1014
+ function sameSide(a, b) {
1015
+ return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
1016
+ }
1017
+ function asProperty(src, property) {
1018
+ return { ...src, property, inherited: normalizer.inherited.isInherited(property) };
1019
+ }
1020
+ function withBaseDecls(src, baseDecls) {
1021
+ const blocks = /* @__PURE__ */ new Map();
1022
+ for (const [key, block] of src.blocks) {
1023
+ const decls = key === BASE_CONDITION_KEY ? baseDecls : new Map(block.decls);
466
1024
  blocks.set(key, { condition: block.condition, decls });
467
1025
  }
468
1026
  return { blocks };
469
1027
  }
470
- var insetShorthand = pattern({
1028
+ var insetShorthand = definePattern({
471
1029
  name: "inset-shorthand",
472
1030
  category: "compress/inset-shorthand",
473
1031
  safety: 2,
@@ -476,17 +1034,7 @@ var insetShorthand = pattern({
476
1034
  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).",
477
1035
  before: '<div style="top:10px;right:10px;bottom:10px;left:10px"/>',
478
1036
  after: '<div style="inset:10px"/>',
479
- safetyRationale: "Meaning-preserving shorthand compaction; the element is not a combinator subject and carries no ref/handlers/dynamic children/raw HTML, so neither selector matching nor behaviour changes."
480
- },
481
- match: {
482
- where: [
483
- not(hasRef),
484
- not(hasEventHandlers),
485
- not(hasDynamicChildren),
486
- not(hasRawHtml),
487
- not(hasDynamicClasses),
488
- not(targetedByCombinator)
489
- ]
1037
+ 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."
490
1038
  },
491
1039
  rewrite: {
492
1040
  rewriteClasses(computed2) {
@@ -522,22 +1070,22 @@ var insetShorthand = pattern({
522
1070
  return withBaseDecls(computed2, next);
523
1071
  }
524
1072
  },
525
- examples: [
526
- {
527
- // The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
528
- // minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
529
- // covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
530
- before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
531
- after: '<div className="bg-red-200 inset-0">box</div>'
532
- },
533
- {
534
- // No matching inset pair (all four distinct) → nothing collapses.
535
- noMatch: '<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>'
536
- }
537
- ]
1073
+ test: {
1074
+ cases: [
1075
+ {
1076
+ // The four equal inset longhands collapse to an `inset` shorthand at the IR level; the
1077
+ // minimizing reverse-emit expands it back to top/right/bottom/left and picks the single utility
1078
+ // covering all four (`inset-0`), replacing the four physical-side tokens. `bg-red-200` survives.
1079
+ before: '<div className="top-0 right-0 bottom-0 left-0 bg-red-200">box</div>',
1080
+ after: '<div className="bg-red-200 inset-0">box</div>'
1081
+ }
1082
+ ],
1083
+ // No matching inset pair (all four distinct) → nothing collapses.
1084
+ noMatch: ['<div className="top-0 right-1 bottom-2 left-3 bg-red-200">box</div>']
1085
+ }
538
1086
  });
539
1087
 
540
- // ../patterns/src/compress/margin-shorthand.pattern.ts
1088
+ // ../patterns/src/library/compress/margin-shorthand.pattern.ts
541
1089
  init_esm_shims();
542
1090
  var MARGIN_SIDES = [
543
1091
  "margin-top",
@@ -546,12 +1094,7 @@ var MARGIN_SIDES = [
546
1094
  "margin-left"
547
1095
  ];
548
1096
  var MARGIN_SIDE_SET = new Set(MARGIN_SIDES);
549
- var BASE_KEY = conditionKey(BASE_CONDITION);
550
- function asElement(node) {
551
- const n = node;
552
- return n.kind === "element" ? n : null;
553
- }
554
- var hasDangerousHtml2 = (node) => asElement(node)?.meta.hasDangerousHtml ?? false;
1097
+ var BASE_KEY4 = conditionKey(BASE_CONDITION);
555
1098
  function collapseMarginValue(top, right, bottom, left) {
556
1099
  if (right === left) {
557
1100
  if (top === bottom) {
@@ -564,7 +1107,7 @@ function collapseMarginValue(top, right, bottom, left) {
564
1107
  function withFoldedMargin(sm, marginDecl) {
565
1108
  const blocks = /* @__PURE__ */ new Map();
566
1109
  for (const [key, block] of sm.blocks) {
567
- if (key !== BASE_KEY) {
1110
+ if (key !== BASE_KEY4) {
568
1111
  blocks.set(key, block);
569
1112
  continue;
570
1113
  }
@@ -577,7 +1120,7 @@ function withFoldedMargin(sm, marginDecl) {
577
1120
  }
578
1121
  return { blocks };
579
1122
  }
580
- var marginShorthand = pattern({
1123
+ var marginShorthand = definePattern({
581
1124
  name: "margin-shorthand",
582
1125
  category: "compress/margin-shorthand",
583
1126
  safety: 2,
@@ -586,21 +1129,11 @@ var marginShorthand = pattern({
586
1129
  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.",
587
1130
  before: '<div style="margin-top:8px;margin-right:8px;margin-bottom:8px;margin-left:8px"/>',
588
1131
  after: '<div style="margin:8px"/>',
589
- safetyRationale: "Pure representation change (no pixels move); skips nodes with ref/handlers/dynamic children/raw html, dynamic class segments, or combinator-subject selectors."
590
- },
591
- match: {
592
- where: [
593
- not(hasRef),
594
- not(hasEventHandlers),
595
- not(hasDynamicChildren),
596
- not(hasDynamicClasses),
597
- not(hasDangerousHtml2),
598
- not(targetedByCombinator)
599
- ]
1132
+ 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."
600
1133
  },
601
1134
  rewrite: {
602
1135
  rewriteClasses(computed2) {
603
- const base = computed2.blocks.get(BASE_KEY);
1136
+ const base = computed2.blocks.get(BASE_KEY4);
604
1137
  if (!base) return null;
605
1138
  const sides = MARGIN_SIDES.map((p) => base.decls.get(p));
606
1139
  if (sides.some((d) => d === void 0)) return null;
@@ -623,22 +1156,169 @@ var marginShorthand = pattern({
623
1156
  return withFoldedMargin(computed2, marginDecl);
624
1157
  }
625
1158
  },
626
- examples: [
627
- {
628
- // The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
629
- // minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
630
- // the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
631
- before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
632
- after: '<div className="bg-red-200 m-2">box</div>'
633
- },
634
- {
635
- // Only two margin sides set → the four-longhand `margin` collapse does not apply.
636
- noMatch: '<div className="mt-2 mb-2 bg-red-200">box</div>'
1159
+ test: {
1160
+ cases: [
1161
+ {
1162
+ // The four equal margin longhands collapse to a `margin` shorthand at the IR level, and the
1163
+ // minimizing reverse-emit picks the single shortest utility (`m-2`) reproducing it, replacing
1164
+ // the four `m{t,r,b,l}-2` tokens. `bg-red-200` is preserved.
1165
+ before: '<div className="mt-2 mr-2 mb-2 ml-2 bg-red-200">box</div>',
1166
+ after: '<div className="bg-red-200 m-2">box</div>'
1167
+ }
1168
+ ],
1169
+ // Only two margin sides set → the four-longhand `margin` collapse does not apply.
1170
+ noMatch: ['<div className="mt-2 mb-2 bg-red-200">box</div>']
1171
+ }
1172
+ });
1173
+
1174
+ // ../patterns/src/library/compress/overflow-shorthand.pattern.ts
1175
+ init_esm_shims();
1176
+ var OVERFLOW_X = "overflow-x";
1177
+ var OVERFLOW_Y = "overflow-y";
1178
+ var OVERFLOW = "overflow";
1179
+ var BASE_KEY5 = conditionKey(BASE_CONDITION);
1180
+ function withOverflowShorthand(sm, overflowDecl) {
1181
+ const blocks = /* @__PURE__ */ new Map();
1182
+ for (const [key, block] of sm.blocks) {
1183
+ if (key !== BASE_KEY5) {
1184
+ blocks.set(key, block);
1185
+ continue;
1186
+ }
1187
+ const decls = /* @__PURE__ */ new Map();
1188
+ for (const [prop, decl] of block.decls) {
1189
+ if (prop === OVERFLOW_X || prop === OVERFLOW_Y) continue;
1190
+ decls.set(prop, decl);
1191
+ }
1192
+ decls.set(overflowDecl.property, overflowDecl);
1193
+ blocks.set(key, { condition: block.condition, decls });
1194
+ }
1195
+ return { blocks };
1196
+ }
1197
+ var overflowShorthand = definePattern({
1198
+ name: "overflow-shorthand",
1199
+ category: "compress/overflow-shorthand",
1200
+ safety: 1,
1201
+ doc: {
1202
+ title: "Collapse equal overflow axes into the `overflow` shorthand",
1203
+ 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-*).",
1204
+ before: '<div style="overflow-x:auto;overflow-y:auto"/>',
1205
+ after: '<div style="overflow:auto"/>',
1206
+ 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."
1207
+ },
1208
+ rewrite: {
1209
+ rewriteClasses(computed2) {
1210
+ const base = computed2.blocks.get(BASE_KEY5);
1211
+ if (!base) return null;
1212
+ const overflowX = base.decls.get(OVERFLOW_X);
1213
+ const overflowY = base.decls.get(OVERFLOW_Y);
1214
+ if (!overflowX || !overflowY) return null;
1215
+ if (overflowX.important !== overflowY.important) return null;
1216
+ if (overflowX.value !== overflowY.value) return null;
1217
+ const overflowDecl = {
1218
+ property: OVERFLOW,
1219
+ value: overflowX.value,
1220
+ important: overflowX.important,
1221
+ relativeToParent: overflowX.relativeToParent || overflowY.relativeToParent,
1222
+ inherited: false
1223
+ // overflow is not an inherited property
1224
+ };
1225
+ return withOverflowShorthand(computed2, overflowDecl);
1226
+ }
1227
+ },
1228
+ test: {
1229
+ cases: [
1230
+ {
1231
+ // Equal overflow axes collapse to an `overflow` decl at the IR level; the minimizing
1232
+ // reverse-emit picks the single utility covering both (`overflow-auto`), replacing the
1233
+ // `overflow-x-auto`+`overflow-y-auto` pair. `bg-red-200` is preserved.
1234
+ before: '<div className="overflow-x-auto overflow-y-auto bg-red-200">box</div>',
1235
+ after: '<div className="bg-red-200 overflow-auto">box</div>'
1236
+ }
1237
+ ],
1238
+ // Mismatched axes (overflow-x != overflow-y) have no single-keyword equivalent → not collapsed.
1239
+ noMatch: ['<div className="overflow-x-auto overflow-y-hidden bg-red-200">box</div>']
1240
+ }
1241
+ });
1242
+
1243
+ // ../patterns/src/library/compress/overscroll-behavior-shorthand.pattern.ts
1244
+ init_esm_shims();
1245
+ var OVERSCROLL_X = "overscroll-behavior-x";
1246
+ var OVERSCROLL_Y = "overscroll-behavior-y";
1247
+ var OVERSCROLL = "overscroll-behavior";
1248
+ var BASE_KEY6 = conditionKey(BASE_CONDITION);
1249
+ var NON_COLLAPSIBLE_VALUES2 = /* @__PURE__ */ new Set([
1250
+ "initial",
1251
+ "inherit",
1252
+ "unset",
1253
+ "revert",
1254
+ "revert-layer"
1255
+ ]);
1256
+ function withOverscrollShorthand(sm, shorthand) {
1257
+ const blocks = /* @__PURE__ */ new Map();
1258
+ for (const [key, block] of sm.blocks) {
1259
+ if (key !== BASE_KEY6) {
1260
+ blocks.set(key, block);
1261
+ continue;
1262
+ }
1263
+ const decls = /* @__PURE__ */ new Map();
1264
+ for (const [prop, decl] of block.decls) {
1265
+ if (prop === OVERSCROLL_X || prop === OVERSCROLL_Y) continue;
1266
+ decls.set(prop, decl);
1267
+ }
1268
+ decls.set(shorthand.property, shorthand);
1269
+ blocks.set(key, { condition: block.condition, decls });
1270
+ }
1271
+ return { blocks };
1272
+ }
1273
+ var overscrollBehaviorShorthand = definePattern({
1274
+ name: "overscroll-behavior-shorthand",
1275
+ category: "compress/overscroll-behavior-shorthand",
1276
+ safety: 1,
1277
+ doc: {
1278
+ title: "Collapse equal overscroll-behavior axes into overscroll-behavior",
1279
+ 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-*).",
1280
+ before: '<div style="overscroll-behavior-x:contain;overscroll-behavior-y:contain"/>',
1281
+ after: '<div class="overscroll-contain"/>',
1282
+ 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."
1283
+ },
1284
+ rewrite: {
1285
+ rewriteClasses(computed2) {
1286
+ const base = computed2.blocks.get(BASE_KEY6);
1287
+ if (!base) return null;
1288
+ const x = base.decls.get(OVERSCROLL_X);
1289
+ const y = base.decls.get(OVERSCROLL_Y);
1290
+ if (!x || !y) return null;
1291
+ if (x.important !== y.important) return null;
1292
+ const value = String(x.value);
1293
+ if (NON_COLLAPSIBLE_VALUES2.has(value)) return null;
1294
+ if (value !== String(y.value)) return null;
1295
+ const shorthand = {
1296
+ property: OVERSCROLL,
1297
+ value: x.value,
1298
+ important: x.important,
1299
+ relativeToParent: x.relativeToParent || y.relativeToParent,
1300
+ inherited: false
1301
+ // overscroll-behavior is not an inherited property
1302
+ };
1303
+ return withOverscrollShorthand(computed2, shorthand);
637
1304
  }
638
- ]
1305
+ },
1306
+ test: {
1307
+ cases: [
1308
+ {
1309
+ // Equal x/y axes collapse to an `overscroll-behavior` decl at the IR level; the minimizing
1310
+ // reverse-emit picks the single utility covering both (`overscroll-contain`), replacing the
1311
+ // `overscroll-x-contain`+`overscroll-y-contain` pair. `bg-red-200` is preserved.
1312
+ before: '<div className="overscroll-x-contain overscroll-y-contain bg-red-200">box</div>',
1313
+ after: '<div className="bg-red-200 overscroll-contain">box</div>'
1314
+ }
1315
+ ],
1316
+ // Axes differ (x != y) → no equal-axis collapse.
1317
+ noMatch: ['<div className="overscroll-x-contain overscroll-y-auto bg-red-200">box</div>']
1318
+ }
639
1319
  });
640
1320
 
641
- // ../patterns/src/compress/padding-shorthand.pattern.ts
1321
+ // ../patterns/src/library/compress/padding-shorthand.pattern.ts
642
1322
  init_esm_shims();
643
1323
  var PADDING_SIDES = [
644
1324
  "padding-top",
@@ -647,9 +1327,9 @@ var PADDING_SIDES = [
647
1327
  "padding-left"
648
1328
  ];
649
1329
  var PADDING_SIDE_SET = new Set(PADDING_SIDES);
650
- var BASE_KEY2 = conditionKey(BASE_CONDITION);
1330
+ var BASE_KEY7 = conditionKey(BASE_CONDITION);
651
1331
  function analyzePadding(sm) {
652
- const block = sm.blocks.get(BASE_KEY2);
1332
+ const block = sm.blocks.get(BASE_KEY7);
653
1333
  if (!block) return null;
654
1334
  const sides = [];
655
1335
  for (const side of PADDING_SIDES) {
@@ -670,16 +1350,10 @@ function analyzePadding(sm) {
670
1350
  const relative = sides.some((d) => d.relativeToParent);
671
1351
  return { value, important: top.important, relative };
672
1352
  }
673
- var isInert = (node) => {
674
- const n = node;
675
- if (n.kind !== "element") return false;
676
- const el = n;
677
- return !el.meta.hasDangerousHtml && !el.meta.hasSpreadAttrs && !el.isComponent;
678
- };
679
1353
  function withFoldedPadding(sm, fold) {
680
1354
  const blocks = /* @__PURE__ */ new Map();
681
1355
  for (const [key, block] of sm.blocks) {
682
- if (key !== BASE_KEY2) {
1356
+ if (key !== BASE_KEY7) {
683
1357
  blocks.set(key, block);
684
1358
  continue;
685
1359
  }
@@ -701,7 +1375,7 @@ function withFoldedPadding(sm, fold) {
701
1375
  }
702
1376
  return { blocks };
703
1377
  }
704
- var paddingShorthand = pattern({
1378
+ var paddingShorthand = definePattern({
705
1379
  name: "padding-shorthand",
706
1380
  category: "compress/padding-shorthand",
707
1381
  safety: 1,
@@ -710,17 +1384,7 @@ var paddingShorthand = pattern({
710
1384
  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-*).",
711
1385
  before: '<div class="pt-4 pr-4 pb-4 pl-4"/>',
712
1386
  after: '<div class="p-4"/>',
713
- safetyRationale: "A value-preserving re-serialization of the same computed styles on the same node; it skips nodes with ref/handlers/dynamic children/dynamic classes/dangerous html and combinator subjects, so no JS identity, behaviour, or project selector is disturbed."
714
- },
715
- match: {
716
- where: [
717
- not(hasRef),
718
- not(hasEventHandlers),
719
- not(hasDynamicChildren),
720
- not(hasDynamicClasses),
721
- not(targetedByCombinator),
722
- isInert
723
- ]
1387
+ 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."
724
1388
  },
725
1389
  rewrite: {
726
1390
  rewriteClasses(computed2) {
@@ -728,35 +1392,304 @@ var paddingShorthand = pattern({
728
1392
  return fold ? withFoldedPadding(computed2, fold) : null;
729
1393
  }
730
1394
  },
731
- examples: [
732
- {
733
- // The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
734
- // minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
735
- // replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
736
- before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
737
- after: '<div className="bg-red-200 p-4">box</div>'
738
- },
739
- {
740
- // Asymmetric padding (top != bottom) cannot fold into a shorthand.
741
- noMatch: '<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>'
1395
+ test: {
1396
+ cases: [
1397
+ {
1398
+ // The four equal padding longhands collapse to a `padding` shorthand at the IR level, and the
1399
+ // minimizing reverse-emit picks the single shortest utility (`p-4`) that reproduces it,
1400
+ // replacing the four `p{t,r,b,l}-4` tokens. `bg-red-200` is preserved (its order is stable).
1401
+ before: '<div className="pt-4 pr-4 pb-4 pl-4 bg-red-200">box</div>',
1402
+ after: '<div className="bg-red-200 p-4">box</div>'
1403
+ },
1404
+ {
1405
+ // A dynamic `{x}` child no longer blocks compress: only the element's OWN class tokens are
1406
+ // rewritten (px-4 py-4 → p-4); the dynamic child is untouched by a class-only change. This is
1407
+ // the real-app common case (most elements have dynamic content).
1408
+ before: '<div className="px-4 py-4">{x}</div>',
1409
+ after: '<div className="p-4">{x}</div>'
1410
+ }
1411
+ ],
1412
+ // Asymmetric padding (top != bottom) cannot fold into a shorthand → left unchanged.
1413
+ noMatch: ['<div className="pt-2 pr-4 pb-8 pl-4 bg-red-200">box</div>']
1414
+ }
1415
+ });
1416
+
1417
+ // ../patterns/src/library/compress/place-shorthand.pattern.ts
1418
+ init_esm_shims();
1419
+ var ALIGN_ITEMS = "align-items";
1420
+ var JUSTIFY_ITEMS = "justify-items";
1421
+ var PLACE_ITEMS = "place-items";
1422
+ var ALIGN_CONTENT = "align-content";
1423
+ var JUSTIFY_CONTENT = "justify-content";
1424
+ var PLACE_CONTENT = "place-content";
1425
+ var BASE_KEY8 = conditionKey(BASE_CONDITION);
1426
+ function samePair(a, b) {
1427
+ return a !== void 0 && b !== void 0 && a.value === b.value && a.important === b.important;
1428
+ }
1429
+ function placeDecl(property, align) {
1430
+ return {
1431
+ property,
1432
+ value: align.value,
1433
+ important: align.important,
1434
+ relativeToParent: false,
1435
+ // alignment keywords (center/start/stretch/…) are not length-relative
1436
+ inherited: false
1437
+ // none of the place-* alignment properties are inherited
1438
+ };
1439
+ }
1440
+ function withBaseDecls2(sm, baseDecls) {
1441
+ const blocks = /* @__PURE__ */ new Map();
1442
+ for (const [key, block] of sm.blocks) {
1443
+ const decls = key === BASE_KEY8 ? new Map(baseDecls) : block.decls;
1444
+ blocks.set(key, { condition: block.condition, decls });
1445
+ }
1446
+ return { blocks };
1447
+ }
1448
+ var placeShorthand = definePattern({
1449
+ name: "place-shorthand",
1450
+ category: "compress/place-shorthand",
1451
+ safety: 1,
1452
+ doc: {
1453
+ title: "Collapse matching alignment pairs into `place-*` shorthands",
1454
+ 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.",
1455
+ before: '<div style="align-items:center;justify-items:center"/>',
1456
+ after: '<div style="place-items:center"/>',
1457
+ 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."
1458
+ },
1459
+ rewrite: {
1460
+ rewriteClasses(computed2) {
1461
+ const base = computed2.blocks.get(BASE_KEY8);
1462
+ if (!base) return null;
1463
+ const alignItems = base.decls.get(ALIGN_ITEMS);
1464
+ const justifyItems = base.decls.get(JUSTIFY_ITEMS);
1465
+ const alignContent = base.decls.get(ALIGN_CONTENT);
1466
+ const justifyContent = base.decls.get(JUSTIFY_CONTENT);
1467
+ const next = new Map(base.decls);
1468
+ let collapsed = false;
1469
+ if (samePair(alignItems, justifyItems)) {
1470
+ next.delete(ALIGN_ITEMS);
1471
+ next.delete(JUSTIFY_ITEMS);
1472
+ next.set(PLACE_ITEMS, placeDecl(PLACE_ITEMS, alignItems));
1473
+ collapsed = true;
1474
+ }
1475
+ if (samePair(alignContent, justifyContent)) {
1476
+ next.delete(ALIGN_CONTENT);
1477
+ next.delete(JUSTIFY_CONTENT);
1478
+ next.set(PLACE_CONTENT, placeDecl(PLACE_CONTENT, alignContent));
1479
+ collapsed = true;
1480
+ }
1481
+ if (!collapsed) return null;
1482
+ return withBaseDecls2(computed2, next);
1483
+ }
1484
+ },
1485
+ test: {
1486
+ cases: [
1487
+ {
1488
+ // The matching items pair collapses to a `place-items` decl at the IR level; the minimizing
1489
+ // reverse-emit picks the single utility covering both (`place-items-center`), replacing the
1490
+ // `items-center`+`justify-items-center` pair. `bg-red-200` is preserved.
1491
+ before: '<div className="items-center justify-items-center bg-red-200">box</div>',
1492
+ after: '<div className="bg-red-200 place-items-center">box</div>'
1493
+ }
1494
+ ],
1495
+ // Mismatched alignment (align-items != justify-items, no content pair) → nothing collapses.
1496
+ noMatch: ['<div className="items-center justify-items-start bg-red-200">box</div>']
1497
+ }
1498
+ });
1499
+
1500
+ // ../patterns/src/library/compress/scroll-margin-shorthand.pattern.ts
1501
+ init_esm_shims();
1502
+ var SCROLL_MARGIN_SIDES = [
1503
+ "scroll-margin-top",
1504
+ "scroll-margin-right",
1505
+ "scroll-margin-bottom",
1506
+ "scroll-margin-left"
1507
+ ];
1508
+ var SIDE_SET = new Set(SCROLL_MARGIN_SIDES);
1509
+ var BASE_KEY9 = conditionKey(BASE_CONDITION);
1510
+ var SCROLL_MARGIN = "scroll-margin";
1511
+ var NON_COLLAPSIBLE_VALUES3 = /* @__PURE__ */ new Set([
1512
+ "initial",
1513
+ "inherit",
1514
+ "unset",
1515
+ "revert",
1516
+ "revert-layer"
1517
+ ]);
1518
+ function analyzeScrollMargin(sm) {
1519
+ const block = sm.blocks.get(BASE_KEY9);
1520
+ if (!block) return null;
1521
+ const sides = [];
1522
+ for (const side of SCROLL_MARGIN_SIDES) {
1523
+ const decl = block.decls.get(side);
1524
+ if (!decl) return null;
1525
+ sides.push(decl);
1526
+ }
1527
+ const important = sides[0].important;
1528
+ if (!sides.every((d) => d.important === important)) return null;
1529
+ const value = String(sides[0].value);
1530
+ if (NON_COLLAPSIBLE_VALUES3.has(value)) return null;
1531
+ if (!sides.every((d) => String(d.value) === value)) return null;
1532
+ const relative = sides.some((d) => d.relativeToParent);
1533
+ return { value, important, relative };
1534
+ }
1535
+ function withFoldedScrollMargin(sm, fold) {
1536
+ const blocks = /* @__PURE__ */ new Map();
1537
+ for (const [key, block] of sm.blocks) {
1538
+ if (key !== BASE_KEY9) {
1539
+ blocks.set(key, block);
1540
+ continue;
1541
+ }
1542
+ const decls = /* @__PURE__ */ new Map();
1543
+ for (const [prop, decl] of block.decls) {
1544
+ if (SIDE_SET.has(String(prop))) continue;
1545
+ decls.set(prop, decl);
1546
+ }
1547
+ const shorthand = {
1548
+ property: SCROLL_MARGIN,
1549
+ value: fold.value,
1550
+ important: fold.important,
1551
+ relativeToParent: fold.relative,
1552
+ inherited: false
1553
+ // scroll-margin is never inherited
1554
+ };
1555
+ decls.set(shorthand.property, shorthand);
1556
+ blocks.set(key, { condition: block.condition, decls });
1557
+ }
1558
+ return { blocks };
1559
+ }
1560
+ var scrollMarginShorthand = definePattern({
1561
+ name: "scroll-margin-shorthand",
1562
+ category: "compress/scroll-margin-shorthand",
1563
+ safety: 1,
1564
+ doc: {
1565
+ title: "Collapse equal scroll-margin sides into scroll-margin",
1566
+ 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).",
1567
+ before: '<div class="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4"/>',
1568
+ after: '<div class="scroll-m-4"/>',
1569
+ 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."
1570
+ },
1571
+ rewrite: {
1572
+ rewriteClasses(computed2) {
1573
+ const fold = analyzeScrollMargin(computed2);
1574
+ return fold ? withFoldedScrollMargin(computed2, fold) : null;
1575
+ }
1576
+ },
1577
+ test: {
1578
+ cases: [
1579
+ {
1580
+ // The four equal scroll-margin longhands collapse to a `scroll-margin` decl at the IR level; the
1581
+ // minimizing reverse-emit then picks the single shortest utility (`scroll-m-4`) that reproduces
1582
+ // it, replacing the four `scroll-m{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
1583
+ before: '<div className="scroll-mt-4 scroll-mr-4 scroll-mb-4 scroll-ml-4 bg-red-200">box</div>',
1584
+ after: '<div className="bg-red-200 scroll-m-4">box</div>'
1585
+ }
1586
+ ],
1587
+ // Sides differ (top != bottom) → no all-equal collapse.
1588
+ noMatch: ['<div className="scroll-mt-2 scroll-mr-4 scroll-mb-8 scroll-ml-4 bg-red-200">box</div>']
1589
+ }
1590
+ });
1591
+
1592
+ // ../patterns/src/library/compress/scroll-padding-shorthand.pattern.ts
1593
+ init_esm_shims();
1594
+ var SCROLL_PADDING_SIDES = [
1595
+ "scroll-padding-top",
1596
+ "scroll-padding-right",
1597
+ "scroll-padding-bottom",
1598
+ "scroll-padding-left"
1599
+ ];
1600
+ var SIDE_SET2 = new Set(SCROLL_PADDING_SIDES);
1601
+ var BASE_KEY10 = conditionKey(BASE_CONDITION);
1602
+ var SCROLL_PADDING = "scroll-padding";
1603
+ var NON_COLLAPSIBLE_VALUES4 = /* @__PURE__ */ new Set([
1604
+ "initial",
1605
+ "inherit",
1606
+ "unset",
1607
+ "revert",
1608
+ "revert-layer"
1609
+ ]);
1610
+ function analyzeScrollPadding(sm) {
1611
+ const block = sm.blocks.get(BASE_KEY10);
1612
+ if (!block) return null;
1613
+ const sides = [];
1614
+ for (const side of SCROLL_PADDING_SIDES) {
1615
+ const decl = block.decls.get(side);
1616
+ if (!decl) return null;
1617
+ sides.push(decl);
1618
+ }
1619
+ const important = sides[0].important;
1620
+ if (!sides.every((d) => d.important === important)) return null;
1621
+ const value = String(sides[0].value);
1622
+ if (NON_COLLAPSIBLE_VALUES4.has(value)) return null;
1623
+ if (!sides.every((d) => String(d.value) === value)) return null;
1624
+ const relative = sides.some((d) => d.relativeToParent);
1625
+ return { value, important, relative };
1626
+ }
1627
+ function withFoldedScrollPadding(sm, fold) {
1628
+ const blocks = /* @__PURE__ */ new Map();
1629
+ for (const [key, block] of sm.blocks) {
1630
+ if (key !== BASE_KEY10) {
1631
+ blocks.set(key, block);
1632
+ continue;
1633
+ }
1634
+ const decls = /* @__PURE__ */ new Map();
1635
+ for (const [prop, decl] of block.decls) {
1636
+ if (SIDE_SET2.has(String(prop))) continue;
1637
+ decls.set(prop, decl);
1638
+ }
1639
+ const shorthand = {
1640
+ property: SCROLL_PADDING,
1641
+ value: fold.value,
1642
+ important: fold.important,
1643
+ relativeToParent: fold.relative,
1644
+ inherited: false
1645
+ // scroll-padding is never inherited
1646
+ };
1647
+ decls.set(shorthand.property, shorthand);
1648
+ blocks.set(key, { condition: block.condition, decls });
1649
+ }
1650
+ return { blocks };
1651
+ }
1652
+ var scrollPaddingShorthand = definePattern({
1653
+ name: "scroll-padding-shorthand",
1654
+ category: "compress/scroll-padding-shorthand",
1655
+ safety: 1,
1656
+ doc: {
1657
+ title: "Collapse equal scroll-padding sides into scroll-padding",
1658
+ 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).",
1659
+ before: '<div class="scroll-pt-4 scroll-pr-4 scroll-pb-4 scroll-pl-4"/>',
1660
+ after: '<div class="scroll-p-4"/>',
1661
+ 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."
1662
+ },
1663
+ rewrite: {
1664
+ rewriteClasses(computed2) {
1665
+ const fold = analyzeScrollPadding(computed2);
1666
+ return fold ? withFoldedScrollPadding(computed2, fold) : null;
742
1667
  }
743
- ]
1668
+ },
1669
+ test: {
1670
+ cases: [
1671
+ {
1672
+ // The four equal scroll-padding longhands collapse to a `scroll-padding` decl at the IR level;
1673
+ // the minimizing reverse-emit then picks the single shortest utility (`scroll-p-4`) that
1674
+ // reproduces it, replacing the four `scroll-p{t,r,b,l}-4` tokens. `bg-red-200` is preserved.
1675
+ before: '<div className="scroll-pt-4 scroll-pr-4 scroll-pb-4 scroll-pl-4 bg-red-200">box</div>',
1676
+ after: '<div className="bg-red-200 scroll-p-4">box</div>'
1677
+ }
1678
+ ],
1679
+ // Sides differ (top != bottom) → no all-equal collapse.
1680
+ noMatch: ['<div className="scroll-pt-2 scroll-pr-4 scroll-pb-8 scroll-pl-4 bg-red-200">box</div>']
1681
+ }
744
1682
  });
745
1683
 
746
- // ../patterns/src/compress/size-shorthand.pattern.ts
1684
+ // ../patterns/src/library/compress/size-shorthand.pattern.ts
747
1685
  init_esm_shims();
748
1686
  var WIDTH = "width";
749
1687
  var HEIGHT = "height";
750
1688
  var SIZE = "size";
751
- var NON_COLLAPSIBLE_VALUES = /* @__PURE__ */ new Set(["auto", "initial", "unset"]);
752
- function asElement2(node) {
753
- const n = node;
754
- return n.kind === "element" ? n : null;
755
- }
1689
+ var NON_COLLAPSIBLE_VALUES5 = /* @__PURE__ */ new Set(["auto", "initial", "unset"]);
756
1690
  function baseBlock(sm) {
757
1691
  return sm.blocks.get(conditionKey(BASE_CONDITION));
758
1692
  }
759
- var hasDangerousHtml3 = (node) => asElement2(node)?.meta.hasDangerousHtml ?? false;
760
1693
  function withSizeShorthand(sm, value, important) {
761
1694
  const baseKey = conditionKey(BASE_CONDITION);
762
1695
  const blocks = /* @__PURE__ */ new Map();
@@ -775,7 +1708,7 @@ function withSizeShorthand(sm, value, important) {
775
1708
  }
776
1709
  return { blocks };
777
1710
  }
778
- var sizeShorthand = pattern({
1711
+ var sizeShorthand = definePattern({
779
1712
  name: "size-shorthand",
780
1713
  category: "compress/size-shorthand",
781
1714
  safety: 2,
@@ -784,17 +1717,7 @@ var sizeShorthand = pattern({
784
1717
  summary: "An element whose computed width and height are equal is rewritten to the single Tailwind size-* utility (size-* === width + height at the same value).",
785
1718
  before: '<div style="width:1rem;height:1rem"/>',
786
1719
  after: '<div class="size-4"/>',
787
- safetyRationale: "size-* is value-identical to equal width+height; the element carries no ref/handlers/dynamic children/dangerous HTML, no dynamic class segment, and is not a combinator subject."
788
- },
789
- match: {
790
- where: [
791
- not(hasRef),
792
- not(hasEventHandlers),
793
- not(hasDynamicChildren),
794
- not(hasDangerousHtml3),
795
- not(hasDynamicClasses),
796
- not(targetedByCombinator)
797
- ]
1720
+ safetyRationale: "`size-*` is value-identical to equal width+height \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."
798
1721
  },
799
1722
  rewrite: {
800
1723
  rewriteClasses(computed2) {
@@ -803,1425 +1726,1674 @@ var sizeShorthand = pattern({
803
1726
  const h = base?.decls.get(HEIGHT);
804
1727
  if (!w || !h) return null;
805
1728
  if (w.important !== h.important) return null;
806
- if (NON_COLLAPSIBLE_VALUES.has(String(w.value))) return null;
1729
+ if (NON_COLLAPSIBLE_VALUES5.has(String(w.value))) return null;
807
1730
  if (w.value !== h.value) return null;
808
1731
  return withSizeShorthand(computed2, String(w.value), w.important);
809
1732
  }
810
1733
  },
811
- examples: [
812
- {
813
- // Equal width/height collapse to a `size` decl at the IR level; the minimizing reverse-emit
814
- // expands `size` back to width+height, finds the single utility covering both (`size-10`), and
815
- // replaces the `h-10`+`w-10` pair with it. `bg-red-200` is preserved.
816
- before: '<div className="h-10 w-10 bg-red-200">box</div>',
817
- after: '<div className="bg-red-200 size-10">box</div>'
818
- },
819
- {
820
- // Width and height differ → no equal-axis collapse.
821
- noMatch: '<div className="h-10 w-20 bg-red-200">box</div>'
822
- }
823
- ]
1734
+ test: {
1735
+ cases: [
1736
+ {
1737
+ // Equal width/height collapse to a `size` decl at the IR level; the minimizing reverse-emit
1738
+ // expands `size` back to width+height, finds the single utility covering both (`size-10`), and
1739
+ // replaces the `h-10`+`w-10` pair with it. `bg-red-200` is preserved.
1740
+ before: '<div className="h-10 w-10 bg-red-200">box</div>',
1741
+ after: '<div className="bg-red-200 size-10">box</div>'
1742
+ }
1743
+ ],
1744
+ // Width and height differ → no equal-axis collapse.
1745
+ noMatch: ['<div className="h-10 w-20 bg-red-200">box</div>']
1746
+ }
824
1747
  });
825
1748
 
826
1749
  // ../patterns/src/_registry.generated.ts
827
1750
  init_esm_shims();
828
1751
  var builtinPatterns = [
1752
+ displayContentsWrapper,
829
1753
  emptyStyleDiv,
830
1754
  flexCenterWrapper,
1755
+ inlineFlexCenterWrapper,
831
1756
  nestedFlexMerge,
1757
+ nestedGridMerge,
832
1758
  passthroughWrapper,
833
1759
  redundantFragment,
1760
+ redundantInlineWrapper,
1761
+ borderRadiusShorthand,
1762
+ borderShorthand,
834
1763
  dedupeClasses,
1764
+ gapShorthand,
835
1765
  insetShorthand,
836
1766
  marginShorthand,
1767
+ overflowShorthand,
1768
+ overscrollBehaviorShorthand,
837
1769
  paddingShorthand,
1770
+ placeShorthand,
1771
+ scrollMarginShorthand,
1772
+ scrollPaddingShorthand,
838
1773
  sizeShorthand
839
1774
  ];
840
1775
 
841
1776
  // ../patterns/src/index.ts
842
1777
  init_esm_shims();
843
1778
 
844
- // ../frontend-jsx/src/frontend.ts
1779
+ // ../resolver-tailwind/src/tailwind/resolver.ts
845
1780
  init_esm_shims();
846
- import { parse as babelParse } from "@babel/parser";
847
- import babelTraverse from "@babel/traverse";
848
- var traverse = typeof babelTraverse === "function" ? babelTraverse : babelTraverse.default;
849
- var JSX_LANGS = ["jsx", "tsx"];
850
- var FILE_ID = 1;
851
- function jsxName(node) {
852
- switch (node.type) {
853
- case "JSXIdentifier":
854
- return node.name;
855
- case "JSXMemberExpression":
856
- return `${jsxName(node.object)}.${node.property.name}`;
857
- case "JSXNamespacedName":
858
- return `${node.namespace.name}:${node.name.name}`;
1781
+
1782
+ // ../resolver-tailwind/src/tailwind/emit.ts
1783
+ init_esm_shims();
1784
+
1785
+ // ../resolver-tailwind/src/tailwind/fingerprint.ts
1786
+ init_esm_shims();
1787
+ function fnv1a(input) {
1788
+ let h = 2166136261;
1789
+ for (let i = 0; i < input.length; i += 1) {
1790
+ h ^= input.charCodeAt(i);
1791
+ h = Math.imul(h, 16777619);
859
1792
  }
1793
+ return (h >>> 0).toString(16).padStart(8, "0");
860
1794
  }
861
- function isComponentName(node) {
862
- if (node.type === "JSXMemberExpression") return true;
863
- if (node.type === "JSXNamespacedName") return false;
864
- return /^[A-Z]/.test(node.name);
865
- }
866
- function attrName(name) {
867
- return name.type === "JSXNamespacedName" ? `${name.namespace.name}:${name.name.name}` : name.name;
868
- }
869
- function exprKind(node) {
870
- switch (node.type) {
871
- case "CallExpression":
872
- case "OptionalCallExpression":
873
- return "call";
874
- case "MemberExpression":
875
- case "OptionalMemberExpression":
876
- return "member";
877
- case "ConditionalExpression":
878
- case "LogicalExpression":
879
- return "conditional";
880
- case "TemplateLiteral":
881
- case "TaggedTemplateExpression":
882
- return "template";
883
- case "Identifier":
884
- return "identifier";
885
- case "SpreadElement":
886
- return "spread";
887
- default:
888
- return "other";
1795
+
1796
+ // ../resolver-tailwind/src/tailwind/emit.ts
1797
+ function expandForEmit(norm, prop, value, important) {
1798
+ const pairsFor = (p, v) => norm.normalizeDeclaration(p, v, important).map((d) => [d.property, String(d.value)]);
1799
+ if (prop === "size") {
1800
+ return [...pairsFor("width", value), ...pairsFor("height", value)];
1801
+ }
1802
+ if (prop === "inset-block" || prop === "inset-inline") {
1803
+ const parts = value.split(/\s+/).filter((s) => s.length > 0);
1804
+ const a = parts[0] ?? value;
1805
+ const b = parts[1] ?? a;
1806
+ const sides = prop === "inset-block" ? ["top", "bottom"] : ["left", "right"];
1807
+ return [...pairsFor(sides[0], a), ...pairsFor(sides[1], b)];
889
1808
  }
1809
+ return pairsFor(prop, value);
890
1810
  }
891
- function classFormOf(node) {
892
- switch (node.type) {
893
- case "TemplateLiteral":
894
- case "TaggedTemplateExpression":
895
- return "template-literal";
896
- case "CallExpression":
897
- case "OptionalCallExpression":
898
- return "call";
899
- case "ConditionalExpression":
900
- case "LogicalExpression":
901
- return "conditional";
902
- case "MemberExpression":
903
- case "OptionalMemberExpression":
904
- case "Identifier":
905
- return "member";
906
- default:
907
- return "call";
1811
+ function synthesizeResidual(remaining, ctx) {
1812
+ if (remaining.size === 0) return void 0;
1813
+ const norm = ctx.normalizer ?? normalizer;
1814
+ const decls = /* @__PURE__ */ new Map();
1815
+ for (const [prop, value] of remaining) {
1816
+ for (const decl of norm.normalizeDeclaration(String(prop), value, false)) {
1817
+ decls.set(decl.property, decl);
1818
+ }
1819
+ }
1820
+ const block = { condition: BASE_CONDITION, decls };
1821
+ const styleMap = { blocks: /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]) };
1822
+ const css = [...remaining].map(([p, v]) => `${p}:${v}`).join(";");
1823
+ const className = `df-${fnv1a(css)}`;
1824
+ const synthetic = { className, decls: styleMap, css: `.${className}{${css}}` };
1825
+ try {
1826
+ ctx.sink.register(synthetic);
1827
+ } catch {
908
1828
  }
1829
+ return synthetic;
909
1830
  }
910
- function looksLikeJsx(id, code) {
911
- if (/\.[jt]sx$/i.test(id)) return true;
912
- return /<\/?[A-Za-z][\w.-]*|<>/.test(code);
1831
+
1832
+ // ../resolver-tailwind/src/tailwind/engine.ts
1833
+ init_esm_shims();
1834
+ import { createRequire } from "module";
1835
+ import * as path from "path";
1836
+ function moduleBase() {
1837
+ return typeof __filename === "string" ? __filename : import.meta.url;
913
1838
  }
914
- function doParse(code, ctx) {
915
- const diagnostics = [];
916
- const doc = createDocument("jsx");
917
- const backref = doc.backref;
918
- const ast = babelParse(code, {
919
- sourceType: "module",
920
- plugins: ["jsx", "typescript"]
921
- });
922
- const eol = code.includes("\r\n") ? "\r\n" : "\n";
923
- const sourceFile = {
924
- id: FILE_ID,
925
- path: ctx.id,
926
- text: code,
927
- frontend: "jsx",
928
- eol,
929
- indentUnit: " ",
930
- native: ast
931
- };
932
- doc.sources.set(FILE_ID, sourceFile);
933
- const spanOf = (node) => {
934
- if (node.start == null || node.end == null) return null;
935
- const span = {
936
- file: FILE_ID,
937
- start: node.start,
938
- end: node.end,
939
- startLoc: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : void 0,
940
- endLoc: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : void 0
941
- };
942
- return span;
943
- };
944
- const sliceOf = (node) => node.start == null || node.end == null ? "" : code.slice(node.start, node.end);
945
- const internExpr = (node, spread) => {
946
- const payload = { text: sliceOf(node), spread };
947
- return doc.exprs.intern({
948
- span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
949
- kind: exprKind(node),
950
- payload
951
- });
952
- };
953
- const splitTokens = (raw) => raw.split(/\s+/).filter((t) => t.length > 0).map((value) => ({ value }));
954
- const buildClassList = (attr) => {
955
- const attrSpan = spanOf(attr) ?? void 0;
956
- const v = attr.value;
957
- const staticList = (tokens, valueSpan) => {
958
- const seg = { kind: "static", span: valueSpan ?? void 0, tokens };
959
- return {
960
- form: "string-literal",
961
- segments: [seg],
962
- valueSpan,
963
- attrSpan,
964
- hasDynamic: false,
965
- opaque: false,
966
- rewritable: true
967
- };
968
- };
969
- if (v == null) return staticList([], null);
970
- if (v.type === "StringLiteral") {
971
- return staticList(splitTokens(v.value), spanOf(v));
972
- }
973
- if (v.type === "JSXExpressionContainer") {
974
- const expr = v.expression;
975
- if (expr.type === "StringLiteral") {
976
- return staticList(splitTokens(expr.value), spanOf(expr));
977
- }
978
- if (expr.type === "JSXEmptyExpression") return staticList([], null);
979
- const ref = internExpr(expr, false);
980
- const valueSpan = spanOf(expr);
981
- const seg = { kind: "dynamic", span: valueSpan ?? void 0, expr: ref };
982
- return {
983
- form: classFormOf(expr),
984
- segments: [seg],
985
- valueSpan,
986
- attrSpan,
987
- hasDynamic: true,
988
- opaque: true,
989
- rewritable: false
990
- };
991
- }
992
- return emptyClassList();
993
- };
994
- const staticTokensOf2 = (classes) => {
995
- const out = [];
996
- for (const seg of classes.segments) {
997
- if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
1839
+ function projectRequire(projectRoot) {
1840
+ const bases = [];
1841
+ if (projectRoot) bases.push(path.join(projectRoot, "__domflax__.js"));
1842
+ bases.push(path.join(process.cwd(), "__domflax__.js"));
1843
+ bases.push(moduleBase());
1844
+ for (const base of bases) {
1845
+ try {
1846
+ const candidate = createRequire(base);
1847
+ candidate.resolve("tailwindcss/package.json");
1848
+ return candidate;
1849
+ } catch {
998
1850
  }
999
- return out;
1000
- };
1001
- const buildAttrValue = (attr) => {
1002
- const v = attr.value;
1003
- if (v == null) return { kind: "static", value: true, span: spanOf(attr) ?? void 0 };
1004
- if (v.type === "StringLiteral") {
1005
- return { kind: "static", value: v.value, span: spanOf(v) ?? void 0 };
1851
+ }
1852
+ return null;
1853
+ }
1854
+ function loadEngine(options) {
1855
+ const req = projectRequire(options.projectRoot);
1856
+ if (!req) return null;
1857
+ try {
1858
+ const resolveConfig = req("tailwindcss/resolveConfig.js");
1859
+ const { createContext } = req("tailwindcss/lib/lib/setupContextUtils.js");
1860
+ const { generateRules } = req("tailwindcss/lib/lib/generateRules.js");
1861
+ const pkg = req("tailwindcss/package.json");
1862
+ let userConfig = options.config ?? { content: [{ raw: "" }] };
1863
+ if (options.configPath !== void 0) {
1864
+ const loadConfig = req("tailwindcss/loadConfig.js");
1865
+ userConfig = loadConfig(options.configPath);
1006
1866
  }
1007
- if (v.type === "JSXExpressionContainer") {
1008
- if (v.expression.type === "JSXEmptyExpression") {
1009
- return { kind: "static", value: true, span: spanOf(v) ?? void 0 };
1867
+ const resolved = resolveConfig(userConfig);
1868
+ const context = createContext(resolved);
1869
+ return {
1870
+ version: pkg.version,
1871
+ context,
1872
+ generate(candidates) {
1873
+ const rules = generateRules(new Set(candidates), context);
1874
+ return rules.map(([, node]) => node);
1010
1875
  }
1011
- return { kind: "dynamic", expr: internExpr(v.expression, false), span: spanOf(v) ?? void 0 };
1876
+ };
1877
+ } catch {
1878
+ return null;
1879
+ }
1880
+ }
1881
+
1882
+ // ../resolver-tailwind/src/tailwind/extract.ts
1883
+ init_esm_shims();
1884
+
1885
+ // ../resolver-tailwind/src/tailwind/selector.ts
1886
+ init_esm_shims();
1887
+ var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set([
1888
+ ":before",
1889
+ ":after",
1890
+ ":first-line",
1891
+ ":first-letter"
1892
+ ]);
1893
+ function parseSelector(selector) {
1894
+ const sel = selector.trim();
1895
+ if (sel.length === 0 || sel[0] !== ".") return { kind: "complex" };
1896
+ let i = 1;
1897
+ for (; i < sel.length; i += 1) {
1898
+ const c = sel[i];
1899
+ if (c === "\\") {
1900
+ i += 1;
1901
+ continue;
1012
1902
  }
1013
- return { kind: "dynamic", expr: internExpr(v, false), span: spanOf(v) ?? void 0 };
1014
- };
1015
- const buildChild = (node, parentId) => {
1016
- switch (node.type) {
1017
- case "JSXText": {
1018
- const id = doc.alloc.next();
1019
- doc.nodes.set(
1020
- id,
1021
- createText(id, node.value, {
1022
- parent: parentId,
1023
- span: spanOf(node),
1024
- collapsible: /^\s*$/.test(node.value)
1025
- })
1026
- );
1027
- return id;
1028
- }
1029
- case "JSXExpressionContainer": {
1030
- if (node.expression.type === "JSXEmptyExpression") return null;
1031
- const id = doc.alloc.next();
1032
- const ref = internExpr(node.expression, false);
1033
- doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
1034
- return id;
1035
- }
1036
- case "JSXSpreadChild": {
1037
- const id = doc.alloc.next();
1038
- const ref = internExpr(node.expression, true);
1039
- doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
1040
- return id;
1041
- }
1042
- case "JSXElement":
1043
- return buildElement(node, parentId);
1044
- case "JSXFragment":
1045
- return buildFragment(node, parentId);
1046
- default:
1047
- return null;
1903
+ if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
1904
+ break;
1048
1905
  }
1049
- };
1050
- const buildFragment = (node, parentId) => {
1051
- const id = doc.alloc.next();
1052
- const children = [];
1053
- for (const c of node.children) {
1054
- const cid = buildChild(c, id);
1055
- if (cid != null) children.push(cid);
1906
+ }
1907
+ const remainder = sel.slice(i);
1908
+ if (remainder.length === 0) {
1909
+ return { kind: "simple", states: [], pseudoElement: "" };
1910
+ }
1911
+ if (!/^(?:::?[-a-z]+(?:\([^()]*\))?)+$/i.test(remainder)) {
1912
+ return { kind: "complex" };
1913
+ }
1914
+ const parts = remainder.match(/::?[-a-z]+(?:\([^()]*\))?/gi) ?? [];
1915
+ const states = [];
1916
+ let pseudoElement = "";
1917
+ for (const part of parts) {
1918
+ if (part.startsWith("::") || LEGACY_PSEUDO_ELEMENTS.has(part)) {
1919
+ pseudoElement = part.startsWith("::") ? part : `:${part}`;
1920
+ } else {
1921
+ states.push(part);
1056
1922
  }
1057
- doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
1058
- backref.set(id, {
1059
- nodeId: id,
1060
- span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
1061
- openTagSpan: spanOf(node.openingFragment),
1062
- closeTagSpan: spanOf(node.closingFragment),
1063
- innerSpan: null,
1064
- selfClosing: false
1065
- });
1066
- return id;
1923
+ }
1924
+ return { kind: "simple", states, pseudoElement };
1925
+ }
1926
+ function makeCondition(media, states, pseudoElement) {
1927
+ return {
1928
+ media,
1929
+ states: [...new Set(states)].sort(),
1930
+ pseudoElement
1067
1931
  };
1068
- const buildElement = (node, parentId) => {
1069
- const id = doc.alloc.next();
1070
- const opening = node.openingElement;
1071
- const tag = jsxName(opening.name);
1072
- const component = isComponentName(opening.name);
1073
- const meta = defaultMeta();
1074
- meta.isComponent = component;
1075
- let classes = emptyClassList();
1076
- const entries = /* @__PURE__ */ new Map();
1077
- const order = [];
1078
- const spreads = [];
1079
- for (const attr of opening.attributes) {
1080
- if (attr.type === "JSXSpreadAttribute") {
1081
- spreads.push(internExpr(attr.argument, true));
1082
- meta.hasSpreadAttrs = true;
1083
- continue;
1084
- }
1085
- const name = attrName(attr.name);
1086
- if (name === "className" || name === "class") {
1087
- classes = buildClassList(attr);
1088
- continue;
1089
- }
1090
- if (name === "ref") meta.hasRef = true;
1091
- else if (name === "key") meta.hasKey = true;
1092
- else if (name === "dangerouslySetInnerHTML") meta.hasDangerousHtml = true;
1093
- else if (/^on[A-Z]/.test(name)) meta.hasEventHandlers = true;
1094
- entries.set(name, buildAttrValue(attr));
1095
- order.push(name);
1096
- }
1097
- const attrs = { entries, spreads, order };
1098
- const children = [];
1099
- for (const c of node.children) {
1100
- const cid = buildChild(c, id);
1101
- if (cid != null) children.push(cid);
1102
- }
1103
- for (const cid of children) {
1104
- const cn = doc.nodes.get(cid);
1105
- if (cn && cn.kind === "expr") {
1106
- meta.hasDynamicChildren = true;
1107
- break;
1108
- }
1932
+ }
1933
+ function unescapeClass(selector) {
1934
+ const sel = selector.trim();
1935
+ if (sel[0] !== ".") return null;
1936
+ let out = "";
1937
+ for (let i = 1; i < sel.length; i += 1) {
1938
+ const c = sel[i];
1939
+ if (c === "\\") {
1940
+ i += 1;
1941
+ if (i < sel.length) out += sel[i];
1942
+ continue;
1109
1943
  }
1110
- let computed2 = emptyStyleMap();
1111
- if (!classes.hasDynamic) {
1112
- const tokens = staticTokensOf2(classes);
1113
- if (tokens.length > 0) {
1114
- const res = ctx.resolver.resolve({
1115
- classes: tokens,
1116
- element: { tagName: tag, namespace: component ? void 0 : "html" }
1117
- });
1118
- computed2 = ctx.normalizer.normalizeStyleMap(res.styles);
1119
- for (const w of res.warnings) {
1120
- diagnostics.push({
1121
- code: "DF_STYLE_CONFLICT_UNRESOLVED",
1122
- severity: w.severity,
1123
- message: w.message,
1124
- nodeId: id
1125
- });
1126
- }
1127
- }
1944
+ if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
1945
+ return null;
1128
1946
  }
1129
- const namespace = component ? "component" : "html";
1130
- const el = createElement(id, {
1131
- tag,
1132
- namespace,
1133
- isComponent: component,
1134
- selfClosing: opening.selfClosing,
1135
- classes,
1136
- computed: computed2,
1137
- attrs,
1138
- children,
1139
- parent: parentId,
1140
- span: spanOf(node),
1141
- meta
1142
- });
1143
- doc.nodes.set(id, el);
1144
- const inner = children.length > 0 ? spanOf(node.children[0]) && spanOf(node.children.at(-1)) ? {
1145
- file: FILE_ID,
1146
- start: spanOf(node.children[0]).start,
1147
- end: spanOf(node.children.at(-1)).end
1148
- } : null : null;
1149
- backref.set(id, {
1150
- nodeId: id,
1151
- span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
1152
- openTagSpan: spanOf(opening),
1153
- closeTagSpan: node.closingElement ? spanOf(node.closingElement) : null,
1154
- innerSpan: inner,
1155
- selfClosing: opening.selfClosing
1156
- });
1157
- return id;
1158
- };
1159
- const roots = [];
1160
- traverse(ast, {
1161
- JSXElement(path3) {
1162
- roots.push(path3.node);
1163
- path3.skip();
1164
- },
1165
- JSXFragment(path3) {
1166
- roots.push(path3.node);
1167
- path3.skip();
1947
+ out += c;
1948
+ }
1949
+ return out.length > 0 ? out : null;
1950
+ }
1951
+
1952
+ // ../resolver-tailwind/src/tailwind/extract.ts
1953
+ function collectRules(node, mediaStack, inUnsupportedAtRule, out) {
1954
+ if (node.type === "rule") {
1955
+ out.push({ rule: node, media: mediaStack, unsupported: inUnsupportedAtRule });
1956
+ return;
1957
+ }
1958
+ if (node.type === "atrule") {
1959
+ const at = node;
1960
+ const children = at.nodes ?? [];
1961
+ if (at.name === "media") {
1962
+ const nextStack = at.params ? [...mediaStack, at.params] : mediaStack;
1963
+ for (const child of children) collectRules(child, nextStack, inUnsupportedAtRule, out);
1964
+ } else {
1965
+ for (const child of children) collectRules(child, mediaStack, true, out);
1168
1966
  }
1169
- });
1170
- const rootFrag = doc.nodes.get(doc.root);
1171
- for (const r of roots) {
1172
- const id = r.type === "JSXFragment" ? buildFragment(r, doc.root) : buildElement(r, doc.root);
1173
- rootFrag.children.push(id);
1174
1967
  }
1175
- return { doc, diagnostics };
1176
1968
  }
1177
- var jsxFrontend = {
1178
- name: "babel-jsx",
1179
- langs: JSX_LANGS,
1180
- canParse(id, code) {
1181
- return looksLikeJsx(id, code);
1182
- },
1183
- parse(code, ctx) {
1184
- return doParse(code, ctx);
1969
+ function extractToken(token, nodes) {
1970
+ if (nodes.length === 0) return { blocks: [], produced: false };
1971
+ const leaves = [];
1972
+ for (const node of nodes) collectRules(node, [], false, leaves);
1973
+ const blocks = [];
1974
+ let sawComplex = false;
1975
+ for (const { rule, media, unsupported } of leaves) {
1976
+ const parsed = parseSelector(rule.selector);
1977
+ if (parsed.kind === "complex" || unsupported) {
1978
+ sawComplex = true;
1979
+ continue;
1980
+ }
1981
+ const decls = [];
1982
+ for (const child of rule.nodes ?? []) {
1983
+ if (child.type !== "decl") continue;
1984
+ const d = child;
1985
+ if (typeof d.value !== "string") continue;
1986
+ decls.push([d.prop, d.value, d.important === true]);
1987
+ }
1988
+ if (decls.length === 0) continue;
1989
+ const mediaQuery = media.join(" and ");
1990
+ blocks.push({ condition: makeCondition(mediaQuery, parsed.states, parsed.pseudoElement), decls });
1185
1991
  }
1186
- };
1187
- function createJsxFrontend() {
1188
- return jsxFrontend;
1992
+ const opaque = sawComplex && blocks.length === 0 ? { token, reason: "combinator-variant", detail: "utility targets descendants/siblings, not its own box" } : void 0;
1993
+ return { blocks, produced: true, opaque };
1189
1994
  }
1190
1995
 
1191
- // ../frontend-jsx/src/backend.ts
1996
+ // ../resolver-tailwind/src/tailwind/serialize.ts
1192
1997
  init_esm_shims();
1193
- import MagicString from "magic-string";
1194
- var JSX_LANGS2 = ["jsx", "tsx"];
1195
- function exprText(doc, ref) {
1196
- const rec = doc.exprs.get(ref);
1197
- const payload = rec?.payload;
1198
- if (payload && typeof payload.text === "string") {
1199
- return { text: payload.text, spread: payload.spread === true };
1998
+ function serializeCssNode(node) {
1999
+ if (node.type === "decl") {
2000
+ const d = node;
2001
+ if (typeof d.value !== "string") return "";
2002
+ return `${d.prop}:${d.value}${d.important === true ? " !important" : ""}`;
1200
2003
  }
1201
- if (rec) {
1202
- const sf = doc.sources.get(rec.span.file);
1203
- if (sf) return { text: sf.text.slice(rec.span.start, rec.span.end), spread: false };
2004
+ if (node.type === "rule") {
2005
+ const r = node;
2006
+ const body = (r.nodes ?? []).map((c) => serializeCssNode(c)).filter((s) => s.length > 0).join(";");
2007
+ return `${r.selector}{${body}}`;
1204
2008
  }
1205
- return { text: "", spread: false };
1206
- }
1207
- function staticTokensOf(classes) {
1208
- const out = [];
1209
- for (const seg of classes.segments) {
1210
- if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
2009
+ if (node.type === "atrule") {
2010
+ const a = node;
2011
+ const body = (a.nodes ?? []).map((c) => serializeCssNode(c)).filter((s) => s.length > 0).join("");
2012
+ return `@${a.name} ${a.params}{${body}}`;
1211
2013
  }
1212
- return out;
2014
+ return "";
1213
2015
  }
1214
- function primarySource(doc) {
1215
- for (const sf of doc.sources.values()) {
1216
- if (typeof sf.text === "string" && sf.text.length > 0) return sf;
2016
+
2017
+ // ../resolver-tailwind/src/tailwind/stylemap.ts
2018
+ init_esm_shims();
2019
+ function buildStyleMap(blockMaps) {
2020
+ if (blockMaps.size === 0) return emptyStyleMap();
2021
+ const blocks = /* @__PURE__ */ new Map();
2022
+ for (const { condition, decls } of blockMaps.values()) {
2023
+ if (decls.size === 0) continue;
2024
+ blocks.set(conditionKey(condition), { condition, decls });
1217
2025
  }
1218
- return null;
2026
+ if (blocks.size === 0) return emptyStyleMap();
2027
+ return normalizer.normalizeStyleMap({ blocks });
1219
2028
  }
1220
- function collectKept(doc) {
2029
+ function shadowedBy(prev) {
1221
2030
  const out = [];
1222
2031
  const seen = /* @__PURE__ */ new Set();
1223
- const visit = (id) => {
1224
- if (seen.has(id)) return;
1225
- seen.add(id);
1226
- const n = doc.nodes.get(id);
1227
- if (!n) return;
1228
- out.push(n);
1229
- if (n.kind === "element" || n.kind === "fragment") for (const c of n.children) visit(c);
2032
+ const add = (o) => {
2033
+ if (!o || o.kind !== "class" || seen.has(o.className)) return;
2034
+ seen.add(o.className);
2035
+ out.push(o);
1230
2036
  };
1231
- visit(doc.root);
1232
- return out;
1233
- }
1234
- function strictlyContains(a, b) {
1235
- if (a.file !== b.file) return false;
1236
- if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
1237
- return false;
2037
+ for (const o of prev.shadowed ?? []) add(o);
2038
+ add(prev.origin);
2039
+ return out.length > 0 ? out : void 0;
1238
2040
  }
1239
- function editClasses(ms, doc, sf, el) {
1240
- const classes = el.classes;
1241
- if (classes.hasDynamic || classes.opaque) return false;
1242
- const tokens = staticTokensOf(classes);
1243
- const valueSpan = classes.valueSpan;
1244
- if (valueSpan && valueSpan.file === sf.id) {
1245
- const current = sf.text.slice(valueSpan.start, valueSpan.end);
1246
- const quote = current.startsWith("'") ? "'" : '"';
1247
- const next = `${quote}${tokens.join(" ")}${quote}`;
1248
- if (current !== next) {
1249
- ms.overwrite(valueSpan.start, valueSpan.end, next);
1250
- return true;
2041
+
2042
+ // ../resolver-tailwind/src/tailwind/usage.ts
2043
+ init_esm_shims();
2044
+ var OPAQUE_USAGE = {
2045
+ asSubject: true,
2046
+ asAncestor: true,
2047
+ asCompound: true,
2048
+ asSibling: true,
2049
+ asHasArgument: true,
2050
+ asStructural: true,
2051
+ droppable: false
2052
+ };
2053
+ var DROPPABLE_USAGE = {
2054
+ asSubject: true,
2055
+ asAncestor: false,
2056
+ asCompound: false,
2057
+ asSibling: false,
2058
+ asHasArgument: false,
2059
+ asStructural: false,
2060
+ droppable: true
2061
+ };
2062
+
2063
+ // ../resolver-tailwind/src/tailwind/resolver.ts
2064
+ var TailwindResolver = class {
2065
+ id = "tailwind";
2066
+ provider;
2067
+ fingerprint;
2068
+ #engine;
2069
+ /** Per-token extraction cache (engine output is pure for a fixed config). */
2070
+ #tokenCache = /* @__PURE__ */ new Map();
2071
+ /** Per-class-set forward-resolution cache. */
2072
+ #resolveCache = /* @__PURE__ */ new Map();
2073
+ /** Lazily built reverse index for {@link emit}. */
2074
+ #reverseIndex = null;
2075
+ constructor(config = {}) {
2076
+ this.#engine = loadEngine(config);
2077
+ this.provider = config.provider ?? (this.#engine ? `tailwindcss@${this.#engine.version}` : "tailwindcss");
2078
+ const seed = JSON.stringify(config.config ?? {}) + (config.configPath ?? "");
2079
+ this.fingerprint = config.fingerprint ?? `${this.provider}/${fnv1a(seed)}`;
2080
+ }
2081
+ /** Engine-backed, cached single-token extraction. */
2082
+ #extract(token) {
2083
+ const cached = this.#tokenCache.get(token);
2084
+ if (cached) return cached;
2085
+ let result;
2086
+ if (!this.#engine) {
2087
+ result = { blocks: [], produced: false };
2088
+ } else {
2089
+ try {
2090
+ result = extractToken(token, this.#engine.generate([token]));
2091
+ } catch {
2092
+ result = { blocks: [], produced: false };
2093
+ }
2094
+ }
2095
+ this.#tokenCache.set(token, result);
2096
+ return result;
2097
+ }
2098
+ owns(token) {
2099
+ if (token.length === 0) return false;
2100
+ return this.#extract(token).produced;
2101
+ }
2102
+ resolve(input) {
2103
+ const key = JSON.stringify(input.classes);
2104
+ const cached = this.#resolveCache.get(key);
2105
+ if (cached) return cached;
2106
+ const blockMaps = /* @__PURE__ */ new Map();
2107
+ const resolved = [];
2108
+ const unknown = [];
2109
+ const opaque = [];
2110
+ input.classes.forEach((token, tokenIndex) => {
2111
+ const extracted = this.#extract(token);
2112
+ if (!extracted.produced) {
2113
+ unknown.push(token);
2114
+ return;
2115
+ }
2116
+ if (extracted.opaque) opaque.push(extracted.opaque);
2117
+ if (extracted.blocks.length === 0) return;
2118
+ const origin = { kind: "class", tokenIndex, className: token };
2119
+ let contributed = false;
2120
+ for (const block of extracted.blocks) {
2121
+ const ck = conditionKey(block.condition);
2122
+ let bucket = blockMaps.get(ck);
2123
+ if (!bucket) {
2124
+ bucket = { condition: block.condition, decls: /* @__PURE__ */ new Map() };
2125
+ blockMaps.set(ck, bucket);
2126
+ }
2127
+ for (const [prop, value, important] of block.decls) {
2128
+ for (const decl of normalizer.normalizeDeclaration(prop, value, important)) {
2129
+ const prev = bucket.decls.get(decl.property);
2130
+ const shadowed = prev ? shadowedBy(prev) : void 0;
2131
+ bucket.decls.set(decl.property, shadowed ? { ...decl, origin, shadowed } : { ...decl, origin });
2132
+ contributed = true;
2133
+ }
2134
+ }
2135
+ }
2136
+ if (contributed) resolved.push(token);
2137
+ });
2138
+ const result = {
2139
+ styles: buildStyleMap(blockMaps),
2140
+ resolved,
2141
+ unknown,
2142
+ opaque,
2143
+ warnings: []
2144
+ };
2145
+ this.#resolveCache.set(key, result);
2146
+ return result;
2147
+ }
2148
+ /**
2149
+ * Lazily build the reverse index from the engine's own enumerable class list. Each indexable
2150
+ * utility maps to its NORMALIZED BASE longhand declarations (property → canonical value). Utilities
2151
+ * with variant conditions, combinator selectors, or no BASE declarations are skipped. Sorted by
2152
+ * declaration count (desc) so greedier (shorthand-like) utilities are tried first.
2153
+ */
2154
+ #buildReverseIndex() {
2155
+ if (this.#reverseIndex) return this.#reverseIndex;
2156
+ const index = [];
2157
+ if (this.#engine) {
2158
+ try {
2159
+ const classes = this.#engine.context.getClassList().filter((c) => typeof c === "string");
2160
+ const nodes = this.#engine.generate(classes);
2161
+ for (const node of nodes) {
2162
+ if (node.type !== "rule") continue;
2163
+ const rule = node;
2164
+ const parsed = parseSelector(rule.selector);
2165
+ if (parsed.kind !== "simple" || parsed.states.length > 0 || parsed.pseudoElement !== "") {
2166
+ continue;
2167
+ }
2168
+ const className = unescapeClass(rule.selector);
2169
+ if (className === null) continue;
2170
+ const decls = /* @__PURE__ */ new Map();
2171
+ for (const child of rule.nodes ?? []) {
2172
+ if (child.type !== "decl") continue;
2173
+ const d = child;
2174
+ if (typeof d.value !== "string") continue;
2175
+ for (const decl of normalizer.normalizeDeclaration(d.prop, d.value, d.important === true)) {
2176
+ decls.set(decl.property, String(decl.value));
2177
+ }
2178
+ }
2179
+ if (decls.size > 0) index.push([className, decls]);
2180
+ }
2181
+ } catch {
2182
+ }
1251
2183
  }
1252
- return false;
1253
- }
1254
- if (tokens.length === 0) return false;
1255
- if (el.isComponent) return false;
1256
- const back = doc.backref.get(el.id);
1257
- const openTag = back?.openTagSpan;
1258
- if (!openTag || openTag.file !== sf.id) return false;
1259
- const insertAt = openTag.start + 1 + el.tag.length;
1260
- ms.appendLeft(insertAt, ` className="${tokens.join(" ")}"`);
1261
- return true;
1262
- }
1263
- function surgicalPrint(doc) {
1264
- const sf = primarySource(doc);
1265
- if (!sf) return null;
1266
- const ms = new MagicString(sf.text);
1267
- const kept = collectKept(doc);
1268
- const keptSpans = [];
1269
- for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
1270
- const removed = [];
1271
- for (const id of backrefIds(doc)) {
1272
- if (doc.nodes.has(id)) continue;
1273
- const back = doc.backref.get(id);
1274
- if (!back || back.span.file !== sf.id) continue;
1275
- const unwrapped = keptSpans.some((k) => strictlyContains(back.span, k));
1276
- removed.push({ backref: back, unwrapped });
2184
+ index.sort((a, b) => b[1].size - a[1].size || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
2185
+ this.#reverseIndex = index;
2186
+ return index;
1277
2187
  }
1278
- const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
1279
- for (const r of removed) {
1280
- const span = r.backref.span;
1281
- const coveredByFull = fullRemovals.some((f) => f !== span && strictlyContains(f, span));
1282
- if (coveredByFull) continue;
1283
- if (r.unwrapped) {
1284
- const open = r.backref.openTagSpan;
1285
- const close = r.backref.closeTagSpan;
1286
- if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
1287
- if (close && close.file === sf.id && close.end > close.start) {
1288
- ms.remove(close.start, close.end);
2188
+ emit(styles, ctx) {
2189
+ const norm = ctx.normalizer ?? normalizer;
2190
+ const normalized = norm.normalizeStyleMap(styles);
2191
+ const base = normalized.blocks.get(conditionKey(BASE_CONDITION));
2192
+ if (!base || base.decls.size === 0) return { classes: [], exact: true, warnings: [] };
2193
+ const hasNonBase = normalized.blocks.size > 1;
2194
+ const target = /* @__PURE__ */ new Map();
2195
+ for (const [prop, decl] of base.decls) {
2196
+ for (const [lp, lv] of expandForEmit(norm, String(prop), String(decl.value), decl.important)) {
2197
+ target.set(lp, lv);
1289
2198
  }
1290
- } else {
1291
- ms.remove(span.start, span.end);
1292
2199
  }
2200
+ const candidates = [];
2201
+ for (const entry of this.#buildReverseIndex()) {
2202
+ const [, declMap] = entry;
2203
+ if (declMap.size === 0 || declMap.size > target.size) continue;
2204
+ let fits = true;
2205
+ for (const [prop, value] of declMap) {
2206
+ if (target.get(prop) !== value) {
2207
+ fits = false;
2208
+ break;
2209
+ }
2210
+ }
2211
+ if (fits) candidates.push(entry);
2212
+ }
2213
+ const remaining = new Map(target);
2214
+ const classes = [];
2215
+ while (remaining.size > 0) {
2216
+ let best = null;
2217
+ let bestCover = 0;
2218
+ for (const entry of candidates) {
2219
+ const [token, declMap] = entry;
2220
+ let cover = 0;
2221
+ for (const prop of declMap.keys()) if (remaining.has(prop)) cover += 1;
2222
+ if (cover === 0) continue;
2223
+ const better = best === null || cover > bestCover || cover === bestCover && declMap.size < best[1].size || cover === bestCover && declMap.size === best[1].size && token < best[0];
2224
+ if (better) {
2225
+ best = entry;
2226
+ bestCover = cover;
2227
+ }
2228
+ }
2229
+ if (!best) break;
2230
+ classes.push(best[0]);
2231
+ for (const prop of best[1].keys()) remaining.delete(prop);
2232
+ }
2233
+ const exact = remaining.size === 0 && !hasNonBase;
2234
+ if (remaining.size === 0) return { classes, exact, warnings: [] };
2235
+ const residual = synthesizeResidual(remaining, ctx);
2236
+ return residual ? { classes, residual, exact, warnings: [] } : { classes, exact, warnings: [] };
1293
2237
  }
1294
- for (const n of kept) {
1295
- if (n.kind === "element") editClasses(ms, doc, sf, n);
1296
- }
1297
- return ms.toString();
1298
- }
1299
- function backrefIds(doc) {
1300
- const out = [];
1301
- const max = doc.alloc.peek;
1302
- for (let i = 1; i < max; i += 1) {
1303
- const id = i;
1304
- if (doc.backref.get(id)) out.push(id);
1305
- }
1306
- return out;
1307
- }
1308
- function classText(doc, classes) {
1309
- if (classes.form === "absent" || classes.segments.length === 0) return null;
1310
- const dynamic = classes.segments.find((s) => s.kind === "dynamic");
1311
- if (dynamic && dynamic.kind === "dynamic") {
1312
- return `className={${exprText(doc, dynamic.expr).text}}`;
1313
- }
1314
- const tokens = staticTokensOf(classes);
1315
- return `className="${tokens.join(" ")}"`;
1316
- }
1317
- function attrText(doc, name, value) {
1318
- if (value.kind === "static") {
1319
- if (value.value === true) return name;
1320
- if (value.value === false) return "";
1321
- return `${name}="${String(value.value)}"`;
1322
- }
1323
- return `${name}={${exprText(doc, value.expr).text}}`;
1324
- }
1325
- function printElement(doc, el) {
1326
- const parts = [];
1327
- const cls = classText(doc, el.classes);
1328
- if (cls !== null) parts.push(cls);
1329
- for (const name of el.attrs.order) {
1330
- const v = el.attrs.entries.get(name);
1331
- if (!v) continue;
1332
- const text = attrText(doc, name, v);
1333
- if (text.length > 0) parts.push(text);
1334
- }
1335
- for (const ref of el.attrs.spreads) parts.push(`{...${exprText(doc, ref).text}}`);
1336
- const attrStr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
1337
- const tag = el.tag;
1338
- if (el.children.length === 0) {
1339
- return el.selfClosing ? `<${tag}${attrStr} />` : `<${tag}${attrStr}></${tag}>`;
1340
- }
1341
- const inner = el.children.map((c) => printNode(doc, c)).join("");
1342
- return `<${tag}${attrStr}>${inner}</${tag}>`;
1343
- }
1344
- function printNode(doc, id) {
1345
- const node = doc.nodes.get(id);
1346
- if (!node) return "";
1347
- switch (node.kind) {
1348
- case "text":
1349
- return node.value;
1350
- case "comment":
1351
- return `{/*${node.value}*/}`;
1352
- case "expr": {
1353
- const { text, spread } = exprText(doc, node.expr);
1354
- return spread ? `{...${text}}` : `{${text}}`;
2238
+ /**
2239
+ * Generate a CSS stylesheet that defines `classes`, so a verifier can render a subtree with the
2240
+ * real Tailwind styling applied. Backed by the same engine `resolve` uses (`generate(candidates)`),
2241
+ * serialized to plain CSS. Returns `''` when the engine is unavailable or generates nothing.
2242
+ */
2243
+ cssFor(classes) {
2244
+ if (!this.#engine) return "";
2245
+ const tokens = [...new Set(classes)].filter((c) => c.length > 0);
2246
+ if (tokens.length === 0) return "";
2247
+ try {
2248
+ return this.#engine.generate(tokens).map((n) => serializeCssNode(n)).filter((s) => s.length > 0).join("\n");
2249
+ } catch {
2250
+ return "";
1355
2251
  }
1356
- case "fragment":
1357
- return `<>${node.children.map((c) => printNode(doc, c)).join("")}</>`;
1358
- case "element":
1359
- return printElement(doc, node);
1360
2252
  }
1361
- }
1362
- function rePrint(doc) {
1363
- const root = doc.nodes.get(doc.root);
1364
- if (!root || root.kind !== "fragment") return printNode(doc, doc.root);
1365
- return root.children.map((c) => printNode(doc, c)).join("");
1366
- }
1367
- function doPrint(doc) {
1368
- const surgical = surgicalPrint(doc);
1369
- return surgical ?? rePrint(doc);
1370
- }
1371
- var jsxBackend = {
1372
- name: "babel-jsx",
1373
- langs: JSX_LANGS2,
1374
- print(doc, _plan, _ctx) {
1375
- const code = doPrint(doc);
1376
- return { code, map: null, edits: [], diagnostics: [] };
2253
+ selectorUsage(token) {
2254
+ const ex = this.#extract(token);
2255
+ if (!ex.produced || ex.opaque) return OPAQUE_USAGE;
2256
+ const baseOnly = ex.blocks.length > 0 && ex.blocks.every((b) => conditionKey(b.condition) === conditionKey(BASE_CONDITION));
2257
+ if (!baseOnly) return OPAQUE_USAGE;
2258
+ return DROPPABLE_USAGE;
1377
2259
  }
1378
2260
  };
1379
- function createJsxBackend() {
1380
- return jsxBackend;
2261
+ function createTailwindResolver(config) {
2262
+ return new TailwindResolver(config);
1381
2263
  }
1382
2264
 
1383
- // ../frontend-jsx/src/index.ts
2265
+ // ../resolver-tailwind/src/index.ts
1384
2266
  init_esm_shims();
1385
2267
 
1386
- // ../resolver-tailwind/src/index.ts
2268
+ // ../resolver-css/src/resolver.ts
1387
2269
  init_esm_shims();
1388
- import { createRequire } from "module";
1389
- import * as path from "path";
1390
- function moduleBase() {
1391
- return typeof __filename === "string" ? __filename : import.meta.url;
1392
- }
1393
- function projectRequire(projectRoot) {
1394
- const bases = [];
1395
- if (projectRoot) bases.push(path.join(projectRoot, "__domflax__.js"));
1396
- bases.push(path.join(process.cwd(), "__domflax__.js"));
1397
- bases.push(moduleBase());
1398
- for (const base of bases) {
1399
- try {
1400
- const candidate = createRequire(base);
1401
- candidate.resolve("tailwindcss/package.json");
1402
- return candidate;
1403
- } catch {
1404
- }
1405
- }
1406
- return null;
1407
- }
1408
- function loadEngine(options) {
1409
- const req = projectRequire(options.projectRoot);
1410
- if (!req) return null;
1411
- try {
1412
- const resolveConfig = req("tailwindcss/resolveConfig.js");
1413
- const { createContext } = req("tailwindcss/lib/lib/setupContextUtils.js");
1414
- const { generateRules } = req("tailwindcss/lib/lib/generateRules.js");
1415
- const pkg = req("tailwindcss/package.json");
1416
- let userConfig = options.config ?? { content: [{ raw: "" }] };
1417
- if (options.configPath !== void 0) {
1418
- const loadConfig = req("tailwindcss/loadConfig.js");
1419
- userConfig = loadConfig(options.configPath);
1420
- }
1421
- const resolved = resolveConfig(userConfig);
1422
- const context = createContext(resolved);
1423
- return {
1424
- version: pkg.version,
1425
- context,
1426
- generate(candidates) {
1427
- const rules = generateRules(new Set(candidates), context);
1428
- return rules.map(([, node]) => node);
1429
- }
1430
- };
1431
- } catch {
1432
- return null;
1433
- }
1434
- }
1435
- var LEGACY_PSEUDO_ELEMENTS = /* @__PURE__ */ new Set([
2270
+
2271
+ // ../resolver-css/src/constants.ts
2272
+ init_esm_shims();
2273
+ var CSS_RESOLVER_ID = "css";
2274
+ var CSS_RESOLVER_PROVIDER = "custom-css";
2275
+ var ENGINE_VERSION = "css-index@1";
2276
+ var STRUCTURAL_PSEUDOS = /* @__PURE__ */ new Set([
2277
+ ":nth-child",
2278
+ ":nth-last-child",
2279
+ ":first-child",
2280
+ ":last-child",
2281
+ ":only-child",
2282
+ ":nth-of-type",
2283
+ ":nth-last-of-type",
2284
+ ":first-of-type",
2285
+ ":last-of-type",
2286
+ ":only-of-type"
2287
+ ]);
2288
+ var FUNCTIONAL_PSEUDOS = /* @__PURE__ */ new Set([
2289
+ ":not",
2290
+ ":is",
2291
+ ":where",
2292
+ ":has",
2293
+ ":matches"
2294
+ ]);
2295
+ var LEGACY_PSEUDO_ELEMENTS2 = /* @__PURE__ */ new Set([
1436
2296
  ":before",
1437
2297
  ":after",
1438
2298
  ":first-line",
1439
2299
  ":first-letter"
1440
2300
  ]);
1441
- function parseSelector(selector) {
1442
- const sel = selector.trim();
1443
- if (sel.length === 0 || sel[0] !== ".") return { kind: "complex" };
1444
- let i = 1;
1445
- for (; i < sel.length; i += 1) {
1446
- const c = sel[i];
1447
- if (c === "\\") {
1448
- i += 1;
1449
- continue;
1450
- }
1451
- if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
1452
- break;
2301
+
2302
+ // ../resolver-css/src/engine.ts
2303
+ init_esm_shims();
2304
+ import { createRequire as createRequire2 } from "module";
2305
+ import * as path2 from "path";
2306
+ function moduleBase2() {
2307
+ return typeof __filename === "string" ? __filename : import.meta.url;
2308
+ }
2309
+ function loadPostcssEngine(projectRoot) {
2310
+ const bases = [];
2311
+ if (projectRoot) bases.push(path2.join(projectRoot, "__domflax__.js"));
2312
+ bases.push(path2.join(process.cwd(), "__domflax__.js"));
2313
+ bases.push(moduleBase2());
2314
+ for (const base of bases) {
2315
+ try {
2316
+ const req = createRequire2(base);
2317
+ req.resolve("postcss");
2318
+ req.resolve("postcss-selector-parser");
2319
+ const postcss = req("postcss");
2320
+ const raw = req("postcss-selector-parser");
2321
+ const selector = raw.default ?? raw;
2322
+ return { parse: postcss.parse, selectorParser: selector };
2323
+ } catch {
1453
2324
  }
1454
2325
  }
1455
- const remainder = sel.slice(i);
1456
- if (remainder.length === 0) {
1457
- return { kind: "simple", states: [], pseudoElement: "" };
2326
+ return null;
2327
+ }
2328
+ var pc = null;
2329
+ var sp = null;
2330
+ function ensurePostcss(projectRoot) {
2331
+ if (pc && sp) return;
2332
+ const engine = loadPostcssEngine(projectRoot);
2333
+ if (!engine) {
2334
+ throw new Error(
2335
+ '@domflax/resolver-css requires "postcss" and "postcss-selector-parser" to be installed in your project (they are optional peer dependencies of domflax, loaded only when the custom-CSS provider is used). Install them with: npm install postcss postcss-selector-parser'
2336
+ );
1458
2337
  }
1459
- if (!/^(?:::?[-a-z]+(?:\([^()]*\))?)+$/i.test(remainder)) {
1460
- return { kind: "complex" };
2338
+ pc = engine.parse;
2339
+ sp = engine.selectorParser;
2340
+ }
2341
+
2342
+ // ../resolver-css/src/postcss-helpers.ts
2343
+ init_esm_shims();
2344
+ function mediaContext(rule) {
2345
+ const parts = [];
2346
+ let skip = false;
2347
+ let parent = rule.parent;
2348
+ while (parent && parent.type === "atrule") {
2349
+ const at = parent;
2350
+ const name = at.name.toLowerCase();
2351
+ if (name === "media") parts.unshift(at.params.trim().replace(/\s+/g, " "));
2352
+ else if (name === "keyframes" || name.endsWith("keyframes") || name === "font-face") skip = true;
2353
+ parent = parent.parent;
1461
2354
  }
1462
- const parts = remainder.match(/::?[-a-z]+(?:\([^()]*\))?/gi) ?? [];
1463
- const states = [];
1464
- let pseudoElement = "";
1465
- for (const part of parts) {
1466
- if (part.startsWith("::") || LEGACY_PSEUDO_ELEMENTS.has(part)) {
1467
- pseudoElement = part.startsWith("::") ? part : `:${part}`;
1468
- } else {
1469
- states.push(part);
1470
- }
2355
+ return { media: parts.join(" and "), skip };
2356
+ }
2357
+ function collectDecls(rule) {
2358
+ const out = [];
2359
+ for (const node of rule.nodes) {
2360
+ if (node.type === "decl") out.push([node.prop, node.value, node.important === true]);
1471
2361
  }
1472
- return { kind: "simple", states, pseudoElement };
2362
+ return out;
1473
2363
  }
1474
- function makeCondition(media, states, pseudoElement) {
1475
- return {
1476
- media,
1477
- states: [...new Set(states)].sort(),
1478
- pseudoElement
1479
- };
2364
+
2365
+ // ../resolver-css/src/misc-helpers.ts
2366
+ init_esm_shims();
2367
+ import { readFileSync } from "fs";
2368
+ function isPlainClassToken(token) {
2369
+ return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
1480
2370
  }
1481
- function collectRules(node, mediaStack, inUnsupportedAtRule, out) {
1482
- if (node.type === "rule") {
1483
- out.push({ rule: node, media: mediaStack, unsupported: inUnsupportedAtRule });
1484
- return;
2371
+ function readCssPath(path3) {
2372
+ try {
2373
+ return { id: path3, css: readFileSync(path3, "utf8") };
2374
+ } catch (cause) {
2375
+ throw new Error(`resolver-css: cannot read CSS file "${path3}"`, { cause });
1485
2376
  }
1486
- if (node.type === "atrule") {
1487
- const at = node;
1488
- const children = at.nodes ?? [];
1489
- if (at.name === "media") {
1490
- const nextStack = at.params ? [...mediaStack, at.params] : mediaStack;
1491
- for (const child of children) collectRules(child, nextStack, inUnsupportedAtRule, out);
2377
+ }
2378
+ function deriveFingerprint(provider, files) {
2379
+ const parts = files.map((f) => `${f.id}:${f.css.length}`).sort();
2380
+ return `${provider}/${ENGINE_VERSION}::${parts.join("|")}`;
2381
+ }
2382
+
2383
+ // ../resolver-css/src/selector-helpers.ts
2384
+ init_esm_shims();
2385
+ function splitCompounds(selector) {
2386
+ const compounds = [];
2387
+ let current = [];
2388
+ let leftCombinator = null;
2389
+ for (const node of selector.nodes) {
2390
+ if (sp.isCombinator(node)) {
2391
+ compounds.push({ leftCombinator, nodes: current });
2392
+ current = [];
2393
+ leftCombinator = combinatorValue(node);
1492
2394
  } else {
1493
- for (const child of children) collectRules(child, mediaStack, true, out);
2395
+ current.push(node);
1494
2396
  }
1495
2397
  }
2398
+ compounds.push({ leftCombinator, nodes: current });
2399
+ return compounds;
1496
2400
  }
1497
- function extractToken(token, nodes) {
1498
- if (nodes.length === 0) return { blocks: [], produced: false };
1499
- const leaves = [];
1500
- for (const node of nodes) collectRules(node, [], false, leaves);
1501
- const blocks = [];
1502
- let sawComplex = false;
1503
- for (const { rule, media, unsupported } of leaves) {
1504
- const parsed = parseSelector(rule.selector);
1505
- if (parsed.kind === "complex" || unsupported) {
1506
- sawComplex = true;
1507
- continue;
1508
- }
1509
- const decls = [];
1510
- for (const child of rule.nodes ?? []) {
1511
- if (child.type !== "decl") continue;
1512
- const d = child;
1513
- if (typeof d.value !== "string") continue;
1514
- decls.push([d.prop, d.value, d.important === true]);
1515
- }
1516
- if (decls.length === 0) continue;
1517
- const mediaQuery = media.join(" and ");
1518
- blocks.push({ condition: makeCondition(mediaQuery, parsed.states, parsed.pseudoElement), decls });
1519
- }
1520
- const opaque = sawComplex && blocks.length === 0 ? { token, reason: "combinator-variant", detail: "utility targets descendants/siblings, not its own box" } : void 0;
1521
- return { blocks, produced: true, opaque };
2401
+ function combinatorValue(node) {
2402
+ const v = node.value;
2403
+ return v.trim() === "" ? " " : v.trim();
1522
2404
  }
1523
- function buildStyleMap(blockMaps) {
1524
- if (blockMaps.size === 0) return emptyStyleMap();
1525
- const blocks = /* @__PURE__ */ new Map();
1526
- for (const { condition, decls } of blockMaps.values()) {
1527
- if (decls.size === 0) continue;
1528
- blocks.set(conditionKey(condition), { condition, decls });
1529
- }
1530
- if (blocks.size === 0) return emptyStyleMap();
1531
- return normalizer.normalizeStyleMap({ blocks });
2405
+ function pseudoName(node) {
2406
+ return node.value.toLowerCase();
1532
2407
  }
1533
- function shadowedBy(prev) {
1534
- const out = [];
1535
- const seen = /* @__PURE__ */ new Set();
1536
- const add = (o) => {
1537
- if (!o || o.kind !== "class" || seen.has(o.className)) return;
1538
- seen.add(o.className);
1539
- out.push(o);
1540
- };
1541
- for (const o of prev.shadowed ?? []) add(o);
1542
- add(prev.origin);
1543
- return out.length > 0 ? out : void 0;
2408
+ function isPseudoElement(node) {
2409
+ return sp.isPseudoElement(node) || LEGACY_PSEUDO_ELEMENTS2.has(pseudoName(node));
1544
2410
  }
1545
- var OPAQUE_USAGE = {
1546
- asSubject: true,
1547
- asAncestor: true,
1548
- asCompound: true,
1549
- asSibling: true,
1550
- asHasArgument: true,
1551
- asStructural: true,
1552
- droppable: false
1553
- };
1554
- var DROPPABLE_USAGE = {
1555
- asSubject: true,
1556
- asAncestor: false,
1557
- asCompound: false,
1558
- asSibling: false,
1559
- asHasArgument: false,
1560
- asStructural: false,
1561
- droppable: true
1562
- };
1563
- function fnv1a(input) {
1564
- let h = 2166136261;
1565
- for (let i = 0; i < input.length; i += 1) {
1566
- h ^= input.charCodeAt(i);
1567
- h = Math.imul(h, 16777619);
1568
- }
1569
- return (h >>> 0).toString(16).padStart(8, "0");
2411
+ function normalizePseudoElement(node) {
2412
+ const name = pseudoName(node);
2413
+ return name.startsWith("::") ? name : `::${name.replace(/^:/, "")}`;
1570
2414
  }
1571
- var TailwindResolver = class {
1572
- id = "tailwind";
1573
- provider;
2415
+
2416
+ // ../resolver-css/src/resolver.ts
2417
+ var CustomCSSResolver = class {
2418
+ id = CSS_RESOLVER_ID;
2419
+ provider = CSS_RESOLVER_PROVIDER;
1574
2420
  fingerprint;
1575
- #engine;
1576
- /** Per-token extraction cache (engine output is pure for a fixed config). */
1577
- #tokenCache = /* @__PURE__ */ new Map();
1578
- /** Per-class-set forward-resolution cache. */
1579
- #resolveCache = /* @__PURE__ */ new Map();
1580
- /** Lazily built reverse index for {@link emit}. */
1581
- #reverseIndex = null;
1582
- constructor(config = {}) {
1583
- this.#engine = loadEngine(config);
1584
- this.provider = config.provider ?? (this.#engine ? `tailwindcss@${this.#engine.version}` : "tailwindcss");
1585
- const seed = JSON.stringify(config.config ?? {}) + (config.configPath ?? "");
1586
- this.fingerprint = config.fingerprint ?? `${this.provider}/${fnv1a(seed)}`;
1587
- }
1588
- /** Engine-backed, cached single-token extraction. */
1589
- #extract(token) {
1590
- const cached = this.#tokenCache.get(token);
1591
- if (cached) return cached;
1592
- let result;
1593
- if (!this.#engine) {
1594
- result = { blocks: [], produced: false };
1595
- } else {
1596
- try {
1597
- result = extractToken(token, this.#engine.generate([token]));
1598
- } catch {
1599
- result = { blocks: [], produced: false };
1600
- }
2421
+ #files;
2422
+ /** Forward map: class token simple-`.class` rule contributions (source order). */
2423
+ #classIndex = /* @__PURE__ */ new Map();
2424
+ /** Selector-participation facts per class token. */
2425
+ #usage = /* @__PURE__ */ new Map();
2426
+ /** Every class referenced anywhere in the stylesheets (forward-resolvable or not). */
2427
+ #known = /* @__PURE__ */ new Set();
2428
+ /** Distinct COMPLEX selectors (combinator or structural pseudo), sorted. */
2429
+ #complex;
2430
+ #reverse = null;
2431
+ constructor(cssFiles = [], options = {}) {
2432
+ ensurePostcss(options.projectRoot);
2433
+ const fromDisk = (options.files ?? []).map(readCssPath);
2434
+ this.#files = [...cssFiles, ...fromDisk];
2435
+ this.fingerprint = options.fingerprint ?? deriveFingerprint(this.provider, this.#files);
2436
+ const complex = /* @__PURE__ */ new Set();
2437
+ let order = 0;
2438
+ for (const file of this.#files) {
2439
+ order = this.#indexFile(file, order, complex);
1601
2440
  }
1602
- this.#tokenCache.set(token, result);
1603
- return result;
2441
+ this.#complex = [...complex].sort();
2442
+ }
2443
+ /** The stylesheets this resolver was constructed with (raw sources + any read from disk). */
2444
+ get files() {
2445
+ return this.#files;
1604
2446
  }
2447
+ /** Owns any plain class token referenced by one of {@link files}. */
1605
2448
  owns(token) {
1606
- if (token.length === 0) return false;
1607
- return this.#extract(token).produced;
2449
+ return isPlainClassToken(token) && this.#known.has(token);
1608
2450
  }
1609
2451
  resolve(input) {
1610
- const key = JSON.stringify(input.classes);
1611
- const cached = this.#resolveCache.get(key);
1612
- if (cached) return cached;
1613
- const blockMaps = /* @__PURE__ */ new Map();
2452
+ const styles = this.#resolveTokens(input.classes, input.classes);
1614
2453
  const resolved = [];
1615
2454
  const unknown = [];
1616
- const opaque = [];
1617
- input.classes.forEach((token, tokenIndex) => {
1618
- const extracted = this.#extract(token);
1619
- if (!extracted.produced) {
1620
- unknown.push(token);
1621
- return;
2455
+ for (const token of input.classes) {
2456
+ if (this.#classIndex.has(token)) resolved.push(token);
2457
+ else unknown.push(token);
2458
+ }
2459
+ return { styles, resolved, unknown, opaque: [], warnings: [] };
2460
+ }
2461
+ emit(styles, ctx) {
2462
+ const norm = ctx.normalizer ?? normalizer;
2463
+ const remaining = /* @__PURE__ */ new Map();
2464
+ for (const [ck, block] of norm.normalizeStyleMap(styles).blocks) {
2465
+ for (const [prop, decl] of block.decls) {
2466
+ remaining.set(`${ck} ${prop}`, String(decl.value));
1622
2467
  }
1623
- if (extracted.opaque) opaque.push(extracted.opaque);
1624
- if (extracted.blocks.length === 0) return;
1625
- const origin = { kind: "class", tokenIndex, className: token };
1626
- let contributed = false;
1627
- for (const block of extracted.blocks) {
1628
- const ck = conditionKey(block.condition);
1629
- let bucket = blockMaps.get(ck);
1630
- if (!bucket) {
1631
- bucket = { condition: block.condition, decls: /* @__PURE__ */ new Map() };
1632
- blockMaps.set(ck, bucket);
1633
- }
1634
- for (const [prop, value, important] of block.decls) {
1635
- for (const decl of normalizer.normalizeDeclaration(prop, value, important)) {
1636
- const prev = bucket.decls.get(decl.property);
1637
- const shadowed = prev ? shadowedBy(prev) : void 0;
1638
- bucket.decls.set(decl.property, shadowed ? { ...decl, origin, shadowed } : { ...decl, origin });
1639
- contributed = true;
1640
- }
2468
+ }
2469
+ if (remaining.size === 0) return { classes: [], exact: true, warnings: [] };
2470
+ const classes = [];
2471
+ for (const { token, keyed } of this.#reverseIndex()) {
2472
+ let matches = true;
2473
+ for (const [key, value] of keyed) {
2474
+ if (remaining.get(key) !== value) {
2475
+ matches = false;
2476
+ break;
1641
2477
  }
1642
2478
  }
1643
- if (contributed) resolved.push(token);
1644
- });
1645
- const result = {
1646
- styles: buildStyleMap(blockMaps),
1647
- resolved,
1648
- unknown,
1649
- opaque,
1650
- warnings: []
2479
+ if (!matches) continue;
2480
+ classes.push(token);
2481
+ for (const key of keyed.keys()) remaining.delete(key);
2482
+ if (remaining.size === 0) break;
2483
+ }
2484
+ return { classes, exact: remaining.size === 0, warnings: [] };
2485
+ }
2486
+ /**
2487
+ * Return a CSS stylesheet defining the given class tokens, so a verifier can render a subtree with
2488
+ * the project's real styling applied. The source stylesheets ARE the definition, so we hand back
2489
+ * their concatenation verbatim (every relevant rule — including combinator/structural selectors —
2490
+ * is preserved). `classes` is accepted for interface parity but the full source is always returned.
2491
+ */
2492
+ cssFor(_classes) {
2493
+ return this.#files.map((f) => f.css).join("\n");
2494
+ }
2495
+ selectorUsage(token) {
2496
+ const u = this.#usage.get(token);
2497
+ if (!u) {
2498
+ return {
2499
+ asSubject: false,
2500
+ asAncestor: false,
2501
+ asCompound: false,
2502
+ asSibling: false,
2503
+ asHasArgument: false,
2504
+ asStructural: false,
2505
+ droppable: true
2506
+ };
2507
+ }
2508
+ return {
2509
+ asSubject: u.asSubject,
2510
+ asAncestor: u.asAncestor,
2511
+ asCompound: u.asCompound,
2512
+ asSibling: u.asSibling,
2513
+ asHasArgument: u.asHasArgument,
2514
+ asStructural: u.asStructural,
2515
+ // Safe to drop/rename only when every reference is the lone subject of a bare `.x {}`.
2516
+ droppable: u.referenced && !u.loadBearing
1651
2517
  };
1652
- this.#resolveCache.set(key, result);
1653
- return result;
1654
2518
  }
1655
2519
  /**
1656
- * Lazily build the reverse index from the engine's own enumerable class list. Each indexable
1657
- * utility maps to its NORMALIZED BASE longhand declarations (property canonical value). Utilities
1658
- * with variant conditions, combinator selectors, or no BASE declarations are skipped. Sorted by
1659
- * declaration count (desc) so greedier (shorthand-like) utilities are tried first.
2520
+ * The distinct COMPLEX selectors found across all stylesheets anything containing a combinator
2521
+ * (descendant / `>` / `+` / `~`) or a structural pseudo (`:nth-child`, `:first-child`, ). Feeds
2522
+ * domflax's CSS-selector-safety guard.
1660
2523
  */
1661
- #buildReverseIndex() {
1662
- if (this.#reverseIndex) return this.#reverseIndex;
1663
- const index = [];
1664
- if (this.#engine) {
2524
+ complexSelectors() {
2525
+ return this.#complex;
2526
+ }
2527
+ /* ─────────────────────────── internals ─────────────────────────── */
2528
+ /** Parse one stylesheet and fold its rules into the indexes. Returns the advanced order counter. */
2529
+ #indexFile(file, startOrder, complex) {
2530
+ let order = startOrder;
2531
+ let root;
2532
+ try {
2533
+ root = pc(file.css, { from: file.id });
2534
+ } catch {
2535
+ return order;
2536
+ }
2537
+ root.walkRules((rule) => {
2538
+ const media = mediaContext(rule);
2539
+ if (media.skip) return;
2540
+ const decls = collectDecls(rule);
2541
+ let ast;
1665
2542
  try {
1666
- const classes = this.#engine.context.getClassList().filter((c) => typeof c === "string");
1667
- const nodes = this.#engine.generate(classes);
1668
- for (const node of nodes) {
1669
- if (node.type !== "rule") continue;
1670
- const rule = node;
1671
- const parsed = parseSelector(rule.selector);
1672
- if (parsed.kind !== "simple" || parsed.states.length > 0 || parsed.pseudoElement !== "") {
1673
- continue;
1674
- }
1675
- const className = unescapeClass(rule.selector);
1676
- if (className === null) continue;
1677
- const decls = /* @__PURE__ */ new Map();
1678
- for (const child of rule.nodes ?? []) {
1679
- if (child.type !== "decl") continue;
1680
- const d = child;
1681
- if (typeof d.value !== "string") continue;
1682
- for (const decl of normalizer.normalizeDeclaration(d.prop, d.value, d.important === true)) {
1683
- decls.set(decl.property, String(decl.value));
1684
- }
1685
- }
1686
- if (decls.size > 0) index.push([className, decls]);
1687
- }
2543
+ ast = sp().astSync(rule.selector);
1688
2544
  } catch {
2545
+ return;
1689
2546
  }
1690
- }
1691
- index.sort((a, b) => b[1].size - a[1].size || (a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0));
1692
- this.#reverseIndex = index;
1693
- return index;
1694
- }
1695
- emit(styles, ctx) {
1696
- const norm = ctx.normalizer ?? normalizer;
1697
- const normalized = norm.normalizeStyleMap(styles);
1698
- const base = normalized.blocks.get(conditionKey(BASE_CONDITION));
1699
- if (!base || base.decls.size === 0) return { classes: [], exact: true, warnings: [] };
1700
- const hasNonBase = normalized.blocks.size > 1;
1701
- const target = /* @__PURE__ */ new Map();
1702
- for (const [prop, decl] of base.decls) {
1703
- for (const [lp, lv] of expandForEmit(norm, String(prop), String(decl.value), decl.important)) {
1704
- target.set(lp, lv);
2547
+ for (const sel of ast.nodes) {
2548
+ const thisOrder = order;
2549
+ this.#analyzeSelector(sel, media.media, decls, thisOrder, complex);
1705
2550
  }
1706
- }
1707
- const candidates = [];
1708
- for (const entry of this.#buildReverseIndex()) {
1709
- const [, declMap] = entry;
1710
- if (declMap.size === 0 || declMap.size > target.size) continue;
1711
- let fits = true;
1712
- for (const [prop, value] of declMap) {
1713
- if (target.get(prop) !== value) {
1714
- fits = false;
1715
- break;
2551
+ order += 1;
2552
+ });
2553
+ return order;
2554
+ }
2555
+ /** Analyze one comma-segment selector: forward indexing, usage facts, complex detection. */
2556
+ #analyzeSelector(selector, media, decls, order, complex) {
2557
+ const compounds = splitCompounds(selector);
2558
+ let hasCombinator = false;
2559
+ let hasStructural = false;
2560
+ compounds.forEach((compound, index) => {
2561
+ const isSubject = index === compounds.length - 1;
2562
+ const rightCombinator = index < compounds.length - 1 ? compounds[index + 1].leftCombinator : null;
2563
+ if (rightCombinator) hasCombinator = true;
2564
+ const classes = compound.nodes.filter((n) => sp.isClassName(n));
2565
+ const otherSimple = compound.nodes.some(
2566
+ (n) => sp.isTag(n) || sp.isIdentifier(n) || sp.isAttribute(n) || sp.isUniversal(n) || sp.isNesting(n)
2567
+ );
2568
+ const pseudos = compound.nodes.filter((n) => sp.isPseudo(n));
2569
+ const structuralPseudo = pseudos.some((p) => STRUCTURAL_PSEUDOS.has(pseudoName(p)));
2570
+ const functionalPseudo = pseudos.some((p) => FUNCTIONAL_PSEUDOS.has(pseudoName(p)));
2571
+ const statePseudos = pseudos.filter(
2572
+ (p) => sp.isPseudoClass(p) && !STRUCTURAL_PSEUDOS.has(pseudoName(p)) && !FUNCTIONAL_PSEUDOS.has(pseudoName(p))
2573
+ );
2574
+ const elementPseudos = pseudos.filter((p) => isPseudoElement(p));
2575
+ const qualified = classes.length > 1 || otherSimple || functionalPseudo || statePseudos.length > 0;
2576
+ if (structuralPseudo) hasStructural = true;
2577
+ for (const cls of classes) {
2578
+ const token = cls.value;
2579
+ this.#known.add(token);
2580
+ const u = this.#getUsage(token);
2581
+ u.referenced = true;
2582
+ if (isSubject) u.asSubject = true;
2583
+ if (rightCombinator === " " || rightCombinator === ">") u.asAncestor = true;
2584
+ if (rightCombinator === "+" || rightCombinator === "~") u.asSibling = true;
2585
+ if (qualified) u.asCompound = true;
2586
+ if (structuralPseudo) u.asStructural = true;
2587
+ if (rightCombinator !== null || qualified || structuralPseudo || elementPseudos.length > 0) {
2588
+ u.loadBearing = true;
1716
2589
  }
1717
- }
1718
- if (fits) candidates.push(entry);
1719
- }
1720
- const remaining = new Map(target);
1721
- const classes = [];
1722
- while (remaining.size > 0) {
1723
- let best = null;
1724
- let bestCover = 0;
1725
- for (const entry of candidates) {
1726
- const [token, declMap] = entry;
1727
- let cover = 0;
1728
- for (const prop of declMap.keys()) if (remaining.has(prop)) cover += 1;
1729
- if (cover === 0) continue;
1730
- const better = best === null || cover > bestCover || cover === bestCover && declMap.size < best[1].size || cover === bestCover && declMap.size === best[1].size && token < best[0];
1731
- if (better) {
1732
- best = entry;
1733
- bestCover = cover;
2590
+ const forwardEligible = compounds.length === 1 && classes.length === 1 && !otherSimple && !structuralPseudo && !functionalPseudo && elementPseudos.length <= 1;
2591
+ if (forwardEligible && decls.length > 0) {
2592
+ const condition = {
2593
+ media,
2594
+ states: statePseudos.map(pseudoName).sort(),
2595
+ pseudoElement: elementPseudos.length === 1 ? normalizePseudoElement(elementPseudos[0]) : ""
2596
+ };
2597
+ this.#addRuleEntry(token, { order, token, condition, decls });
1734
2598
  }
1735
2599
  }
1736
- if (!best) break;
1737
- classes.push(best[0]);
1738
- for (const prop of best[1].keys()) remaining.delete(prop);
2600
+ for (const p of pseudos) {
2601
+ const isHas = pseudoName(p) === ":has";
2602
+ p.walkClasses((inner) => {
2603
+ const token = inner.value;
2604
+ this.#known.add(token);
2605
+ const u = this.#getUsage(token);
2606
+ u.referenced = true;
2607
+ u.loadBearing = true;
2608
+ if (isHas) u.asHasArgument = true;
2609
+ });
2610
+ }
2611
+ });
2612
+ if (hasCombinator || hasStructural) {
2613
+ complex.add(selector.toString().trim());
1739
2614
  }
1740
- const exact = remaining.size === 0 && !hasNonBase;
1741
- if (remaining.size === 0) return { classes, exact, warnings: [] };
1742
- const residual = synthesizeResidual(remaining, ctx);
1743
- return residual ? { classes, residual, exact, warnings: [] } : { classes, exact, warnings: [] };
1744
- }
1745
- selectorUsage(token) {
1746
- const ex = this.#extract(token);
1747
- if (!ex.produced || ex.opaque) return OPAQUE_USAGE;
1748
- const baseOnly = ex.blocks.length > 0 && ex.blocks.every((b) => conditionKey(b.condition) === conditionKey(BASE_CONDITION));
1749
- if (!baseOnly) return OPAQUE_USAGE;
1750
- return DROPPABLE_USAGE;
1751
- }
1752
- };
1753
- function expandForEmit(norm, prop, value, important) {
1754
- const pairsFor = (p, v) => norm.normalizeDeclaration(p, v, important).map((d) => [d.property, String(d.value)]);
1755
- if (prop === "size") {
1756
- return [...pairsFor("width", value), ...pairsFor("height", value)];
1757
2615
  }
1758
- if (prop === "inset-block" || prop === "inset-inline") {
1759
- const parts = value.split(/\s+/).filter((s) => s.length > 0);
1760
- const a = parts[0] ?? value;
1761
- const b = parts[1] ?? a;
1762
- const sides = prop === "inset-block" ? ["top", "bottom"] : ["left", "right"];
1763
- return [...pairsFor(sides[0], a), ...pairsFor(sides[1], b)];
2616
+ #addRuleEntry(token, entry) {
2617
+ const list = this.#classIndex.get(token);
2618
+ if (list) list.push(entry);
2619
+ else this.#classIndex.set(token, [entry]);
1764
2620
  }
1765
- return pairsFor(prop, value);
1766
- }
1767
- function synthesizeResidual(remaining, ctx) {
1768
- if (remaining.size === 0) return void 0;
1769
- const norm = ctx.normalizer ?? normalizer;
1770
- const decls = /* @__PURE__ */ new Map();
1771
- for (const [prop, value] of remaining) {
1772
- for (const decl of norm.normalizeDeclaration(String(prop), value, false)) {
1773
- decls.set(decl.property, decl);
2621
+ #getUsage(token) {
2622
+ let u = this.#usage.get(token);
2623
+ if (!u) {
2624
+ u = {
2625
+ referenced: false,
2626
+ asSubject: false,
2627
+ asAncestor: false,
2628
+ asCompound: false,
2629
+ asSibling: false,
2630
+ asHasArgument: false,
2631
+ asStructural: false,
2632
+ loadBearing: false
2633
+ };
2634
+ this.#usage.set(token, u);
1774
2635
  }
2636
+ return u;
1775
2637
  }
1776
- const block = { condition: BASE_CONDITION, decls };
1777
- const styleMap = { blocks: /* @__PURE__ */ new Map([[conditionKey(BASE_CONDITION), block]]) };
1778
- const css = [...remaining].map(([p, v]) => `${p}:${v}`).join(";");
1779
- const className = `df-${fnv1a(css)}`;
1780
- const synthetic = { className, decls: styleMap, css: `.${className}{${css}}` };
1781
- try {
1782
- ctx.sink.register(synthetic);
1783
- } catch {
1784
- }
1785
- return synthetic;
1786
- }
1787
- function unescapeClass(selector) {
1788
- const sel = selector.trim();
1789
- if (sel[0] !== ".") return null;
1790
- let out = "";
1791
- for (let i = 1; i < sel.length; i += 1) {
1792
- const c = sel[i];
1793
- if (c === "\\") {
1794
- i += 1;
1795
- if (i < sel.length) out += sel[i];
1796
- continue;
2638
+ /**
2639
+ * Resolve a set of tokens into a normalized condition-keyed StyleMap. `tokenList` is the original
2640
+ * class list (for per-declaration `tokenIndex` provenance); `request` is the set being resolved.
2641
+ */
2642
+ #resolveTokens(request, tokenList) {
2643
+ const entries = [];
2644
+ for (const token of new Set(request)) {
2645
+ const list = this.#classIndex.get(token);
2646
+ if (list) entries.push(...list);
1797
2647
  }
1798
- if (c === ":" || c === "." || c === "[" || c === " " || c === ">" || c === "+" || c === "~" || c === ",") {
1799
- return null;
2648
+ if (entries.length === 0) return emptyStyleMap();
2649
+ entries.sort((a, b) => a.order - b.order);
2650
+ const acc = /* @__PURE__ */ new Map();
2651
+ for (const entry of entries) {
2652
+ const key = conditionKey(entry.condition);
2653
+ let block = acc.get(key);
2654
+ if (!block) {
2655
+ block = { condition: entry.condition, decls: /* @__PURE__ */ new Map() };
2656
+ acc.set(key, block);
2657
+ }
2658
+ const tokenIndex = tokenList.indexOf(entry.token);
2659
+ const origin = { kind: "class", tokenIndex, className: entry.token };
2660
+ for (const [prop, value, important] of entry.decls) {
2661
+ for (const decl of normalizer.normalizeDeclaration(prop, value, important)) {
2662
+ block.decls.set(decl.property, { ...decl, origin });
2663
+ }
2664
+ }
1800
2665
  }
1801
- out += c;
2666
+ const rawBlocks = /* @__PURE__ */ new Map();
2667
+ for (const [key, block] of acc) {
2668
+ if (block.decls.size === 0) continue;
2669
+ rawBlocks.set(key, { condition: block.condition, decls: block.decls });
2670
+ }
2671
+ if (rawBlocks.size === 0) return emptyStyleMap();
2672
+ return normalizer.normalizeStyleMap({ blocks: rawBlocks });
1802
2673
  }
1803
- return out.length > 0 ? out : null;
1804
- }
1805
- function createTailwindResolver(config) {
1806
- return new TailwindResolver(config);
2674
+ /** Build (once) the reverse index used by {@link emit}. */
2675
+ #reverseIndex() {
2676
+ if (this.#reverse) return this.#reverse;
2677
+ const out = [];
2678
+ for (const token of this.#classIndex.keys()) {
2679
+ const styles = this.#resolveTokens([token], [token]);
2680
+ const keyed = /* @__PURE__ */ new Map();
2681
+ for (const [ck, block] of styles.blocks) {
2682
+ for (const [prop, decl] of block.decls) keyed.set(`${ck} ${prop}`, String(decl.value));
2683
+ }
2684
+ if (keyed.size > 0) out.push({ token, keyed });
2685
+ }
2686
+ out.sort((a, b) => b.keyed.size - a.keyed.size);
2687
+ this.#reverse = out;
2688
+ return out;
2689
+ }
2690
+ };
2691
+ function createCssResolver(cssFiles = [], options) {
2692
+ return new CustomCSSResolver(cssFiles, options);
1807
2693
  }
1808
2694
 
1809
2695
  // ../resolver-css/src/index.ts
1810
2696
  init_esm_shims();
1811
- import { readFileSync } from "fs";
1812
- import { createRequire as createRequire2 } from "module";
1813
- import * as path2 from "path";
1814
- function moduleBase2() {
1815
- return typeof __filename === "string" ? __filename : import.meta.url;
1816
- }
1817
- function loadPostcssEngine(projectRoot) {
1818
- const bases = [];
1819
- if (projectRoot) bases.push(path2.join(projectRoot, "__domflax__.js"));
1820
- bases.push(path2.join(process.cwd(), "__domflax__.js"));
1821
- bases.push(moduleBase2());
1822
- for (const base of bases) {
1823
- try {
1824
- const req = createRequire2(base);
1825
- req.resolve("postcss");
1826
- req.resolve("postcss-selector-parser");
1827
- const postcss = req("postcss");
1828
- const raw = req("postcss-selector-parser");
1829
- const selector = raw.default ?? raw;
1830
- return { parse: postcss.parse, selectorParser: selector };
1831
- } catch {
1832
- }
1833
- }
1834
- return null;
1835
- }
1836
- var pc = null;
1837
- var sp = null;
1838
- function ensurePostcss(projectRoot) {
1839
- if (pc && sp) return;
1840
- const engine = loadPostcssEngine(projectRoot);
1841
- if (!engine) {
1842
- throw new Error(
1843
- '@domflax/resolver-css requires "postcss" and "postcss-selector-parser" to be installed in your project (they are optional peer dependencies of domflax, loaded only when the custom-CSS provider is used). Install them with: npm install postcss postcss-selector-parser'
1844
- );
2697
+
2698
+ // ../frontend-jsx/src/frontend.ts
2699
+ init_esm_shims();
2700
+
2701
+ // ../frontend-jsx/src/frontend-ast.ts
2702
+ init_esm_shims();
2703
+ import babelTraverse from "@babel/traverse";
2704
+ var traverse = typeof babelTraverse === "function" ? babelTraverse : babelTraverse.default;
2705
+ var JSX_LANGS = ["jsx", "tsx"];
2706
+ var FILE_ID = 1;
2707
+ function jsxName(node) {
2708
+ switch (node.type) {
2709
+ case "JSXIdentifier":
2710
+ return node.name;
2711
+ case "JSXMemberExpression":
2712
+ return `${jsxName(node.object)}.${node.property.name}`;
2713
+ case "JSXNamespacedName":
2714
+ return `${node.namespace.name}:${node.name.name}`;
1845
2715
  }
1846
- pc = engine.parse;
1847
- sp = engine.selectorParser;
1848
2716
  }
1849
- var CSS_RESOLVER_ID = "css";
1850
- var CSS_RESOLVER_PROVIDER = "custom-css";
1851
- var ENGINE_VERSION = "css-index@1";
1852
- var STRUCTURAL_PSEUDOS = /* @__PURE__ */ new Set([
1853
- ":nth-child",
1854
- ":nth-last-child",
1855
- ":first-child",
1856
- ":last-child",
1857
- ":only-child",
1858
- ":nth-of-type",
1859
- ":nth-last-of-type",
1860
- ":first-of-type",
1861
- ":last-of-type",
1862
- ":only-of-type"
1863
- ]);
1864
- var FUNCTIONAL_PSEUDOS = /* @__PURE__ */ new Set([
1865
- ":not",
1866
- ":is",
1867
- ":where",
1868
- ":has",
1869
- ":matches"
1870
- ]);
1871
- var LEGACY_PSEUDO_ELEMENTS2 = /* @__PURE__ */ new Set([
1872
- ":before",
1873
- ":after",
1874
- ":first-line",
1875
- ":first-letter"
1876
- ]);
1877
- var CustomCSSResolver = class {
1878
- id = CSS_RESOLVER_ID;
1879
- provider = CSS_RESOLVER_PROVIDER;
1880
- fingerprint;
1881
- #files;
1882
- /** Forward map: class token → simple-`.class` rule contributions (source order). */
1883
- #classIndex = /* @__PURE__ */ new Map();
1884
- /** Selector-participation facts per class token. */
1885
- #usage = /* @__PURE__ */ new Map();
1886
- /** Every class referenced anywhere in the stylesheets (forward-resolvable or not). */
1887
- #known = /* @__PURE__ */ new Set();
1888
- /** Distinct COMPLEX selectors (combinator or structural pseudo), sorted. */
1889
- #complex;
1890
- #reverse = null;
1891
- constructor(cssFiles = [], options = {}) {
1892
- ensurePostcss(options.projectRoot);
1893
- const fromDisk = (options.files ?? []).map(readCssPath);
1894
- this.#files = [...cssFiles, ...fromDisk];
1895
- this.fingerprint = options.fingerprint ?? deriveFingerprint(this.provider, this.#files);
1896
- const complex = /* @__PURE__ */ new Set();
1897
- let order = 0;
1898
- for (const file of this.#files) {
1899
- order = this.#indexFile(file, order, complex);
1900
- }
1901
- this.#complex = [...complex].sort();
1902
- }
1903
- /** The stylesheets this resolver was constructed with (raw sources + any read from disk). */
1904
- get files() {
1905
- return this.#files;
2717
+ function isComponentName(node) {
2718
+ if (node.type === "JSXMemberExpression") return true;
2719
+ if (node.type === "JSXNamespacedName") return false;
2720
+ return /^[A-Z]/.test(node.name);
2721
+ }
2722
+ function attrName(name) {
2723
+ return name.type === "JSXNamespacedName" ? `${name.namespace.name}:${name.name.name}` : name.name;
2724
+ }
2725
+ function exprKind(node) {
2726
+ switch (node.type) {
2727
+ case "CallExpression":
2728
+ case "OptionalCallExpression":
2729
+ return "call";
2730
+ case "MemberExpression":
2731
+ case "OptionalMemberExpression":
2732
+ return "member";
2733
+ case "ConditionalExpression":
2734
+ case "LogicalExpression":
2735
+ return "conditional";
2736
+ case "TemplateLiteral":
2737
+ case "TaggedTemplateExpression":
2738
+ return "template";
2739
+ case "Identifier":
2740
+ return "identifier";
2741
+ case "SpreadElement":
2742
+ return "spread";
2743
+ default:
2744
+ return "other";
1906
2745
  }
1907
- /** Owns any plain class token referenced by one of {@link files}. */
1908
- owns(token) {
1909
- return isPlainClassToken(token) && this.#known.has(token);
2746
+ }
2747
+ function classFormOf(node) {
2748
+ switch (node.type) {
2749
+ case "TemplateLiteral":
2750
+ case "TaggedTemplateExpression":
2751
+ return "template-literal";
2752
+ case "CallExpression":
2753
+ case "OptionalCallExpression":
2754
+ return "call";
2755
+ case "ConditionalExpression":
2756
+ case "LogicalExpression":
2757
+ return "conditional";
2758
+ case "MemberExpression":
2759
+ case "OptionalMemberExpression":
2760
+ case "Identifier":
2761
+ return "member";
2762
+ default:
2763
+ return "call";
1910
2764
  }
1911
- resolve(input) {
1912
- const styles = this.#resolveTokens(input.classes, input.classes);
1913
- const resolved = [];
1914
- const unknown = [];
1915
- for (const token of input.classes) {
1916
- if (this.#classIndex.has(token)) resolved.push(token);
1917
- else unknown.push(token);
2765
+ }
2766
+ function findNestedJsxRoots(root) {
2767
+ const out = [];
2768
+ const seen = /* @__PURE__ */ new Set();
2769
+ const visit = (n) => {
2770
+ if (!n || seen.has(n)) return;
2771
+ seen.add(n);
2772
+ switch (n.type) {
2773
+ case "JSXElement":
2774
+ case "JSXFragment":
2775
+ out.push(n);
2776
+ return;
2777
+ case "ParenthesizedExpression":
2778
+ case "TSNonNullExpression":
2779
+ case "TSAsExpression":
2780
+ case "TSSatisfiesExpression":
2781
+ case "TSTypeAssertion":
2782
+ visit(n.expression);
2783
+ return;
2784
+ case "LogicalExpression":
2785
+ visit(n.left);
2786
+ visit(n.right);
2787
+ return;
2788
+ case "ConditionalExpression":
2789
+ visit(n.consequent);
2790
+ visit(n.alternate);
2791
+ return;
2792
+ case "SequenceExpression":
2793
+ for (const e of n.expressions) visit(e);
2794
+ return;
2795
+ case "CallExpression":
2796
+ case "OptionalCallExpression":
2797
+ for (const a of n.arguments) visit(a);
2798
+ return;
2799
+ case "ArrowFunctionExpression":
2800
+ case "FunctionExpression":
2801
+ visit(n.body);
2802
+ return;
2803
+ case "BlockStatement":
2804
+ for (const s of n.body) visit(s);
2805
+ return;
2806
+ case "ReturnStatement":
2807
+ visit(n.argument);
2808
+ return;
2809
+ case "IfStatement":
2810
+ visit(n.consequent);
2811
+ visit(n.alternate);
2812
+ return;
2813
+ case "ArrayExpression":
2814
+ for (const el of n.elements) visit(el);
2815
+ return;
2816
+ default:
2817
+ return;
1918
2818
  }
1919
- return { styles, resolved, unknown, opaque: [], warnings: [] };
1920
- }
1921
- emit(styles, ctx) {
1922
- const norm = ctx.normalizer ?? normalizer;
1923
- const remaining = /* @__PURE__ */ new Map();
1924
- for (const [ck, block] of norm.normalizeStyleMap(styles).blocks) {
1925
- for (const [prop, decl] of block.decls) {
1926
- remaining.set(`${ck}\0${prop}`, String(decl.value));
1927
- }
2819
+ };
2820
+ visit(root);
2821
+ return out;
2822
+ }
2823
+ function looksLikeJsx(id, code) {
2824
+ if (/\.[jt]sx$/i.test(id)) return true;
2825
+ return /<\/?[A-Za-z][\w.-]*|<>/.test(code);
2826
+ }
2827
+
2828
+ // ../frontend-jsx/src/frontend-parse.ts
2829
+ init_esm_shims();
2830
+ import { parse as babelParse } from "@babel/parser";
2831
+ function doParse(code, ctx) {
2832
+ const diagnostics = [];
2833
+ const doc = createDocument("jsx");
2834
+ const backref = doc.backref;
2835
+ const ast = babelParse(code, {
2836
+ sourceType: "module",
2837
+ plugins: ["jsx", "typescript"]
2838
+ });
2839
+ const eol = code.includes("\r\n") ? "\r\n" : "\n";
2840
+ const sourceFile = {
2841
+ id: FILE_ID,
2842
+ path: ctx.id,
2843
+ text: code,
2844
+ frontend: "jsx",
2845
+ eol,
2846
+ indentUnit: " ",
2847
+ native: ast
2848
+ };
2849
+ doc.sources.set(FILE_ID, sourceFile);
2850
+ const spanOf = (node) => {
2851
+ if (node.start == null || node.end == null) return null;
2852
+ const span = {
2853
+ file: FILE_ID,
2854
+ start: node.start,
2855
+ end: node.end,
2856
+ startLoc: node.loc ? { line: node.loc.start.line, column: node.loc.start.column } : void 0,
2857
+ endLoc: node.loc ? { line: node.loc.end.line, column: node.loc.end.column } : void 0
2858
+ };
2859
+ return span;
2860
+ };
2861
+ const sliceOf = (node) => node.start == null || node.end == null ? "" : code.slice(node.start, node.end);
2862
+ const internExpr = (node, spread) => {
2863
+ const payload = { text: sliceOf(node), spread };
2864
+ return doc.exprs.intern({
2865
+ span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
2866
+ kind: exprKind(node),
2867
+ payload
2868
+ });
2869
+ };
2870
+ const splitTokens = (raw) => raw.split(/\s+/).filter((t) => t.length > 0).map((value) => ({ value }));
2871
+ const buildClassList = (attr) => {
2872
+ const attrSpan = spanOf(attr) ?? void 0;
2873
+ const v = attr.value;
2874
+ const staticList = (tokens, valueSpan) => {
2875
+ const seg = { kind: "static", span: valueSpan ?? void 0, tokens };
2876
+ return {
2877
+ form: "string-literal",
2878
+ segments: [seg],
2879
+ valueSpan,
2880
+ attrSpan,
2881
+ hasDynamic: false,
2882
+ opaque: false,
2883
+ rewritable: true
2884
+ };
2885
+ };
2886
+ if (v == null) return staticList([], null);
2887
+ if (v.type === "StringLiteral") {
2888
+ return staticList(splitTokens(v.value), spanOf(v));
1928
2889
  }
1929
- if (remaining.size === 0) return { classes: [], exact: true, warnings: [] };
1930
- const classes = [];
1931
- for (const { token, keyed } of this.#reverseIndex()) {
1932
- let matches = true;
1933
- for (const [key, value] of keyed) {
1934
- if (remaining.get(key) !== value) {
1935
- matches = false;
1936
- break;
1937
- }
2890
+ if (v.type === "JSXExpressionContainer") {
2891
+ const expr = v.expression;
2892
+ if (expr.type === "StringLiteral") {
2893
+ return staticList(splitTokens(expr.value), spanOf(expr));
1938
2894
  }
1939
- if (!matches) continue;
1940
- classes.push(token);
1941
- for (const key of keyed.keys()) remaining.delete(key);
1942
- if (remaining.size === 0) break;
1943
- }
1944
- return { classes, exact: remaining.size === 0, warnings: [] };
1945
- }
1946
- selectorUsage(token) {
1947
- const u = this.#usage.get(token);
1948
- if (!u) {
2895
+ if (expr.type === "JSXEmptyExpression") return staticList([], null);
2896
+ const ref = internExpr(expr, false);
2897
+ const valueSpan = spanOf(expr);
2898
+ const seg = { kind: "dynamic", span: valueSpan ?? void 0, expr: ref };
1949
2899
  return {
1950
- asSubject: false,
1951
- asAncestor: false,
1952
- asCompound: false,
1953
- asSibling: false,
1954
- asHasArgument: false,
1955
- asStructural: false,
1956
- droppable: true
2900
+ form: classFormOf(expr),
2901
+ segments: [seg],
2902
+ valueSpan,
2903
+ attrSpan,
2904
+ hasDynamic: true,
2905
+ opaque: true,
2906
+ rewritable: false
1957
2907
  };
1958
2908
  }
1959
- return {
1960
- asSubject: u.asSubject,
1961
- asAncestor: u.asAncestor,
1962
- asCompound: u.asCompound,
1963
- asSibling: u.asSibling,
1964
- asHasArgument: u.asHasArgument,
1965
- asStructural: u.asStructural,
1966
- // Safe to drop/rename only when every reference is the lone subject of a bare `.x {}`.
1967
- droppable: u.referenced && !u.loadBearing
1968
- };
1969
- }
1970
- /**
1971
- * The distinct COMPLEX selectors found across all stylesheets — anything containing a combinator
1972
- * (descendant / `>` / `+` / `~`) or a structural pseudo (`:nth-child`, `:first-child`, …). Feeds
1973
- * domflax's CSS-selector-safety guard.
1974
- */
1975
- complexSelectors() {
1976
- return this.#complex;
1977
- }
1978
- /* ─────────────────────────── internals ─────────────────────────── */
1979
- /** Parse one stylesheet and fold its rules into the indexes. Returns the advanced order counter. */
1980
- #indexFile(file, startOrder, complex) {
1981
- let order = startOrder;
1982
- let root;
1983
- try {
1984
- root = pc(file.css, { from: file.id });
1985
- } catch {
1986
- return order;
2909
+ return emptyClassList();
2910
+ };
2911
+ const staticTokensOf2 = (classes) => {
2912
+ const out = [];
2913
+ for (const seg of classes.segments) {
2914
+ if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
1987
2915
  }
1988
- root.walkRules((rule) => {
1989
- const media = mediaContext(rule);
1990
- if (media.skip) return;
1991
- const decls = collectDecls(rule);
1992
- let ast;
1993
- try {
1994
- ast = sp().astSync(rule.selector);
1995
- } catch {
2916
+ return out;
2917
+ };
2918
+ const buildAttrValue = (attr) => {
2919
+ const v = attr.value;
2920
+ if (v == null) return { kind: "static", value: true, span: spanOf(attr) ?? void 0 };
2921
+ if (v.type === "StringLiteral") {
2922
+ return { kind: "static", value: v.value, span: spanOf(v) ?? void 0 };
2923
+ }
2924
+ if (v.type === "JSXExpressionContainer") {
2925
+ if (v.expression.type === "JSXEmptyExpression") {
2926
+ return { kind: "static", value: true, span: spanOf(v) ?? void 0 };
2927
+ }
2928
+ return { kind: "dynamic", expr: internExpr(v.expression, false), span: spanOf(v) ?? void 0 };
2929
+ }
2930
+ return { kind: "dynamic", expr: internExpr(v, false), span: spanOf(v) ?? void 0 };
2931
+ };
2932
+ const buildNestedRoot = (jsx, parentId) => jsx.type === "JSXFragment" ? buildFragment(jsx, parentId) : buildElement(jsx, parentId);
2933
+ const appendChild = (node, parentId, out) => {
2934
+ switch (node.type) {
2935
+ case "JSXText": {
2936
+ const id = doc.alloc.next();
2937
+ doc.nodes.set(
2938
+ id,
2939
+ createText(id, node.value, {
2940
+ parent: parentId,
2941
+ span: spanOf(node),
2942
+ collapsible: /^\s*$/.test(node.value)
2943
+ })
2944
+ );
2945
+ out.push(id);
1996
2946
  return;
1997
2947
  }
1998
- for (const sel of ast.nodes) {
1999
- const thisOrder = order;
2000
- this.#analyzeSelector(sel, media.media, decls, thisOrder, complex);
2001
- }
2002
- order += 1;
2003
- });
2004
- return order;
2005
- }
2006
- /** Analyze one comma-segment selector: forward indexing, usage facts, complex detection. */
2007
- #analyzeSelector(selector, media, decls, order, complex) {
2008
- const compounds = splitCompounds(selector);
2009
- let hasCombinator = false;
2010
- let hasStructural = false;
2011
- compounds.forEach((compound, index) => {
2012
- const isSubject = index === compounds.length - 1;
2013
- const rightCombinator = index < compounds.length - 1 ? compounds[index + 1].leftCombinator : null;
2014
- if (rightCombinator) hasCombinator = true;
2015
- const classes = compound.nodes.filter((n) => sp.isClassName(n));
2016
- const otherSimple = compound.nodes.some(
2017
- (n) => sp.isTag(n) || sp.isIdentifier(n) || sp.isAttribute(n) || sp.isUniversal(n) || sp.isNesting(n)
2018
- );
2019
- const pseudos = compound.nodes.filter((n) => sp.isPseudo(n));
2020
- const structuralPseudo = pseudos.some((p) => STRUCTURAL_PSEUDOS.has(pseudoName(p)));
2021
- const functionalPseudo = pseudos.some((p) => FUNCTIONAL_PSEUDOS.has(pseudoName(p)));
2022
- const statePseudos = pseudos.filter(
2023
- (p) => sp.isPseudoClass(p) && !STRUCTURAL_PSEUDOS.has(pseudoName(p)) && !FUNCTIONAL_PSEUDOS.has(pseudoName(p))
2024
- );
2025
- const elementPseudos = pseudos.filter((p) => isPseudoElement(p));
2026
- const qualified = classes.length > 1 || otherSimple || functionalPseudo || statePseudos.length > 0;
2027
- if (structuralPseudo) hasStructural = true;
2028
- for (const cls of classes) {
2029
- const token = cls.value;
2030
- this.#known.add(token);
2031
- const u = this.#getUsage(token);
2032
- u.referenced = true;
2033
- if (isSubject) u.asSubject = true;
2034
- if (rightCombinator === " " || rightCombinator === ">") u.asAncestor = true;
2035
- if (rightCombinator === "+" || rightCombinator === "~") u.asSibling = true;
2036
- if (qualified) u.asCompound = true;
2037
- if (structuralPseudo) u.asStructural = true;
2038
- if (rightCombinator !== null || qualified || structuralPseudo || elementPseudos.length > 0) {
2039
- u.loadBearing = true;
2948
+ case "JSXExpressionContainer": {
2949
+ const expr = node.expression;
2950
+ if (expr.type === "JSXEmptyExpression") return;
2951
+ if (expr.type === "JSXElement" || expr.type === "JSXFragment") {
2952
+ out.push(buildNestedRoot(expr, parentId));
2953
+ return;
2040
2954
  }
2041
- const forwardEligible = compounds.length === 1 && classes.length === 1 && !otherSimple && !structuralPseudo && !functionalPseudo && elementPseudos.length <= 1;
2042
- if (forwardEligible && decls.length > 0) {
2043
- const condition = {
2044
- media,
2045
- states: statePseudos.map(pseudoName).sort(),
2046
- pseudoElement: elementPseudos.length === 1 ? normalizePseudoElement(elementPseudos[0]) : ""
2047
- };
2048
- this.#addRuleEntry(token, { order, token, condition, decls });
2955
+ const id = doc.alloc.next();
2956
+ const ref = internExpr(expr, false);
2957
+ doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
2958
+ out.push(id);
2959
+ for (const jsx of findNestedJsxRoots(expr)) out.push(buildNestedRoot(jsx, parentId));
2960
+ return;
2961
+ }
2962
+ case "JSXSpreadChild": {
2963
+ const id = doc.alloc.next();
2964
+ const ref = internExpr(node.expression, true);
2965
+ doc.nodes.set(id, createExpr(id, ref, { parent: parentId, span: spanOf(node) }));
2966
+ out.push(id);
2967
+ for (const jsx of findNestedJsxRoots(node.expression)) {
2968
+ out.push(buildNestedRoot(jsx, parentId));
2049
2969
  }
2970
+ return;
2050
2971
  }
2051
- for (const p of pseudos) {
2052
- const isHas = pseudoName(p) === ":has";
2053
- p.walkClasses((inner) => {
2054
- const token = inner.value;
2055
- this.#known.add(token);
2056
- const u = this.#getUsage(token);
2057
- u.referenced = true;
2058
- u.loadBearing = true;
2059
- if (isHas) u.asHasArgument = true;
2972
+ case "JSXElement":
2973
+ out.push(buildElement(node, parentId));
2974
+ return;
2975
+ case "JSXFragment":
2976
+ out.push(buildFragment(node, parentId));
2977
+ return;
2978
+ default:
2979
+ return;
2980
+ }
2981
+ };
2982
+ const buildFragment = (node, parentId) => {
2983
+ const id = doc.alloc.next();
2984
+ const children = [];
2985
+ for (const c of node.children) appendChild(c, id, children);
2986
+ doc.nodes.set(id, createFragment(id, { children, parent: parentId, span: spanOf(node) }));
2987
+ backref.set(id, {
2988
+ nodeId: id,
2989
+ span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
2990
+ openTagSpan: spanOf(node.openingFragment),
2991
+ closeTagSpan: spanOf(node.closingFragment),
2992
+ innerSpan: null,
2993
+ selfClosing: false
2994
+ });
2995
+ return id;
2996
+ };
2997
+ const buildElement = (node, parentId) => {
2998
+ const id = doc.alloc.next();
2999
+ const opening = node.openingElement;
3000
+ const tag = jsxName(opening.name);
3001
+ const component = isComponentName(opening.name);
3002
+ const meta = defaultMeta();
3003
+ meta.isComponent = component;
3004
+ let classes = emptyClassList();
3005
+ const entries = /* @__PURE__ */ new Map();
3006
+ const order = [];
3007
+ const spreads = [];
3008
+ for (const attr of opening.attributes) {
3009
+ if (attr.type === "JSXSpreadAttribute") {
3010
+ spreads.push(internExpr(attr.argument, true));
3011
+ meta.hasSpreadAttrs = true;
3012
+ continue;
3013
+ }
3014
+ const name = attrName(attr.name);
3015
+ if (name === "className" || name === "class") {
3016
+ classes = buildClassList(attr);
3017
+ continue;
3018
+ }
3019
+ if (name === "ref") meta.hasRef = true;
3020
+ else if (name === "key") meta.hasKey = true;
3021
+ else if (name === "dangerouslySetInnerHTML") meta.hasDangerousHtml = true;
3022
+ else if (/^on[A-Z]/.test(name)) meta.hasEventHandlers = true;
3023
+ entries.set(name, buildAttrValue(attr));
3024
+ order.push(name);
3025
+ }
3026
+ const attrs = { entries, spreads, order };
3027
+ const children = [];
3028
+ for (const c of node.children) appendChild(c, id, children);
3029
+ for (const cid of children) {
3030
+ const cn = doc.nodes.get(cid);
3031
+ if (cn && cn.kind === "expr") {
3032
+ meta.hasDynamicChildren = true;
3033
+ break;
3034
+ }
3035
+ }
3036
+ let computed2 = emptyStyleMap();
3037
+ if (!classes.hasDynamic) {
3038
+ const tokens = staticTokensOf2(classes);
3039
+ if (tokens.length > 0) {
3040
+ const res = ctx.resolver.resolve({
3041
+ classes: tokens,
3042
+ element: { tagName: tag, namespace: component ? void 0 : "html" }
2060
3043
  });
3044
+ computed2 = ctx.normalizer.normalizeStyleMap(res.styles);
3045
+ for (const w of res.warnings) {
3046
+ diagnostics.push({
3047
+ code: "DF_STYLE_CONFLICT_UNRESOLVED",
3048
+ severity: w.severity,
3049
+ message: w.message,
3050
+ nodeId: id
3051
+ });
3052
+ }
2061
3053
  }
3054
+ }
3055
+ const namespace = component ? "component" : "html";
3056
+ const el = createElement(id, {
3057
+ tag,
3058
+ namespace,
3059
+ isComponent: component,
3060
+ selfClosing: opening.selfClosing,
3061
+ classes,
3062
+ computed: computed2,
3063
+ attrs,
3064
+ children,
3065
+ parent: parentId,
3066
+ span: spanOf(node),
3067
+ meta
2062
3068
  });
2063
- if (hasCombinator || hasStructural) {
2064
- complex.add(selector.toString().trim());
3069
+ doc.nodes.set(id, el);
3070
+ const inner = children.length > 0 ? spanOf(node.children[0]) && spanOf(node.children.at(-1)) ? {
3071
+ file: FILE_ID,
3072
+ start: spanOf(node.children[0]).start,
3073
+ end: spanOf(node.children.at(-1)).end
3074
+ } : null : null;
3075
+ backref.set(id, {
3076
+ nodeId: id,
3077
+ span: spanOf(node) ?? { file: FILE_ID, start: 0, end: 0 },
3078
+ openTagSpan: spanOf(opening),
3079
+ closeTagSpan: node.closingElement ? spanOf(node.closingElement) : null,
3080
+ innerSpan: inner,
3081
+ selfClosing: opening.selfClosing
3082
+ });
3083
+ return id;
3084
+ };
3085
+ const roots = [];
3086
+ traverse(ast, {
3087
+ JSXElement(path3) {
3088
+ roots.push(path3.node);
3089
+ path3.skip();
3090
+ },
3091
+ JSXFragment(path3) {
3092
+ roots.push(path3.node);
3093
+ path3.skip();
2065
3094
  }
3095
+ });
3096
+ const rootFrag = doc.nodes.get(doc.root);
3097
+ for (const r of roots) {
3098
+ const id = r.type === "JSXFragment" ? buildFragment(r, doc.root) : buildElement(r, doc.root);
3099
+ rootFrag.children.push(id);
2066
3100
  }
2067
- #addRuleEntry(token, entry) {
2068
- const list = this.#classIndex.get(token);
2069
- if (list) list.push(entry);
2070
- else this.#classIndex.set(token, [entry]);
3101
+ return { doc, diagnostics };
3102
+ }
3103
+
3104
+ // ../frontend-jsx/src/frontend.ts
3105
+ var jsxFrontend = {
3106
+ name: "babel-jsx",
3107
+ langs: JSX_LANGS,
3108
+ canParse(id, code) {
3109
+ return looksLikeJsx(id, code);
3110
+ },
3111
+ parse(code, ctx) {
3112
+ return doParse(code, ctx);
2071
3113
  }
2072
- #getUsage(token) {
2073
- let u = this.#usage.get(token);
2074
- if (!u) {
2075
- u = {
2076
- referenced: false,
2077
- asSubject: false,
2078
- asAncestor: false,
2079
- asCompound: false,
2080
- asSibling: false,
2081
- asHasArgument: false,
2082
- asStructural: false,
2083
- loadBearing: false
2084
- };
2085
- this.#usage.set(token, u);
2086
- }
2087
- return u;
3114
+ };
3115
+ function createJsxFrontend() {
3116
+ return jsxFrontend;
3117
+ }
3118
+
3119
+ // ../frontend-jsx/src/backend.ts
3120
+ init_esm_shims();
3121
+ import MagicString from "magic-string";
3122
+ var JSX_LANGS2 = ["jsx", "tsx"];
3123
+ function exprText(doc, ref) {
3124
+ const rec = doc.exprs.get(ref);
3125
+ const payload = rec?.payload;
3126
+ if (payload && typeof payload.text === "string") {
3127
+ return { text: payload.text, spread: payload.spread === true };
3128
+ }
3129
+ if (rec) {
3130
+ const sf = doc.sources.get(rec.span.file);
3131
+ if (sf) return { text: sf.text.slice(rec.span.start, rec.span.end), spread: false };
3132
+ }
3133
+ return { text: "", spread: false };
3134
+ }
3135
+ function staticTokensOf(classes) {
3136
+ const out = [];
3137
+ for (const seg of classes.segments) {
3138
+ if (seg.kind === "static") for (const t of seg.tokens) out.push(t.value);
3139
+ }
3140
+ return out;
3141
+ }
3142
+ function primarySource(doc) {
3143
+ for (const sf of doc.sources.values()) {
3144
+ if (typeof sf.text === "string" && sf.text.length > 0) return sf;
2088
3145
  }
2089
- /**
2090
- * Resolve a set of tokens into a normalized condition-keyed StyleMap. `tokenList` is the original
2091
- * class list (for per-declaration `tokenIndex` provenance); `request` is the set being resolved.
2092
- */
2093
- #resolveTokens(request, tokenList) {
2094
- const entries = [];
2095
- for (const token of new Set(request)) {
2096
- const list = this.#classIndex.get(token);
2097
- if (list) entries.push(...list);
3146
+ return null;
3147
+ }
3148
+ function collectKept(doc) {
3149
+ const out = [];
3150
+ const seen = /* @__PURE__ */ new Set();
3151
+ const visit = (id) => {
3152
+ if (seen.has(id)) return;
3153
+ seen.add(id);
3154
+ const n = doc.nodes.get(id);
3155
+ if (!n) return;
3156
+ out.push(n);
3157
+ if (n.kind === "element" || n.kind === "fragment") for (const c of n.children) visit(c);
3158
+ };
3159
+ visit(doc.root);
3160
+ return out;
3161
+ }
3162
+ function strictlyContains(a, b) {
3163
+ if (a.file !== b.file) return false;
3164
+ if (a.start <= b.start && b.end <= a.end) return !(a.start === b.start && a.end === b.end);
3165
+ return false;
3166
+ }
3167
+ function editClasses(ms, doc, sf, el) {
3168
+ const classes = el.classes;
3169
+ if (classes.hasDynamic || classes.opaque) return false;
3170
+ const tokens = staticTokensOf(classes);
3171
+ const valueSpan = classes.valueSpan;
3172
+ if (valueSpan && valueSpan.file === sf.id) {
3173
+ const current = sf.text.slice(valueSpan.start, valueSpan.end);
3174
+ const quote = current.startsWith("'") ? "'" : '"';
3175
+ const next = `${quote}${tokens.join(" ")}${quote}`;
3176
+ if (current !== next) {
3177
+ ms.overwrite(valueSpan.start, valueSpan.end, next);
3178
+ return true;
2098
3179
  }
2099
- if (entries.length === 0) return emptyStyleMap();
2100
- entries.sort((a, b) => a.order - b.order);
2101
- const acc = /* @__PURE__ */ new Map();
2102
- for (const entry of entries) {
2103
- const key = conditionKey(entry.condition);
2104
- let block = acc.get(key);
2105
- if (!block) {
2106
- block = { condition: entry.condition, decls: /* @__PURE__ */ new Map() };
2107
- acc.set(key, block);
2108
- }
2109
- const tokenIndex = tokenList.indexOf(entry.token);
2110
- const origin = { kind: "class", tokenIndex, className: entry.token };
2111
- for (const [prop, value, important] of entry.decls) {
2112
- for (const decl of normalizer.normalizeDeclaration(prop, value, important)) {
2113
- block.decls.set(decl.property, { ...decl, origin });
3180
+ return false;
3181
+ }
3182
+ if (tokens.length === 0) return false;
3183
+ if (el.isComponent) return false;
3184
+ const back = doc.backref.get(el.id);
3185
+ const openTag = back?.openTagSpan;
3186
+ if (!openTag || openTag.file !== sf.id) return false;
3187
+ const insertAt = openTag.start + 1 + el.tag.length;
3188
+ ms.appendLeft(insertAt, ` className="${tokens.join(" ")}"`);
3189
+ return true;
3190
+ }
3191
+ function extractKeyAttr(openTag) {
3192
+ const m = /(^|\s)key\s*=\s*/.exec(openTag);
3193
+ if (!m) return null;
3194
+ const keyStart = m.index + m[1].length;
3195
+ let i = m.index + m[0].length;
3196
+ const ch = openTag[i];
3197
+ if (ch === "{") {
3198
+ let depth = 0;
3199
+ for (; i < openTag.length; i += 1) {
3200
+ const c = openTag[i];
3201
+ if (c === "{") depth += 1;
3202
+ else if (c === "}") {
3203
+ depth -= 1;
3204
+ if (depth === 0) {
3205
+ i += 1;
3206
+ break;
2114
3207
  }
2115
3208
  }
2116
3209
  }
2117
- const rawBlocks = /* @__PURE__ */ new Map();
2118
- for (const [key, block] of acc) {
2119
- if (block.decls.size === 0) continue;
2120
- rawBlocks.set(key, { condition: block.condition, decls: block.decls });
2121
- }
2122
- if (rawBlocks.size === 0) return emptyStyleMap();
2123
- return normalizer.normalizeStyleMap({ blocks: rawBlocks });
2124
- }
2125
- /** Build (once) the reverse index used by {@link emit}. */
2126
- #reverseIndex() {
2127
- if (this.#reverse) return this.#reverse;
2128
- const out = [];
2129
- for (const token of this.#classIndex.keys()) {
2130
- const styles = this.#resolveTokens([token], [token]);
2131
- const keyed = /* @__PURE__ */ new Map();
2132
- for (const [ck, block] of styles.blocks) {
2133
- for (const [prop, decl] of block.decls) keyed.set(`${ck}\0${prop}`, String(decl.value));
3210
+ } else if (ch === '"' || ch === "'") {
3211
+ const q = ch;
3212
+ i += 1;
3213
+ for (; i < openTag.length; i += 1) {
3214
+ if (openTag[i] === q) {
3215
+ i += 1;
3216
+ break;
2134
3217
  }
2135
- if (keyed.size > 0) out.push({ token, keyed });
2136
3218
  }
2137
- out.sort((a, b) => b.keyed.size - a.keyed.size);
2138
- this.#reverse = out;
2139
- return out;
3219
+ } else {
3220
+ for (; i < openTag.length; i += 1) {
3221
+ if (/[\s>/]/.test(openTag[i])) break;
3222
+ }
2140
3223
  }
2141
- };
2142
- function createCssResolver(cssFiles = [], options) {
2143
- return new CustomCSSResolver(cssFiles, options);
3224
+ return openTag.slice(keyStart, i);
2144
3225
  }
2145
- function splitCompounds(selector) {
2146
- const compounds = [];
2147
- let current = [];
2148
- let leftCombinator = null;
2149
- for (const node of selector.nodes) {
2150
- if (sp.isCombinator(node)) {
2151
- compounds.push({ leftCombinator, nodes: current });
2152
- current = [];
2153
- leftCombinator = combinatorValue(node);
3226
+ function transferKeyOnUnwrap(ms, doc, sf, region, kept) {
3227
+ const open = region.openTagSpan;
3228
+ if (!open || open.file !== sf.id) return;
3229
+ const keyAttr = extractKeyAttr(sf.text.slice(open.start, open.end));
3230
+ if (!keyAttr) return;
3231
+ const inside = [];
3232
+ for (const n of kept) {
3233
+ if (n.kind !== "element" || !n.span || n.span.file !== sf.id) continue;
3234
+ if (strictlyContains(region.span, n.span)) inside.push(n);
3235
+ }
3236
+ const maximal = inside.filter(
3237
+ (n) => !inside.some((o) => o !== n && o.span && n.span && strictlyContains(o.span, n.span))
3238
+ );
3239
+ if (maximal.length !== 1) return;
3240
+ const child = maximal[0];
3241
+ const childOpen = doc.backref.get(child.id)?.openTagSpan;
3242
+ if (!childOpen || childOpen.file !== sf.id) return;
3243
+ if (extractKeyAttr(sf.text.slice(childOpen.start, childOpen.end))) return;
3244
+ ms.appendLeft(childOpen.start + 1 + child.tag.length, ` ${keyAttr}`);
3245
+ }
3246
+ function surgicalPrint(doc) {
3247
+ const sf = primarySource(doc);
3248
+ if (!sf) return null;
3249
+ const ms = new MagicString(sf.text);
3250
+ const kept = collectKept(doc);
3251
+ const keptSpans = [];
3252
+ for (const n of kept) if (n.span && n.span.file === sf.id) keptSpans.push(n.span);
3253
+ const removed = [];
3254
+ for (const id of backrefIds(doc)) {
3255
+ if (doc.nodes.has(id)) continue;
3256
+ const back = doc.backref.get(id);
3257
+ if (!back || back.span.file !== sf.id) continue;
3258
+ const unwrapped = keptSpans.some((k) => strictlyContains(back.span, k));
3259
+ removed.push({ backref: back, unwrapped });
3260
+ }
3261
+ const fullRemovals = removed.filter((r) => !r.unwrapped).map((r) => r.backref.span);
3262
+ for (const r of removed) {
3263
+ const span = r.backref.span;
3264
+ const coveredByFull = fullRemovals.some((f) => f !== span && strictlyContains(f, span));
3265
+ if (coveredByFull) continue;
3266
+ if (r.unwrapped) {
3267
+ transferKeyOnUnwrap(ms, doc, sf, r.backref, kept);
3268
+ const open = r.backref.openTagSpan;
3269
+ const close = r.backref.closeTagSpan;
3270
+ if (open && open.file === sf.id && open.end > open.start) ms.remove(open.start, open.end);
3271
+ if (close && close.file === sf.id && close.end > close.start) {
3272
+ ms.remove(close.start, close.end);
3273
+ }
2154
3274
  } else {
2155
- current.push(node);
3275
+ ms.remove(span.start, span.end);
2156
3276
  }
2157
3277
  }
2158
- compounds.push({ leftCombinator, nodes: current });
2159
- return compounds;
2160
- }
2161
- function combinatorValue(node) {
2162
- const v = node.value;
2163
- return v.trim() === "" ? " " : v.trim();
3278
+ for (const n of kept) {
3279
+ if (n.kind === "element") editClasses(ms, doc, sf, n);
3280
+ }
3281
+ return ms.toString();
2164
3282
  }
2165
- function pseudoName(node) {
2166
- return node.value.toLowerCase();
3283
+ function backrefIds(doc) {
3284
+ const out = [];
3285
+ const max = doc.alloc.peek;
3286
+ for (let i = 1; i < max; i += 1) {
3287
+ const id = i;
3288
+ if (doc.backref.get(id)) out.push(id);
3289
+ }
3290
+ return out;
2167
3291
  }
2168
- function isPseudoElement(node) {
2169
- return sp.isPseudoElement(node) || LEGACY_PSEUDO_ELEMENTS2.has(pseudoName(node));
3292
+ function classText(doc, classes) {
3293
+ if (classes.form === "absent" || classes.segments.length === 0) return null;
3294
+ const dynamic = classes.segments.find((s) => s.kind === "dynamic");
3295
+ if (dynamic && dynamic.kind === "dynamic") {
3296
+ return `className={${exprText(doc, dynamic.expr).text}}`;
3297
+ }
3298
+ const tokens = staticTokensOf(classes);
3299
+ return `className="${tokens.join(" ")}"`;
2170
3300
  }
2171
- function normalizePseudoElement(node) {
2172
- const name = pseudoName(node);
2173
- return name.startsWith("::") ? name : `::${name.replace(/^:/, "")}`;
3301
+ function attrText(doc, name, value) {
3302
+ if (value.kind === "static") {
3303
+ if (value.value === true) return name;
3304
+ if (value.value === false) return "";
3305
+ return `${name}="${String(value.value)}"`;
3306
+ }
3307
+ return `${name}={${exprText(doc, value.expr).text}}`;
2174
3308
  }
2175
- function mediaContext(rule) {
3309
+ function printElement(doc, el) {
2176
3310
  const parts = [];
2177
- let skip = false;
2178
- let parent = rule.parent;
2179
- while (parent && parent.type === "atrule") {
2180
- const at = parent;
2181
- const name = at.name.toLowerCase();
2182
- if (name === "media") parts.unshift(at.params.trim().replace(/\s+/g, " "));
2183
- else if (name === "keyframes" || name.endsWith("keyframes") || name === "font-face") skip = true;
2184
- parent = parent.parent;
3311
+ const cls = classText(doc, el.classes);
3312
+ if (cls !== null) parts.push(cls);
3313
+ for (const name of el.attrs.order) {
3314
+ const v = el.attrs.entries.get(name);
3315
+ if (!v) continue;
3316
+ const text = attrText(doc, name, v);
3317
+ if (text.length > 0) parts.push(text);
2185
3318
  }
2186
- return { media: parts.join(" and "), skip };
3319
+ for (const ref of el.attrs.spreads) parts.push(`{...${exprText(doc, ref).text}}`);
3320
+ const attrStr = parts.length > 0 ? ` ${parts.join(" ")}` : "";
3321
+ const tag = el.tag;
3322
+ if (el.children.length === 0) {
3323
+ return el.selfClosing ? `<${tag}${attrStr} />` : `<${tag}${attrStr}></${tag}>`;
3324
+ }
3325
+ const inner = el.children.map((c) => printNode(doc, c)).join("");
3326
+ return `<${tag}${attrStr}>${inner}</${tag}>`;
2187
3327
  }
2188
- function collectDecls(rule) {
2189
- const out = [];
2190
- for (const node of rule.nodes) {
2191
- if (node.type === "decl") out.push([node.prop, node.value, node.important === true]);
3328
+ function printNode(doc, id) {
3329
+ const node = doc.nodes.get(id);
3330
+ if (!node) return "";
3331
+ switch (node.kind) {
3332
+ case "text":
3333
+ return node.value;
3334
+ case "comment":
3335
+ return `{/*${node.value}*/}`;
3336
+ case "expr": {
3337
+ const { text, spread } = exprText(doc, node.expr);
3338
+ return spread ? `{...${text}}` : `{${text}}`;
3339
+ }
3340
+ case "fragment":
3341
+ return `<>${node.children.map((c) => printNode(doc, c)).join("")}</>`;
3342
+ case "element":
3343
+ return printElement(doc, node);
2192
3344
  }
2193
- return out;
2194
3345
  }
2195
- function isPlainClassToken(token) {
2196
- return token.length > 0 && !/[\s.#>+~:[\]()]/.test(token);
3346
+ function rePrint(doc) {
3347
+ const root = doc.nodes.get(doc.root);
3348
+ if (!root || root.kind !== "fragment") return printNode(doc, doc.root);
3349
+ return root.children.map((c) => printNode(doc, c)).join("");
2197
3350
  }
2198
- function readCssPath(path3) {
2199
- try {
2200
- return { id: path3, css: readFileSync(path3, "utf8") };
2201
- } catch (cause) {
2202
- throw new Error(`resolver-css: cannot read CSS file "${path3}"`, { cause });
2203
- }
3351
+ function doPrint(doc) {
3352
+ const surgical = surgicalPrint(doc);
3353
+ return surgical ?? rePrint(doc);
2204
3354
  }
2205
- function deriveFingerprint(provider, files) {
2206
- const parts = files.map((f) => `${f.id}:${f.css.length}`).sort();
2207
- return `${provider}/${ENGINE_VERSION}::${parts.join("|")}`;
3355
+ var jsxBackend = {
3356
+ name: "babel-jsx",
3357
+ langs: JSX_LANGS2,
3358
+ print(doc, _plan, _ctx) {
3359
+ const code = doPrint(doc);
3360
+ return { code, map: null, edits: [], diagnostics: [] };
3361
+ }
3362
+ };
3363
+ function createJsxBackend() {
3364
+ return jsxBackend;
2208
3365
  }
2209
3366
 
3367
+ // ../frontend-jsx/src/index.ts
3368
+ init_esm_shims();
3369
+
2210
3370
  export {
3371
+ displayContentsWrapper,
2211
3372
  emptyStyleDiv,
2212
3373
  flexCenterWrapper,
3374
+ inlineFlexCenterWrapper,
2213
3375
  nestedFlexMerge,
3376
+ nestedGridMerge,
2214
3377
  passthroughWrapper,
2215
3378
  redundantFragment,
3379
+ redundantInlineWrapper,
3380
+ borderRadiusShorthand,
3381
+ borderShorthand,
2216
3382
  dedupeClasses,
3383
+ gapShorthand,
2217
3384
  insetShorthand,
2218
3385
  marginShorthand,
3386
+ overflowShorthand,
3387
+ overscrollBehaviorShorthand,
2219
3388
  paddingShorthand,
3389
+ placeShorthand,
3390
+ scrollMarginShorthand,
3391
+ scrollPaddingShorthand,
2220
3392
  sizeShorthand,
2221
3393
  builtinPatterns,
2222
- createJsxFrontend,
2223
- createJsxBackend,
2224
3394
  createTailwindResolver,
2225
- createCssResolver
3395
+ createCssResolver,
3396
+ createJsxFrontend,
3397
+ createJsxBackend
2226
3398
  };
2227
- //# sourceMappingURL=chunk-4HHISSMR.js.map
3399
+ //# sourceMappingURL=chunk-DNHOGPYV.js.map