@jesscss/core 2.0.0-alpha.2 → 2.0.0-alpha.5

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 (106) hide show
  1. package/README.md +3 -1
  2. package/lib/tree/ampersand.d.ts +4 -0
  3. package/lib/tree/ampersand.d.ts.map +1 -1
  4. package/lib/tree/ampersand.js +51 -1
  5. package/lib/tree/ampersand.js.map +1 -1
  6. package/lib/tree/any.d.ts +1 -1
  7. package/lib/tree/any.d.ts.map +1 -1
  8. package/lib/tree/any.js +5 -2
  9. package/lib/tree/any.js.map +1 -1
  10. package/lib/tree/bool.d.ts +1 -0
  11. package/lib/tree/bool.d.ts.map +1 -1
  12. package/lib/tree/bool.js +5 -1
  13. package/lib/tree/bool.js.map +1 -1
  14. package/lib/tree/color.d.ts.map +1 -1
  15. package/lib/tree/color.js +2 -2
  16. package/lib/tree/color.js.map +1 -1
  17. package/lib/tree/combinator.d.ts +1 -0
  18. package/lib/tree/combinator.d.ts.map +1 -1
  19. package/lib/tree/combinator.js +5 -1
  20. package/lib/tree/combinator.js.map +1 -1
  21. package/lib/tree/comment.d.ts.map +1 -1
  22. package/lib/tree/comment.js +2 -1
  23. package/lib/tree/comment.js.map +1 -1
  24. package/lib/tree/declaration-var.d.ts +0 -1
  25. package/lib/tree/declaration-var.d.ts.map +1 -1
  26. package/lib/tree/declaration-var.js +1 -1
  27. package/lib/tree/declaration-var.js.map +1 -1
  28. package/lib/tree/declaration.d.ts.map +1 -1
  29. package/lib/tree/declaration.js +5 -8
  30. package/lib/tree/declaration.js.map +1 -1
  31. package/lib/tree/dimension.d.ts +1 -0
  32. package/lib/tree/dimension.d.ts.map +1 -1
  33. package/lib/tree/dimension.js +5 -2
  34. package/lib/tree/dimension.js.map +1 -1
  35. package/lib/tree/expression.d.ts +1 -0
  36. package/lib/tree/expression.d.ts.map +1 -1
  37. package/lib/tree/expression.js +5 -1
  38. package/lib/tree/expression.js.map +1 -1
  39. package/lib/tree/extend-list.d.ts +2 -2
  40. package/lib/tree/extend-list.d.ts.map +1 -1
  41. package/lib/tree/extend-list.js +5 -2
  42. package/lib/tree/extend-list.js.map +1 -1
  43. package/lib/tree/extend.d.ts +2 -2
  44. package/lib/tree/extend.d.ts.map +1 -1
  45. package/lib/tree/extend.js +6 -2
  46. package/lib/tree/extend.js.map +1 -1
  47. package/lib/tree/import-style.d.ts.map +1 -1
  48. package/lib/tree/import-style.js +11 -1
  49. package/lib/tree/import-style.js.map +1 -1
  50. package/lib/tree/nil.d.ts +0 -1
  51. package/lib/tree/nil.d.ts.map +1 -1
  52. package/lib/tree/nil.js +2 -3
  53. package/lib/tree/nil.js.map +1 -1
  54. package/lib/tree/node-base.d.ts +2 -0
  55. package/lib/tree/node-base.d.ts.map +1 -1
  56. package/lib/tree/node-base.js +46 -0
  57. package/lib/tree/node-base.js.map +1 -1
  58. package/lib/tree/paren.d.ts +2 -1
  59. package/lib/tree/paren.d.ts.map +1 -1
  60. package/lib/tree/paren.js +7 -1
  61. package/lib/tree/paren.js.map +1 -1
  62. package/lib/tree/quoted.d.ts +2 -1
  63. package/lib/tree/quoted.d.ts.map +1 -1
  64. package/lib/tree/quoted.js +10 -1
  65. package/lib/tree/quoted.js.map +1 -1
  66. package/lib/tree/reference.d.ts.map +1 -1
  67. package/lib/tree/reference.js.map +1 -1
  68. package/lib/tree/rules.d.ts +7 -0
  69. package/lib/tree/rules.d.ts.map +1 -1
  70. package/lib/tree/rules.js +147 -81
  71. package/lib/tree/rules.js.map +1 -1
  72. package/lib/tree/ruleset.d.ts +1 -0
  73. package/lib/tree/ruleset.d.ts.map +1 -1
  74. package/lib/tree/ruleset.js +27 -5
  75. package/lib/tree/ruleset.js.map +1 -1
  76. package/lib/tree/selector-basic.d.ts +1 -0
  77. package/lib/tree/selector-basic.d.ts.map +1 -1
  78. package/lib/tree/selector-basic.js +5 -1
  79. package/lib/tree/selector-basic.js.map +1 -1
  80. package/lib/tree/selector-list.d.ts.map +1 -1
  81. package/lib/tree/selector-list.js +3 -13
  82. package/lib/tree/selector-list.js.map +1 -1
  83. package/lib/tree/selector-pseudo.d.ts.map +1 -1
  84. package/lib/tree/selector-pseudo.js +13 -0
  85. package/lib/tree/selector-pseudo.js.map +1 -1
  86. package/lib/tree/sequence.d.ts.map +1 -1
  87. package/lib/tree/sequence.js +4 -1
  88. package/lib/tree/sequence.js.map +1 -1
  89. package/lib/tree/util/extend-roots.d.ts.map +1 -1
  90. package/lib/tree/util/extend-roots.js +25 -7
  91. package/lib/tree/util/extend-roots.js.map +1 -1
  92. package/lib/tree/util/extend-walk.d.ts +53 -0
  93. package/lib/tree/util/extend-walk.d.ts.map +1 -0
  94. package/lib/tree/util/extend-walk.js +881 -0
  95. package/lib/tree/util/extend-walk.js.map +1 -0
  96. package/lib/tree/util/extend.d.ts.map +1 -1
  97. package/lib/tree/util/extend.js +160 -11
  98. package/lib/tree/util/extend.js.map +1 -1
  99. package/lib/tree/util/registry-utils.d.ts.map +1 -1
  100. package/lib/tree/util/registry-utils.js +13 -41
  101. package/lib/tree/util/registry-utils.js.map +1 -1
  102. package/lib/tree/util/selector-match-core.d.ts +13 -0
  103. package/lib/tree/util/selector-match-core.d.ts.map +1 -1
  104. package/lib/tree/util/selector-match-core.js +55 -30
  105. package/lib/tree/util/selector-match-core.js.map +1 -1
  106. package/package.json +4 -2
@@ -0,0 +1,881 @@
1
+ /**
2
+ * Walk-and-consume extend algorithm.
3
+ *
4
+ * The find selector decomposes into *positions* — each position is a set of
5
+ * simple selectors (AND). Positions are separated by combinators. The walk
6
+ * progresses through the target tree, consuming find positions:
7
+ *
8
+ * - SimpleSelector find → 1 position, 1 simple
9
+ * - CompoundSelector find → 1 position, N simples
10
+ * - ComplexSelector find → M positions separated by combinators
11
+ *
12
+ * At each target position:
13
+ * - full match → consume ALL of a compound
14
+ * - partial → consume any subset of a compound
15
+ * - :is() / SelectorList → OR (try each alternative)
16
+ *
17
+ * ## :is() as AND branch
18
+ *
19
+ * When :is() appears as a component in a compound selector, it adds an AND
20
+ * branch. The *last* selector in each :is() alternative occupies the same
21
+ * compound position as its sibling components. Everything before the last
22
+ * selector is an ancestral prefix — a separate path.
23
+ *
24
+ * .a:is(.x > .y).b
25
+ * → subject has .a, .b, .y (all at one position, ANDed)
26
+ * → AND is child of .x (ancestral branch from :is())
27
+ *
28
+ * This is equivalent to .x > .a.y.b when there's only one branch. But when
29
+ * combined with outer complex context, the branches can't be flattened:
30
+ *
31
+ * div + .a:is(.x > .y).b
32
+ * → subject has .a, .b, .y
33
+ * → AND follows div (outer complex path)
34
+ * → AND is child of .x (:is() branch path)
35
+ *
36
+ * Walking handles both paths naturally. Flattening cannot represent this.
37
+ *
38
+ * ## Reading back-to-front
39
+ *
40
+ * Selectors read back-to-front: rightmost = subject element, leftward =
41
+ * ancestors/preceding. For :is() alternatives that are complex selectors,
42
+ * the last element is at the current position; the rest is a branch going up.
43
+ *
44
+ * ComplexSelector find support is currently used for diagnostics only
45
+ * (wouldExtendChange); the extend application falls through to legacy
46
+ * due to createProcessedSelector differences.
47
+ */
48
+ import { SelectorList } from '../selector-list.js';
49
+ import { ComplexSelector } from '../selector-complex.js';
50
+ import { CompoundSelector } from '../selector-compound.js';
51
+ import { PseudoSelector } from '../selector-pseudo.js';
52
+ import { Ampersand } from '../ampersand.js';
53
+ import { isNode } from './is-node.js';
54
+ import { F_EXTENDED, F_EXTEND_TARGET } from '../node.js';
55
+ import { createProcessedSelector } from './extend.js';
56
+ const { isArray } = Array;
57
+ function decomposeFind(find) {
58
+ if (isNode(find, 'ComplexSelector')) {
59
+ const positions = [];
60
+ const combinators = [];
61
+ for (const comp of find.value) {
62
+ if (isNode(comp, 'Combinator')) {
63
+ combinators.push(comp.value);
64
+ }
65
+ else if (isNode(comp, 'CompoundSelector')) {
66
+ positions.push([...comp.value]);
67
+ }
68
+ else {
69
+ positions.push([comp]);
70
+ }
71
+ }
72
+ return { positions, combinators, original: find };
73
+ }
74
+ if (isNode(find, 'CompoundSelector')) {
75
+ return {
76
+ positions: [[...find.value]],
77
+ combinators: [],
78
+ original: find
79
+ };
80
+ }
81
+ // SimpleSelector or anything else: single position, single simple
82
+ return { positions: [[find]], combinators: [], original: find };
83
+ }
84
+ /** Whether the find spans multiple positions (has combinators) */
85
+ function isMultiPosition(spec) {
86
+ return spec.positions.length > 1;
87
+ }
88
+ /** Whether the current position has multiple simples (compound) */
89
+ function isMultiSimple(spec) {
90
+ return spec.positions.length === 1 && spec.positions[0].length > 1;
91
+ }
92
+ // ─────────────────────────────────────────────────
93
+ // Equivalence checks
94
+ // ─────────────────────────────────────────────────
95
+ function isWholeNodeMatch(node, spec) {
96
+ const find = spec.original;
97
+ if (isNode(node, 'SelectorList') && !isNode(find, 'SelectorList')) {
98
+ return false;
99
+ }
100
+ if (isNode(node, 'ComplexSelector') && isNode(find, 'ComplexSelector')) {
101
+ return areComplexEquivalent(node, find);
102
+ }
103
+ if (isNode(node, 'ComplexSelector') && !isNode(find, 'ComplexSelector')) {
104
+ return false;
105
+ }
106
+ if (isNode(node, 'CompoundSelector') && isNode(find, 'CompoundSelector')) {
107
+ return areCompoundsEquivalent(node, find);
108
+ }
109
+ if (isNode(node, 'CompoundSelector') && !isNode(find, 'CompoundSelector')) {
110
+ return false;
111
+ }
112
+ return node.valueOf() === find.valueOf();
113
+ }
114
+ function areCompoundsEquivalent(a, b) {
115
+ if (a.value.length !== b.value.length) {
116
+ return false;
117
+ }
118
+ if (a.valueOf() === b.valueOf()) {
119
+ return true;
120
+ }
121
+ const used = new Uint8Array(b.value.length);
122
+ for (const aComp of a.value) {
123
+ const aVal = aComp.valueOf();
124
+ let found = false;
125
+ for (let j = 0; j < b.value.length; j++) {
126
+ if (!used[j] && b.value[j].valueOf() === aVal) {
127
+ used[j] = 1;
128
+ found = true;
129
+ break;
130
+ }
131
+ }
132
+ if (!found) {
133
+ return false;
134
+ }
135
+ }
136
+ return true;
137
+ }
138
+ function areComplexEquivalent(a, b) {
139
+ if (a.value.length !== b.value.length) {
140
+ return false;
141
+ }
142
+ if (a.valueOf() === b.valueOf()) {
143
+ return true;
144
+ }
145
+ for (let i = 0; i < a.value.length; i++) {
146
+ const ac = a.value[i];
147
+ const bc = b.value[i];
148
+ if (isNode(ac, 'Combinator') !== isNode(bc, 'Combinator')) {
149
+ return false;
150
+ }
151
+ if (isNode(ac, 'Combinator')) {
152
+ if (ac.value !== bc.value) {
153
+ return false;
154
+ }
155
+ continue;
156
+ }
157
+ if (isNode(ac, 'CompoundSelector') && isNode(bc, 'CompoundSelector')) {
158
+ if (!areCompoundsEquivalent(ac, bc)) {
159
+ return false;
160
+ }
161
+ }
162
+ else if (ac.valueOf() !== bc.valueOf()) {
163
+ return false;
164
+ }
165
+ }
166
+ return true;
167
+ }
168
+ // ─────────────────────────────────────────────────
169
+ // Position matching
170
+ // ─────────────────────────────────────────────────
171
+ /**
172
+ * Extract what's at the current position from a selector.
173
+ *
174
+ * For a ComplexSelector, the last non-combinator component is at the current
175
+ * position; everything before it is an ancestral prefix. For anything else,
176
+ * the entire selector is at the current position.
177
+ *
178
+ * .a → .a
179
+ * .a.b → .a.b
180
+ * .x > .y → .y (the .x > prefix is an ancestral branch)
181
+ * .x > .y.z → .y.z
182
+ */
183
+ function tailOf(sel) {
184
+ if (isNode(sel, 'ComplexSelector')) {
185
+ const comps = sel.value;
186
+ for (let i = comps.length - 1; i >= 0; i--) {
187
+ if (!isNode(comps[i], 'Combinator')) {
188
+ return comps[i];
189
+ }
190
+ }
191
+ }
192
+ return sel;
193
+ }
194
+ /**
195
+ * Does `find` match `target` at this compound position?
196
+ *
197
+ * Like compoundComponentMatches, but tail-aware: when an :is() alternative
198
+ * is a ComplexSelector, only its tail (last non-combinator) is at the
199
+ * current position.
200
+ *
201
+ * .y vs :is(.x > .y) → true (.y is the tail of .x > .y)
202
+ * .x vs :is(.x > .y) → false (.x is in the ancestral prefix)
203
+ */
204
+ function positionSimpleMatches(find, target) {
205
+ if (find.valueOf() === target.valueOf()) {
206
+ return true;
207
+ }
208
+ // find is :is() → OR: try each alternative's tail
209
+ if (isNode(find, 'PseudoSelector') && find.value.name === ':is' && find.value.arg) {
210
+ const arg = find.value.arg;
211
+ if (isNode(arg, 'SelectorList')) {
212
+ return arg.value.some((alt) => positionSimpleMatches(tailOf(alt), target));
213
+ }
214
+ return positionSimpleMatches(tailOf(arg), target);
215
+ }
216
+ // target is :is() → OR: try each alternative's tail
217
+ if (isNode(target, 'PseudoSelector') && target.value.name === ':is' && target.value.arg) {
218
+ const arg = target.value.arg;
219
+ if (isNode(arg, 'SelectorList')) {
220
+ return arg.value.some((alt) => positionSimpleMatches(find, tailOf(alt)));
221
+ }
222
+ return positionSimpleMatches(find, tailOf(arg));
223
+ }
224
+ return false;
225
+ }
226
+ /**
227
+ * Does `targetComp` match `findComp` at this position?
228
+ * Handles :is() as OR alternatives (tail-aware) and compound equivalence.
229
+ */
230
+ function positionComponentMatches(findComp, targetComp) {
231
+ if (isNode(findComp, 'CompoundSelector') && isNode(targetComp, 'CompoundSelector')) {
232
+ return areCompoundsEquivalent(findComp, targetComp);
233
+ }
234
+ return positionSimpleMatches(findComp, targetComp);
235
+ }
236
+ /**
237
+ * Try to match find positions as a contiguous subsequence in target
238
+ * components. Returns the start index or -1.
239
+ */
240
+ function findSubsequence(targetComps, spec) {
241
+ // Reconstruct find components (positions interleaved with combinators)
242
+ const findComps = [];
243
+ for (let p = 0; p < spec.positions.length; p++) {
244
+ if (p > 0) {
245
+ findComps.push({ type: 'Combinator', value: spec.combinators[p - 1] });
246
+ }
247
+ const simples = spec.positions[p];
248
+ if (simples.length === 1) {
249
+ findComps.push(simples[0]);
250
+ }
251
+ else {
252
+ findComps.push({ type: 'CompoundSelector', value: simples, _isVirtual: true, simples });
253
+ }
254
+ }
255
+ const maxStart = targetComps.length - findComps.length;
256
+ for (let start = 0; start <= maxStart; start++) {
257
+ let matches = true;
258
+ for (let j = 0; j < findComps.length; j++) {
259
+ const tc = targetComps[start + j];
260
+ const fc = findComps[j];
261
+ if (fc.type === 'Combinator') {
262
+ if (!isNode(tc, 'Combinator') || tc.value !== fc.value) {
263
+ matches = false;
264
+ break;
265
+ }
266
+ }
267
+ else {
268
+ if (isNode(tc, 'Combinator')) {
269
+ matches = false;
270
+ break;
271
+ }
272
+ if (fc._isVirtual) {
273
+ // Virtual compound: check if all simples match the target component
274
+ if (isNode(tc, 'CompoundSelector')) {
275
+ if (!areCompoundsEquivalent(CompoundSelector.create(fc.simples), tc)) {
276
+ matches = false;
277
+ break;
278
+ }
279
+ }
280
+ else {
281
+ matches = false;
282
+ break;
283
+ }
284
+ }
285
+ else if (!positionComponentMatches(fc, tc)) {
286
+ matches = false;
287
+ break;
288
+ }
289
+ }
290
+ }
291
+ if (matches) {
292
+ return start;
293
+ }
294
+ }
295
+ return -1;
296
+ }
297
+ /**
298
+ * Try to consume find simples from a compound's components (subsequence match).
299
+ * Returns matched indices or null if not all consumed.
300
+ */
301
+ function consumeSimples(targetComps, findSimples) {
302
+ const matchIndices = [];
303
+ let findIdx = 0;
304
+ for (let i = 0; i < targetComps.length && findIdx < findSimples.length; i++) {
305
+ if (positionSimpleMatches(findSimples[findIdx], targetComps[i])) {
306
+ matchIndices.push(i);
307
+ findIdx++;
308
+ }
309
+ }
310
+ return matchIndices.length === findSimples.length ? matchIndices : null;
311
+ }
312
+ const ROOT_CTX = {
313
+ isRoot: true,
314
+ parentType: null,
315
+ hasContentBefore: false,
316
+ hasContentAfter: false
317
+ };
318
+ // ─────────────────────────────────────────────────
319
+ // Quick eligibility check
320
+ // ─────────────────────────────────────────────────
321
+ export function canUseWalkAndConsume(target, find) {
322
+ if (!isNode(find, 'SimpleSelector')
323
+ && !isNode(find, 'CompoundSelector')
324
+ && !isNode(find, 'ComplexSelector')) {
325
+ return false;
326
+ }
327
+ if (containsAmpersand(target)) {
328
+ return false;
329
+ }
330
+ return true;
331
+ }
332
+ export function extendWithNeedsConflictValidation(extendWith) {
333
+ if (isNode(extendWith, 'CompoundSelector')) {
334
+ return extendWith.value.some((child) => child.isTag || child.isId);
335
+ }
336
+ if (extendWith.isTag || extendWith.isId) {
337
+ return true;
338
+ }
339
+ return false;
340
+ }
341
+ function containsAmpersand(sel) {
342
+ if (sel instanceof Ampersand) {
343
+ return true;
344
+ }
345
+ if (isNode(sel, 'SelectorList') || isNode(sel, 'CompoundSelector') || isNode(sel, 'ComplexSelector')) {
346
+ return sel.value.some((child) => containsAmpersand(child));
347
+ }
348
+ if (isNode(sel, 'PseudoSelector') && sel.value.arg && sel.value.arg.isSelector) {
349
+ return containsAmpersand(sel.value.arg);
350
+ }
351
+ return false;
352
+ }
353
+ // ─────────────────────────────────────────────────
354
+ // Main entry point
355
+ // ─────────────────────────────────────────────────
356
+ export function walkAndExtend(target, find, extendWith, partial) {
357
+ const spec = decomposeFind(find);
358
+ return walkNode(target, spec, extendWith, partial, ROOT_CTX);
359
+ }
360
+ // ─────────────────────────────────────────────────
361
+ // Recursive walk
362
+ // ─────────────────────────────────────────────────
363
+ function walkNode(node, spec, extendWith, partial, ctx) {
364
+ // 1. Whole-node equivalence
365
+ if (isWholeNodeMatch(node, spec)) {
366
+ return applyWholeMatch(node, spec, extendWith, partial, ctx);
367
+ }
368
+ // 2. Recurse into containers
369
+ if (isNode(node, 'SelectorList')) {
370
+ return walkSelectorList(node, spec, extendWith, partial, ctx);
371
+ }
372
+ if (isNode(node, 'ComplexSelector')) {
373
+ return walkComplexSelector(node, spec, extendWith, partial, ctx);
374
+ }
375
+ if (isNode(node, 'CompoundSelector')) {
376
+ return walkCompoundSelector(node, spec, extendWith, partial, ctx);
377
+ }
378
+ if (isNode(node, 'PseudoSelector') && node.value.arg && node.value.arg.isSelector) {
379
+ return walkPseudoSelector(node, spec, extendWith, partial, ctx);
380
+ }
381
+ // 3. No match
382
+ return node;
383
+ }
384
+ // ─────────────────────────────────────────────────
385
+ // Whole-match transformation
386
+ // ─────────────────────────────────────────────────
387
+ function applyWholeMatch(node, spec, extendWith, partial, ctx) {
388
+ const findVal = spec.original.valueOf();
389
+ const extendVal = extendWith.valueOf();
390
+ if (findVal === extendVal) {
391
+ return node;
392
+ }
393
+ if (!partial) {
394
+ if (ctx.parentType === 'CompoundSelector' || ctx.parentType === 'ComplexSelector') {
395
+ return node;
396
+ }
397
+ return makeList(node, extendWith);
398
+ }
399
+ if (ctx.parentType === 'CompoundSelector' || ctx.parentType === 'ComplexSelector') {
400
+ return wrapInIs(node, extendWith);
401
+ }
402
+ return makeList(node, extendWith, true);
403
+ }
404
+ // ─────────────────────────────────────────────────
405
+ // Container walkers
406
+ // ─────────────────────────────────────────────────
407
+ function walkSelectorList(list, spec, extendWith, partial, _ctx) {
408
+ const items = list.value;
409
+ const originals = [];
410
+ const appended = [];
411
+ let anyChanged = false;
412
+ for (let i = 0; i < items.length; i++) {
413
+ const item = items[i];
414
+ const childCtx = {
415
+ isRoot: false,
416
+ parentType: 'SelectorList',
417
+ hasContentBefore: i > 0,
418
+ hasContentAfter: i < items.length - 1
419
+ };
420
+ const extended = walkNode(item, spec, extendWith, partial, childCtx);
421
+ if (extended === item) {
422
+ originals.push(item);
423
+ }
424
+ else if (isNode(extended, 'SelectorList')) {
425
+ const extItems = extended.value;
426
+ const first = extItems[0];
427
+ first.addFlag(F_EXTENDED);
428
+ if (partial) {
429
+ first.addFlag(F_EXTEND_TARGET);
430
+ }
431
+ originals.push(first);
432
+ for (let j = 1; j < extItems.length; j++) {
433
+ const ext = extItems[j];
434
+ ext.addFlag(F_EXTENDED);
435
+ appended.push(ext);
436
+ }
437
+ anyChanged = true;
438
+ }
439
+ else {
440
+ extended.addFlag(F_EXTENDED);
441
+ originals.push(extended);
442
+ anyChanged = true;
443
+ }
444
+ }
445
+ if (!anyChanged) {
446
+ return list;
447
+ }
448
+ const allSelectors = [...originals, ...appended];
449
+ const processed = createProcessedSelector(allSelectors, true);
450
+ const processedArray = isArray(processed) ? processed : [processed];
451
+ const safe = processedArray.map(s => (s === list ? s.clone(true) : s));
452
+ return SelectorList.create(safe).inherit(list);
453
+ }
454
+ function walkComplexSelector(complex, spec, extendWith, partial, ctx) {
455
+ // Multi-position find: try contiguous subsequence match
456
+ if (isMultiPosition(spec)) {
457
+ return consumePositionsFromComplex(complex, spec, extendWith, partial, ctx);
458
+ }
459
+ // Single-position find: walk each component individually
460
+ const components = complex.value;
461
+ let anyChanged = false;
462
+ const newComponents = [...components];
463
+ for (let i = 0; i < components.length; i++) {
464
+ const comp = components[i];
465
+ if (isNode(comp, 'Combinator')) {
466
+ continue;
467
+ }
468
+ const childCtx = {
469
+ isRoot: false,
470
+ parentType: 'ComplexSelector',
471
+ hasContentBefore: i > 0,
472
+ hasContentAfter: i < components.length - 1
473
+ };
474
+ const extended = walkNode(comp, spec, extendWith, partial, childCtx);
475
+ if (extended !== comp) {
476
+ newComponents[i] = extended;
477
+ anyChanged = true;
478
+ }
479
+ }
480
+ if (!anyChanged) {
481
+ return complex;
482
+ }
483
+ return ComplexSelector.create(newComponents).inherit(complex);
484
+ }
485
+ function walkCompoundSelector(compound, spec, extendWith, partial, _ctx) {
486
+ // Multi-position: can't consume from a single compound
487
+ if (isMultiPosition(spec)) {
488
+ return compound;
489
+ }
490
+ // Multi-simple: consume find simples from compound components
491
+ if (isMultiSimple(spec) && partial) {
492
+ return consumeSimplesFromCompound(compound, spec, extendWith);
493
+ }
494
+ // Single-simple: walk individual components (recurses into :is())
495
+ const components = compound.value;
496
+ let anyChanged = false;
497
+ const newComponents = [...components];
498
+ for (let i = 0; i < components.length; i++) {
499
+ const comp = components[i];
500
+ const childCtx = {
501
+ isRoot: false,
502
+ parentType: 'CompoundSelector',
503
+ hasContentBefore: i > 0,
504
+ hasContentAfter: i < components.length - 1
505
+ };
506
+ const extended = walkNode(comp, spec, extendWith, partial, childCtx);
507
+ if (extended !== comp) {
508
+ newComponents[i] = extended;
509
+ anyChanged = true;
510
+ }
511
+ }
512
+ if (!anyChanged) {
513
+ return compound;
514
+ }
515
+ return CompoundSelector.create(newComponents).inherit(compound);
516
+ }
517
+ /**
518
+ * Consume find simples from a compound's components.
519
+ * Matched components → :is(matched, extendWith). Remainder stays.
520
+ */
521
+ function consumeSimplesFromCompound(compound, spec, extendWith) {
522
+ const targetComps = compound.value;
523
+ const findSimples = spec.positions[0];
524
+ const matchIndices = consumeSimples(targetComps, findSimples);
525
+ if (!matchIndices) {
526
+ return compound;
527
+ }
528
+ const matchedSet = new Set(matchIndices);
529
+ const remainders = [];
530
+ for (let i = 0; i < targetComps.length; i++) {
531
+ if (!matchedSet.has(i)) {
532
+ remainders.push(targetComps[i]);
533
+ }
534
+ }
535
+ const isWrapper = wrapInIs(spec.original, extendWith);
536
+ const newComponents = [isWrapper, ...remainders];
537
+ const result = CompoundSelector.create(newComponents).inherit(compound);
538
+ result.addFlag(F_EXTENDED);
539
+ return result;
540
+ }
541
+ /**
542
+ * Consume find positions from a complex selector's components.
543
+ * Contiguous subsequence match with exact combinator matching.
544
+ */
545
+ function consumePositionsFromComplex(complex, spec, extendWith, partial, _ctx) {
546
+ const targetComps = complex.value;
547
+ const start = findSubsequence(targetComps, spec);
548
+ if (start < 0) {
549
+ return complex;
550
+ }
551
+ // Reconstruct find component count (positions + combinators)
552
+ const findLen = spec.positions.length + spec.combinators.length;
553
+ const end = start + findLen;
554
+ const hasBefore = start > 0;
555
+ const hasAfter = end < targetComps.length;
556
+ if (!partial && (hasBefore || hasAfter)) {
557
+ return complex;
558
+ }
559
+ if (!hasBefore && !hasAfter) {
560
+ return makeList(complex, extendWith, true);
561
+ }
562
+ // Wrap the matched segment
563
+ const matchedSegment = targetComps.slice(start, end);
564
+ const matchedComplex = ComplexSelector.create(matchedSegment).inherit(complex);
565
+ const isWrapper = wrapInIs(matchedComplex, extendWith);
566
+ const before = targetComps.slice(0, start);
567
+ const after = targetComps.slice(end);
568
+ const newComps = [...before, isWrapper, ...after];
569
+ const result = ComplexSelector.create(newComps).inherit(complex);
570
+ result.addFlag(F_EXTENDED);
571
+ return result;
572
+ }
573
+ function walkPseudoSelector(pseudo, spec, extendWith, partial, ctx) {
574
+ const arg = pseudo.value.arg;
575
+ // When :is() is inside a compound, only the tail of each complex
576
+ // alternative is at the current position. The ancestral prefix is
577
+ // a separate branch and should not be walked for matching.
578
+ if (ctx.parentType === 'CompoundSelector') {
579
+ return walkPseudoTailAware(pseudo, arg, spec, extendWith, partial, ctx);
580
+ }
581
+ const childCtx = {
582
+ isRoot: false,
583
+ parentType: 'PseudoSelector',
584
+ hasContentBefore: false,
585
+ hasContentAfter: false
586
+ };
587
+ const extendedArg = walkNode(arg, spec, extendWith, partial, childCtx);
588
+ if (extendedArg === arg) {
589
+ return pseudo;
590
+ }
591
+ if (!partial && (ctx.hasContentBefore || ctx.hasContentAfter)) {
592
+ return pseudo;
593
+ }
594
+ const result = PseudoSelector.create({
595
+ name: pseudo.value.name,
596
+ arg: extendedArg
597
+ }).inherit(pseudo);
598
+ result.generated = pseudo.generated;
599
+ return result;
600
+ }
601
+ /**
602
+ * Walk :is() alternatives tail-aware: for complex alternatives, only the
603
+ * last non-combinator component (the tail) is at the current compound
604
+ * position. The ancestral prefix is not walked.
605
+ */
606
+ function walkPseudoTailAware(pseudo, arg, spec, extendWith, partial, ctx) {
607
+ if (isNode(arg, 'SelectorList')) {
608
+ const items = arg.value;
609
+ let anyChanged = false;
610
+ const originals = [];
611
+ const appended = [];
612
+ for (let i = 0; i < items.length; i++) {
613
+ const alt = items[i];
614
+ const extended = walkAlternativeTailAware(alt, spec, extendWith, partial);
615
+ if (extended === alt) {
616
+ originals.push(alt);
617
+ }
618
+ else if (isNode(extended, 'SelectorList')) {
619
+ // Decompose: first item stays in position, rest appended at end
620
+ const extItems = extended.value;
621
+ originals.push(extItems[0]);
622
+ for (let j = 1; j < extItems.length; j++) {
623
+ appended.push(extItems[j]);
624
+ }
625
+ anyChanged = true;
626
+ }
627
+ else {
628
+ originals.push(extended);
629
+ anyChanged = true;
630
+ }
631
+ }
632
+ if (!anyChanged) {
633
+ return pseudo;
634
+ }
635
+ if (!partial && (ctx.hasContentBefore || ctx.hasContentAfter)) {
636
+ return pseudo;
637
+ }
638
+ const newList = SelectorList.create([...originals, ...appended]).inherit(arg);
639
+ const result = PseudoSelector.create({
640
+ name: pseudo.value.name,
641
+ arg: newList
642
+ }).inherit(pseudo);
643
+ result.generated = pseudo.generated;
644
+ return result;
645
+ }
646
+ // Single alternative
647
+ const extended = walkAlternativeTailAware(arg, spec, extendWith, partial);
648
+ if (extended === arg) {
649
+ return pseudo;
650
+ }
651
+ if (!partial && (ctx.hasContentBefore || ctx.hasContentAfter)) {
652
+ return pseudo;
653
+ }
654
+ const result = PseudoSelector.create({
655
+ name: pseudo.value.name,
656
+ arg: extended
657
+ }).inherit(pseudo);
658
+ result.generated = pseudo.generated;
659
+ return result;
660
+ }
661
+ /**
662
+ * Walk a single :is() alternative. If it's a ComplexSelector, only walk
663
+ * the tail (last non-combinator). Otherwise walk normally.
664
+ */
665
+ function walkAlternativeTailAware(alt, spec, extendWith, partial) {
666
+ if (!isNode(alt, 'ComplexSelector')) {
667
+ // Simple or compound alternative: walk normally
668
+ const childCtx = {
669
+ isRoot: false,
670
+ parentType: 'PseudoSelector',
671
+ hasContentBefore: false,
672
+ hasContentAfter: false
673
+ };
674
+ return walkNode(alt, spec, extendWith, partial, childCtx);
675
+ }
676
+ // Complex alternative: only walk the tail
677
+ const comps = alt.value;
678
+ let tailIdx = -1;
679
+ for (let i = comps.length - 1; i >= 0; i--) {
680
+ if (!isNode(comps[i], 'Combinator')) {
681
+ tailIdx = i;
682
+ break;
683
+ }
684
+ }
685
+ if (tailIdx < 0) {
686
+ return alt;
687
+ }
688
+ const tail = comps[tailIdx];
689
+ // The tail is at the compound position — walk it as if it were a compound child
690
+ const tailCtx = {
691
+ isRoot: false,
692
+ parentType: 'CompoundSelector',
693
+ hasContentBefore: tailIdx > 0,
694
+ hasContentAfter: tailIdx < comps.length - 1
695
+ };
696
+ const extendedTail = walkNode(tail, spec, extendWith, partial, tailCtx);
697
+ if (extendedTail === tail) {
698
+ return alt;
699
+ }
700
+ // Reconstruct complex with modified tail, keeping prefix intact
701
+ const newComps = [...comps];
702
+ newComps[tailIdx] = extendedTail;
703
+ return ComplexSelector.create(newComps).inherit(alt);
704
+ }
705
+ // ─────────────────────────────────────────────────
706
+ // Helpers
707
+ // ─────────────────────────────────────────────────
708
+ function makeList(original, extendWith, partial = false) {
709
+ const a = original.clone(true);
710
+ a.addFlag(F_EXTENDED);
711
+ if (partial) {
712
+ a.addFlag(F_EXTEND_TARGET);
713
+ }
714
+ const extendItems = extractIsArgs(extendWith);
715
+ const items = [a];
716
+ for (const item of extendItems) {
717
+ const b = item.clone(true);
718
+ b.addFlag(F_EXTENDED);
719
+ items.push(b);
720
+ }
721
+ const processed = createProcessedSelector(items, true);
722
+ const processedArray = isArray(processed) ? processed : [processed];
723
+ return SelectorList.create(processedArray).inherit(original);
724
+ }
725
+ function extractIsArgs(selector) {
726
+ if (isNode(selector, 'PseudoSelector') && selector.value.name === ':is' && selector.value.arg) {
727
+ const arg = selector.value.arg;
728
+ if (isNode(arg, 'SelectorList')) {
729
+ return arg.value;
730
+ }
731
+ return [arg];
732
+ }
733
+ return [selector];
734
+ }
735
+ function wrapInIs(matched, extendWith) {
736
+ const a = matched.copy(true);
737
+ a.addFlag(F_EXTEND_TARGET);
738
+ const extendItems = extractIsArgs(extendWith);
739
+ const extendCopies = extendItems.map((item) => {
740
+ const c = item.copy(true);
741
+ c.addFlag(F_EXTENDED);
742
+ return c;
743
+ });
744
+ if (isNode(matched, 'PseudoSelector') && matched.value.name === ':is' && matched.value.arg) {
745
+ const existing = isNode(matched.value.arg, 'SelectorList')
746
+ ? matched.value.arg.value
747
+ : [matched.value.arg];
748
+ const existingVals = new Set(existing.map(s => s.valueOf()));
749
+ const newItems = extendCopies.filter(c => !existingVals.has(c.valueOf()));
750
+ if (newItems.length === 0) {
751
+ return matched;
752
+ }
753
+ const merged = [...existing.map(s => s.copy(true)), ...newItems];
754
+ const list = SelectorList.create(merged);
755
+ const result = PseudoSelector.create({ name: ':is', arg: list }).inherit(matched);
756
+ result.generated = true;
757
+ return result;
758
+ }
759
+ const list = SelectorList.create([a, ...extendCopies]);
760
+ const result = PseudoSelector.create({ name: ':is', arg: list }).inherit(matched);
761
+ result.generated = true;
762
+ return result;
763
+ }
764
+ // ─────────────────────────────────────────────────
765
+ // Dry-run: would this extend change the selector?
766
+ // ─────────────────────────────────────────────────
767
+ export function wouldExtendChange(target, find, extendWith, partial) {
768
+ const spec = decomposeFind(find);
769
+ return wouldMatchNode(target, spec, extendWith, partial, ROOT_CTX);
770
+ }
771
+ function wouldMatchNode(node, spec, extendWith, partial, ctx) {
772
+ if (spec.original.valueOf() === extendWith.valueOf()) {
773
+ return false;
774
+ }
775
+ if (isWholeNodeMatch(node, spec)) {
776
+ if (!partial) {
777
+ if (ctx.parentType === 'CompoundSelector' || ctx.parentType === 'ComplexSelector') {
778
+ return false;
779
+ }
780
+ }
781
+ return true;
782
+ }
783
+ if (isNode(node, 'SelectorList')) {
784
+ return node.value.some((item, i) => wouldMatchNode(item, spec, extendWith, partial, {
785
+ isRoot: false,
786
+ parentType: 'SelectorList',
787
+ hasContentBefore: i > 0,
788
+ hasContentAfter: i < node.value.length - 1
789
+ }));
790
+ }
791
+ if (isNode(node, 'ComplexSelector')) {
792
+ if (isMultiPosition(spec)) {
793
+ return wouldSubsequenceMatch(node, spec, partial);
794
+ }
795
+ return node.value.some((comp, i) => {
796
+ if (isNode(comp, 'Combinator')) {
797
+ return false;
798
+ }
799
+ return wouldMatchNode(comp, spec, extendWith, partial, {
800
+ isRoot: false,
801
+ parentType: 'ComplexSelector',
802
+ hasContentBefore: i > 0,
803
+ hasContentAfter: i < node.value.length - 1
804
+ });
805
+ });
806
+ }
807
+ if (isNode(node, 'CompoundSelector')) {
808
+ if (isMultiSimple(spec) && partial) {
809
+ return wouldSimplesMatch(node, spec);
810
+ }
811
+ return node.value.some((comp, i) => wouldMatchNode(comp, spec, extendWith, partial, {
812
+ isRoot: false,
813
+ parentType: 'CompoundSelector',
814
+ hasContentBefore: i > 0,
815
+ hasContentAfter: i < node.value.length - 1
816
+ }));
817
+ }
818
+ if (isNode(node, 'PseudoSelector') && node.value.arg && node.value.arg.isSelector) {
819
+ const pseudo = node;
820
+ if (!partial && (ctx.hasContentBefore || ctx.hasContentAfter)) {
821
+ return false;
822
+ }
823
+ // Tail-aware: when :is() is inside a compound, only the tail of
824
+ // complex alternatives is at the current position.
825
+ if (ctx.parentType === 'CompoundSelector') {
826
+ return wouldMatchPseudoTailAware(pseudo, spec, extendWith, partial);
827
+ }
828
+ return wouldMatchNode(pseudo.value.arg, spec, extendWith, partial, {
829
+ isRoot: false,
830
+ parentType: 'PseudoSelector',
831
+ hasContentBefore: false,
832
+ hasContentAfter: false
833
+ });
834
+ }
835
+ return false;
836
+ }
837
+ function wouldMatchPseudoTailAware(pseudo, spec, extendWith, partial) {
838
+ const arg = pseudo.value.arg;
839
+ const checkAlt = (alt) => {
840
+ if (!isNode(alt, 'ComplexSelector')) {
841
+ return wouldMatchNode(alt, spec, extendWith, partial, {
842
+ isRoot: false,
843
+ parentType: 'PseudoSelector',
844
+ hasContentBefore: false,
845
+ hasContentAfter: false
846
+ });
847
+ }
848
+ // Complex: only check the tail
849
+ const comps = alt.value;
850
+ for (let i = comps.length - 1; i >= 0; i--) {
851
+ if (!isNode(comps[i], 'Combinator')) {
852
+ return wouldMatchNode(comps[i], spec, extendWith, partial, {
853
+ isRoot: false,
854
+ parentType: 'CompoundSelector',
855
+ hasContentBefore: i > 0,
856
+ hasContentAfter: i < comps.length - 1
857
+ });
858
+ }
859
+ }
860
+ return false;
861
+ };
862
+ if (isNode(arg, 'SelectorList')) {
863
+ return arg.value.some((alt) => checkAlt(alt));
864
+ }
865
+ return checkAlt(arg);
866
+ }
867
+ function wouldSimplesMatch(target, spec) {
868
+ return consumeSimples(target.value, spec.positions[0]) !== null;
869
+ }
870
+ function wouldSubsequenceMatch(target, spec, partial) {
871
+ const start = findSubsequence(target.value, spec);
872
+ if (start < 0) {
873
+ return false;
874
+ }
875
+ if (!partial) {
876
+ const findLen = spec.positions.length + spec.combinators.length;
877
+ return start === 0 && findLen === target.value.length;
878
+ }
879
+ return true;
880
+ }
881
+ //# sourceMappingURL=extend-walk.js.map