@ng-native/fabric 0.15.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/css.js CHANGED
@@ -32,13 +32,16 @@ function formOf(token, kind) {
32
32
  }
33
33
  /**
34
34
  * Whether a child is one `:empty` sees. An anchor is a comment on the web, which it does not;
35
- * nor text of no length, which a binding to nothing leaves behind.
35
+ * nor text of no length, which a binding to nothing leaves behind; nor a box a rule generated,
36
+ * which is not in the tree a selector is matched against.
36
37
  */
37
38
  export function fills(child) {
38
- if (child.kind === 'anchor')
39
+ if (child.nodeKind === 'anchor' || (boxesDrawn && isGenerated(child.tag)))
39
40
  return false;
40
- return child.kind !== 'text' || child.text !== '';
41
+ return child.nodeKind !== 'text' || child.nodeText !== '';
41
42
  }
43
+ /** The name a type selector matches a node by. */
44
+ const typeOf = (node) => node.typeName ?? node.tag;
42
45
  /**
43
46
  * Properties CSS inherits that mean something on native.
44
47
  *
@@ -77,6 +80,14 @@ const INHERITED = new Set([
77
80
  * children do already, so neither is handed down.
78
81
  */
79
82
  const INHERITED_POINTER_EVENTS = new Set(['none', 'auto']);
83
+ /**
84
+ * A shape's paint, which SVG hands down: a path under an `<svg>` with a `fill` has that fill.
85
+ * Inherited only in a library's drawing (`StyleTarget.namespace`). An icon's shapes are left to
86
+ * react-native-svg, which hands a group's paint down itself, from what the shape set as its own.
87
+ */
88
+ const SHAPE_PAINT_INHERITED = new Set(['fill', 'stroke', 'strokeWidth']);
89
+ /** Whether a prop of a shape in a drawing is a paint it hands down: see `inheritFrom`. */
90
+ export const handsDownPaint = (key) => key === 'propList' || SHAPE_PAINT_INHERITED.has(key);
80
91
  /**
81
92
  * `border-style: none`, which the compiler writes as a style native does not have. On the web it
82
93
  * makes every border width 0, whichever rule set the width, so `border-hidden border-x` draws no
@@ -210,6 +221,8 @@ export const styleStats = {
210
221
  rulesFiled: 0,
211
222
  /** Nodes asked whether a rule `:has()` was asked for could be for them: see `isFor`. */
212
223
  subjectTests: 0,
224
+ /** Children whose place in a list `:has()` noted, to step from one to the next. */
225
+ placesIndexed: 0,
213
226
  };
214
227
  export function resetStyleStats() {
215
228
  styleStats.compoundTests = 0;
@@ -217,6 +230,7 @@ export function resetStyleStats() {
217
230
  styleStats.nodesResolved = 0;
218
231
  styleStats.rulesFiled = 0;
219
232
  styleStats.subjectTests = 0;
233
+ styleStats.placesIndexed = 0;
220
234
  }
221
235
  /** A prop's value as an attribute test compares it: in lower case, under the i flag. */
222
236
  const attributeText = (actual, test) => test.insensitive ? String(actual).toLowerCase() : String(actual);
@@ -229,7 +243,7 @@ const attributeText = (actual, test) => test.insensitive ? String(actual).toLowe
229
243
  * is the element's class list, as the attribute would spell it.
230
244
  */
231
245
  function matchesAttribute(node, test) {
232
- const actual = test.name === 'class' ? classAttribute(node) : node.props[test.name];
246
+ const actual = test.name === 'class' ? classAttribute(node) : attributeOf(node, test.name);
233
247
  if (actual === undefined || actual === null || actual === false)
234
248
  return false;
235
249
  if (test.operator === undefined)
@@ -239,6 +253,11 @@ function matchesAttribute(node, test) {
239
253
  return false;
240
254
  return matchesValue(attributeText(actual, test), test.operator, test.value);
241
255
  }
256
+ /**
257
+ * A node's attribute as a selector reads it: the one a package keeps for it apart from its props,
258
+ * where it keeps one by that name, and otherwise its prop. See `Engine.keepAttribute`.
259
+ */
260
+ export const attributeOf = (node, name) => node.keptAttributes?.[name] ?? node.props[name];
242
261
  /** The classes of a node in the order they were set, one space between each; none is absent. */
243
262
  const classAttribute = (node) => node.classes?.size ? [...node.classes].join(' ') : undefined;
244
263
  /** The operators an empty value can still match by: `=` and `|=`. */
@@ -270,7 +289,7 @@ function matchesValue(value, operator, expected) {
270
289
  function ownedBy(node, compound, sheet) {
271
290
  if (compound.host !== undefined || compound.hostContext !== undefined)
272
291
  return true;
273
- return node.sheet === sheet;
292
+ return node.templateSheet === sheet;
274
293
  }
275
294
  /*
276
295
  * Exempt from the complexity limit deliberately. This is a flat conjunction of independent
@@ -278,6 +297,20 @@ function ownedBy(node, compound, sheet) {
278
297
  * same thing. It is also the hottest function in the matcher, called tens of thousands of times
279
298
  * per commit, so splitting each guard into its own call would cost more than it reads better.
280
299
  */
300
+ /** The first character of a generated box's tag, `::before` or `::after`. */
301
+ const COLON = 58;
302
+ /**
303
+ * Whether any sheet in the process has drawn a generated box: until one has, no node is one, and
304
+ * the checks for one below cost a boolean read. Set once and left set.
305
+ *
306
+ * ponytail: never turned off again, as the checks are only a cost once it is on; count the
307
+ * engines with boxes if an app that drops its last library sheet needs the matching back.
308
+ */
309
+ let boxesDrawn = false;
310
+ /** Note that a sheet in play draws generated boxes: see `boxesDrawn`. */
311
+ export function noteGeneratedBoxes() {
312
+ boxesDrawn = true;
313
+ }
281
314
  // eslint-disable-next-line complexity
282
315
  function matchesCompound(node, compound, sheet) {
283
316
  styleStats.compoundTests++;
@@ -291,7 +324,14 @@ function matchesCompound(node, compound, sheet) {
291
324
  return false;
292
325
  }
293
326
  }
294
- if (compound.type !== undefined && compound.type !== node.tag)
327
+ if (compound.type !== undefined) {
328
+ // `typeOf`, asked here without the call: this is asked of every compound tested.
329
+ if (compound.type !== (node.typeName ?? node.tag))
330
+ return false;
331
+ // A generated box is no element to a compound that names none, `*` or a class alone, as a
332
+ // pseudo-element is none in a browser: one character told from any element's name.
333
+ }
334
+ else if (boxesDrawn && node.tag.charCodeAt(0) === COLON)
295
335
  return false;
296
336
  if (compound.own !== undefined && !ownedBy(node, compound, sheet))
297
337
  return false;
@@ -307,7 +347,7 @@ function matchesCompound(node, compound, sheet) {
307
347
  return false;
308
348
  }
309
349
  }
310
- if (compound.empty !== undefined && node.children?.some(fills))
350
+ if (compound.empty !== undefined && node.childList?.some(fills))
311
351
  return false;
312
352
  if (compound.nth !== undefined) {
313
353
  for (const test of compound.nth) {
@@ -315,17 +355,31 @@ function matchesCompound(node, compound, sheet) {
315
355
  return false;
316
356
  }
317
357
  }
358
+ // `:not()`: the node is none of its arguments, a compound or a longer selector.
318
359
  if (compound.not) {
319
360
  for (const excluded of compound.not) {
320
361
  if (matchesCompound(node, excluded, sheet))
321
362
  return false;
322
363
  }
364
+ if (compound.notComplex) {
365
+ for (const excluded of compound.notComplex) {
366
+ if (matchesComplex(node, excluded, sheet))
367
+ return false;
368
+ }
369
+ }
323
370
  }
371
+ // `:is()` and `:where()`: the node is one of each list of alternatives.
324
372
  if (compound.is) {
325
373
  for (const alternatives of compound.is) {
326
374
  if (!alternatives.some((option) => matchesCompound(node, option, sheet)))
327
375
  return false;
328
376
  }
377
+ if (compound.isComplex) {
378
+ for (const alternatives of compound.isComplex) {
379
+ if (!alternatives.some((option) => matchesComplex(node, option, sheet)))
380
+ return false;
381
+ }
382
+ }
329
383
  }
330
384
  if (compound.has) {
331
385
  for (const test of compound.has) {
@@ -366,18 +420,207 @@ function matchesCompound(node, compound, sheet) {
366
420
  }
367
421
  return true;
368
422
  }
423
+ /**
424
+ * `:has()` answers, by the node asked and the test, while the engine commits: a rule like
425
+ * `main:has(.missing) span` asks the same of `main` for each span in it, and each answer is a
426
+ * walk of everything in `main`. Nothing an answer hangs on changes in a commit, and a change
427
+ * between two drops them all: see `forgetHasAnswers`.
428
+ */
429
+ let hasAnswers = null;
430
+ /**
431
+ * What `:has()` found of a list while the engine commits, kept and dropped with `hasAnswers`:
432
+ * without it, asking each of a thousand items `:has(~ .x)` looks through the list once each.
433
+ */
434
+ let relations = null;
435
+ /** Keep `:has()` answers from here, or stop keeping them. */
436
+ export function keepHasAnswers(keep) {
437
+ hasAnswers = keep ? new Map() : null;
438
+ relations = keep ? new Relations() : null;
439
+ }
440
+ /** Drop the `:has()` answers kept, for something beneath a node changed. */
441
+ export function forgetHasAnswers() {
442
+ hasAnswers?.clear();
443
+ if (relations)
444
+ relations = new Relations();
445
+ }
369
446
  /** Whether some element beneath `node`, or some child of it for `:has(> x)`, matches the test. */
370
447
  function hasBeneath(node, test, sheet) {
371
- for (const child of node.children ?? []) {
372
- if (child.kind !== undefined && child.kind !== 'element')
448
+ const known = hasAnswers?.get(node)?.get(test);
449
+ if (known !== undefined)
450
+ return known;
451
+ const found = test.relative?.some((relative) => hasRelative(node, relative, sheet)) === true ||
452
+ hasCompound(node, test, sheet);
453
+ if (hasAnswers) {
454
+ let answers = hasAnswers.get(node);
455
+ if (!answers)
456
+ hasAnswers.set(node, (answers = new Map()));
457
+ answers.set(test, found);
458
+ }
459
+ return found;
460
+ }
461
+ function hasCompound(node, test, sheet) {
462
+ if (!test.any.length)
463
+ return false;
464
+ for (const child of node.childList ?? []) {
465
+ if (child.nodeKind !== undefined && child.nodeKind !== 'element')
373
466
  continue;
374
467
  if (test.any.some((option) => matchesCompound(child, option, sheet)))
375
468
  return true;
376
- if (!test.child && hasBeneath(child, test, sheet))
469
+ if (!test.child && hasCompound(child, test, sheet))
377
470
  return true;
378
471
  }
379
472
  return false;
380
473
  }
474
+ /** Whether a node is the last element of a selector: matched leftwards, as a rule is. */
475
+ function matchesComplex(node, selector, sheet) {
476
+ const last = selector.compounds.length - 1;
477
+ return (matchesCompound(node, selector.compounds[last], sheet) &&
478
+ matchPrefix(node, selector, last - 1, sheet));
479
+ }
480
+ const SIBLING_COMBINATORS = new Set(['next-sibling', 'later-sibling']);
481
+ /**
482
+ * Each element's place in its parent's list, found once for a `:has()` as it is asked: a list
483
+ * of thousands asked `:has(.a + .b)` is not searched again for each element in it to find the
484
+ * one before. Nothing changes while a selector is matched, so nothing is out of date.
485
+ */
486
+ class Places {
487
+ lists = new Map();
488
+ /** Where `node` is in its parent's list, counting what is not an element. */
489
+ place(node) {
490
+ const siblings = node.parent.childList ?? [];
491
+ let places = this.lists.get(node.parent);
492
+ if (!places) {
493
+ places = new Map(siblings.map((child, at) => [child, at]));
494
+ styleStats.placesIndexed += siblings.length;
495
+ this.lists.set(node.parent, places);
496
+ }
497
+ return places.get(node);
498
+ }
499
+ /** The element `step` places along from `node`: before it for -1, after it for 1. */
500
+ element(node, step) {
501
+ const siblings = node.parent?.childList;
502
+ if (!siblings)
503
+ return null;
504
+ for (let at = this.place(node) + step; at >= 0 && at < siblings.length; at += step) {
505
+ if (isElement(siblings[at]))
506
+ return siblings[at];
507
+ }
508
+ return null;
509
+ }
510
+ }
511
+ /** A `:has()` argument less its first step: `.a .b` of `~ .a .b`, asked of the `.a`. */
512
+ const TAILS = new WeakMap();
513
+ function tailOf(relative) {
514
+ let tail = TAILS.get(relative);
515
+ if (!tail) {
516
+ tail = { compounds: relative.compounds.slice(1), combinators: relative.combinators.slice(1) };
517
+ TAILS.set(relative, tail);
518
+ }
519
+ return tail;
520
+ }
521
+ /**
522
+ * What a `:has()` argument that starts with `+` or `~` finds of a list: which elements after
523
+ * the node asked it can start from, the rest of it asked of each. That is the same whichever
524
+ * element before them asks, so a list is looked through once for all of them.
525
+ */
526
+ class Relations {
527
+ places = new Places();
528
+ /** By argument and list, the place of the last element `~` can start from, or -1. */
529
+ lastStarts = new Map();
530
+ /** Whether `relative`, which starts with `+` or `~`, is true of `anchor`. */
531
+ after(anchor, relative, sheet) {
532
+ if (relative.combinators[0] === 'next-sibling') {
533
+ const next = this.places.element(anchor, 1);
534
+ return next !== null && this.startsAt(next, relative, sheet);
535
+ }
536
+ const list = anchor.parent;
537
+ return list !== null && this.places.place(anchor) < this.lastStart(list, relative, sheet);
538
+ }
539
+ lastStart(list, relative, sheet) {
540
+ let byList = this.lastStarts.get(relative);
541
+ if (!byList)
542
+ this.lastStarts.set(relative, (byList = new Map()));
543
+ let last = byList.get(list);
544
+ if (last === undefined) {
545
+ const siblings = list.childList ?? [];
546
+ last = siblings.length - 1;
547
+ while (last >= 0 &&
548
+ !(isElement(siblings[last]) && this.startsAt(siblings[last], relative, sheet))) {
549
+ last--;
550
+ }
551
+ byList.set(list, last);
552
+ }
553
+ return last;
554
+ }
555
+ /** Whether `node` is where the first compound of `relative` is, the rest of it asked of it. */
556
+ startsAt(node, relative, sheet) {
557
+ if (!matchesCompound(node, relative.compounds[0], sheet))
558
+ return false;
559
+ return relative.compounds.length === 1 || hasRelative(node, tailOf(relative), sheet);
560
+ }
561
+ }
562
+ /**
563
+ * Whether some element is the last of a `:has()` argument asked of `anchor`: one beneath it, or
564
+ * after it where the argument starts with `+` or `~`, and beneath one of those where it goes on
565
+ * to a child or a descendant.
566
+ */
567
+ function hasRelative(anchor, relative, sheet) {
568
+ const known = relations ?? new Relations();
569
+ if (SIBLING_COMBINATORS.has(relative.combinators[0]))
570
+ return known.after(anchor, relative, sheet);
571
+ return someBeneath(anchor, (node) => relativeFits(node, relative, anchor, sheet, known.places));
572
+ }
573
+ /** Whether some element beneath `node` passes `test`. */
574
+ function someBeneath(node, test) {
575
+ for (const child of node.childList ?? []) {
576
+ if (!isElement(child))
577
+ continue;
578
+ if (test(child) || someBeneath(child, test))
579
+ return true;
580
+ }
581
+ return false;
582
+ }
583
+ /** Whether `node` is the last element of a `:has()` argument asked of `anchor`. */
584
+ function relativeFits(node, relative, anchor, sheet, places) {
585
+ const last = relative.compounds.length - 1;
586
+ return (matchesCompound(node, relative.compounds[last], sheet) &&
587
+ relativeLeft(node, last, { relative, anchor, sheet, places }));
588
+ }
589
+ /**
590
+ * Match what is left of `relative.compounds[at]`, which `matched` is: the compound before it,
591
+ * or the anchor where it is the first, by the combinator between. A descendant or a later
592
+ * sibling tries each candidate, as `matchAny` does.
593
+ */
594
+ function relativeLeft(matched, at, relating) {
595
+ const { relative, anchor, sheet, places } = relating;
596
+ const compound = at > 0 ? relative.compounds[at - 1] : null;
597
+ const fits = (node) => compound === null
598
+ ? node === anchor
599
+ : matchesCompound(node, compound, sheet) && relativeLeft(node, at - 1, relating);
600
+ switch (relative.combinators[at]) {
601
+ case 'child':
602
+ return matched.parent !== null && fits(matched.parent);
603
+ case 'next-sibling': {
604
+ const before = places.element(matched, -1);
605
+ return before !== null && fits(before);
606
+ }
607
+ case 'later-sibling':
608
+ for (let before = places.element(matched, -1); before; before = places.element(before, -1)) {
609
+ if (fits(before))
610
+ return true;
611
+ }
612
+ return false;
613
+ default:
614
+ for (let up = matched.parent; up; up = up.parent) {
615
+ if (fits(up))
616
+ return true;
617
+ // Nothing above the anchor is beneath it.
618
+ if (up === anchor)
619
+ return false;
620
+ }
621
+ return false;
622
+ }
623
+ }
381
624
  /**
382
625
  * Right-to-left matching, as every CSS engine does it: the rightmost compound is the cheapest
383
626
  * filter and rejects almost everything immediately.
@@ -392,7 +635,7 @@ export function matches(node, rule, sheet = null) {
392
635
  // of the same name.
393
636
  if (sheet !== null &&
394
637
  sheet === node.hostSheet &&
395
- sheet !== node.sheet &&
638
+ sheet !== node.templateSheet &&
396
639
  subject.host === undefined) {
397
640
  return false;
398
641
  }
@@ -515,7 +758,7 @@ function bitsAbove(node) {
515
758
  for (const className of up.classes)
516
759
  add(bitOf('.', className));
517
760
  if (up.tag !== undefined)
518
- add(bitOf('<', up.tag));
761
+ add(bitOf('<', typeOf(up)));
519
762
  const id = up.props?.['nativeID'];
520
763
  if (typeof id === 'string')
521
764
  add(bitOf('#', id));
@@ -535,7 +778,15 @@ function takesParents(rule) {
535
778
  return takes;
536
779
  }
537
780
  /** Whether a node is one of the children CSS counts. Text and anchors are children, not elements. */
538
- const isElement = (node) => node.kind === undefined || node.kind === 'element';
781
+ const isElement = (node) =>
782
+ // Asked of each sibling a rule counts: told from a generated box here, without a call.
783
+ (node.nodeKind === undefined || node.nodeKind === 'element') &&
784
+ !(boxesDrawn && node.tag.charCodeAt(0) === COLON);
785
+ /**
786
+ * Whether an element's name is a generated box's, `::before` or `::after`: a child the engine
787
+ * keeps for a rule that draws one, which no selector counts among the element's children.
788
+ */
789
+ export const isGenerated = (name) => name.startsWith('::');
539
790
  /** A node's first `count` elements, or its last, skipping everything that is not one. */
540
791
  export function elementsAtEnd(siblings, count, fromEnd) {
541
792
  const found = [];
@@ -594,12 +845,15 @@ function countSiblings(compound, reach, withinHas) {
594
845
  reach.after = Infinity;
595
846
  if (counts.some((test) => withinHas || test.fromEnd))
596
847
  reach.before = true;
848
+ // `:is(.a + .b)`: what the node matches hangs on the element before it, as a rule's does.
849
+ for (const selector of nestedSelectors(compound)) {
850
+ if (!withinHas)
851
+ reach.after = Math.max(reach.after, stepsAlong(selector.combinators));
852
+ }
597
853
  for (const inner of nestedCompounds(compound))
598
854
  countSiblings(inner, reach, withinHas);
599
- for (const test of compound.has ?? []) {
600
- for (const inner of test.any)
601
- countSiblings(inner, reach, true);
602
- }
855
+ for (const inner of hasCompounds(compound))
856
+ countSiblings(inner, reach, true);
603
857
  }
604
858
  const PLACE_ELSEWHERE = new WeakMap();
605
859
  /**
@@ -624,7 +878,7 @@ export function placeReadElsewhere(sheet) {
624
878
  // above the element styled, and its last compound is the element the rest hangs under.
625
879
  let start = 0;
626
880
  rule.compounds.forEach((compound, at) => {
627
- placeReadAbove(compound, elsewhere);
881
+ placeReadAbove(compound, elsewhere, at < rule.compounds.length - 1);
628
882
  const joined = rule.combinators[at];
629
883
  if (joined === 'next-sibling' || joined === 'later-sibling')
630
884
  return;
@@ -651,51 +905,123 @@ const NAMED_BESIDE = new WeakMap();
651
905
  export function namedBeside(sheet) {
652
906
  let named = NAMED_BESIDE.get(sheet);
653
907
  if (named === undefined) {
654
- named = new Set();
655
- for (const rule of sheet.rules) {
656
- rule.combinators.forEach((joined, at) => {
657
- if (joined === 'next-sibling' || joined === 'later-sibling') {
658
- propsNamed(rule.compounds[at], named);
659
- }
660
- });
661
- }
662
- NAMED_BESIDE.set(sheet, named);
908
+ const found = new Set();
909
+ for (const rule of sheet.rules)
910
+ besideIn(rule, rule, found);
911
+ NAMED_BESIDE.set(sheet, (named = found));
663
912
  }
664
913
  return named;
665
914
  }
666
- /** Add each prop a compound, or one nested in it at any depth, asks about: an attribute or an id. */
667
- function propsNamed(compound, into) {
668
- if (compound.id !== undefined)
915
+ /**
916
+ * Add to `into` what each compound joined to a later one by `+` or `~` asks, in a selector and
917
+ * in each one nested in it. A `:has()` argument starts at the element it is asked of, so one
918
+ * with a sibling in it has every prop the rule names added.
919
+ */
920
+ function besideIn(selector, rule, into) {
921
+ if (selector === null || typeof selector !== 'object')
922
+ return;
923
+ if (Array.isArray(selector))
924
+ return selector.forEach((inner) => besideIn(inner, rule, into));
925
+ const { compounds, combinators } = selector;
926
+ if (Array.isArray(compounds) && Array.isArray(combinators)) {
927
+ const relative = combinators.length === compounds.length;
928
+ combinators.forEach((joined, at) => {
929
+ if (joined !== 'next-sibling' && joined !== 'later-sibling')
930
+ return;
931
+ if (relative)
932
+ propsNamed(rule, into);
933
+ else
934
+ propsNamed(compounds[at], into);
935
+ });
936
+ }
937
+ for (const [key, inner] of Object.entries(selector)) {
938
+ if (!DECLARED.has(key))
939
+ besideIn(inner, rule, into);
940
+ }
941
+ }
942
+ /** Add every prop a part of a selector asks about to `into`: each attribute, and an id. */
943
+ function propsNamed(part, into) {
944
+ if (part === null || typeof part !== 'object')
945
+ return;
946
+ if (Array.isArray(part))
947
+ return part.forEach((inner) => propsNamed(inner, into));
948
+ const { attributes, id } = part;
949
+ if (id !== undefined)
669
950
  into.add('nativeID');
670
- for (const test of compound.attributes ?? [])
951
+ for (const test of attributes ?? [])
671
952
  into.add(test.name);
672
- const within = (compound.has ?? []).flatMap((test) => test.any);
673
- for (const inner of [...nestedCompounds(compound), ...within])
674
- propsNamed(inner, into);
953
+ for (const [key, inner] of Object.entries(part))
954
+ if (!DECLARED.has(key))
955
+ propsNamed(inner, into);
675
956
  }
676
957
  /** Whether a compound, or one nested in it at any depth, asks about a place in a child list. */
677
958
  function readsPlace(compound) {
678
959
  if (compound.nth?.length)
679
960
  return true;
680
- const within = (compound.has ?? []).flatMap((test) => test.any);
681
- return [...nestedCompounds(compound), ...within].some(readsPlace);
961
+ return [...nestedCompounds(compound), ...hasCompounds(compound)].some(readsPlace);
682
962
  }
683
963
  /**
684
964
  * Collect the compounds nested in one that are matched against an element above the one it is
685
- * matched against, and ask about that element's place: `:is(.group:first-child *)`.
965
+ * matched against, and ask about that element's place: `:is(.group:first-child *)`. `above`
966
+ * says the compound is itself matched against an element above the one styled.
686
967
  */
687
- function placeReadAbove(compound, elsewhere) {
688
- const above = [
968
+ function placeReadAbove(compound, elsewhere, above) {
969
+ const over = [
689
970
  ...(compound.ancestors ?? []),
690
971
  ...(compound.parents ?? []),
691
972
  ...(compound.hostContext ?? []),
692
973
  ];
693
- for (const inner of above)
974
+ for (const inner of over)
694
975
  if (readsPlace(inner))
695
976
  elsewhere.push({ compound: inner });
696
- for (const inner of nestedCompounds(compound))
697
- placeReadAbove(inner, elsewhere);
977
+ // `:is(.a + .b) .c` or `:not(:first-child .b)`: an element whose place changed can change what
978
+ // one under it matches, which is told from no class here. Only such sheets pay. Of the element
979
+ // styled, `.b:is(.a ~ *)` reads what is before the element alone, as a rule's own `~` does.
980
+ const reads = above ? beside : readFromUnder;
981
+ if (nestedSelectors(compound).some(reads))
982
+ elsewhere.push({ compound: { classes: [] } });
983
+ for (const inner of over)
984
+ placeReadAbove(inner, elsewhere, true);
985
+ placeReadNested(compound, elsewhere, above);
698
986
  }
987
+ /**
988
+ * `placeReadAbove` of the compounds nested in one: those of its `:is()` and `:not()` are matched
989
+ * against the element it is, and those of a longer selector in one before its last against others.
990
+ */
991
+ function placeReadNested(compound, elsewhere, above) {
992
+ for (const inner of [...(compound.not ?? []), ...(compound.is ?? []).flat()]) {
993
+ placeReadAbove(inner, elsewhere, above);
994
+ }
995
+ for (const selector of nestedSelectors(compound)) {
996
+ const last = selector.compounds.length - 1;
997
+ selector.compounds.forEach((inner, at) => placeReadAbove(inner, elsewhere, above || at < last));
998
+ }
999
+ }
1000
+ /** Whether what a selector matches hangs on where an element is among those beside it. */
1001
+ const beside = (one) => one.combinators.some((each) => SIBLING_COMBINATORS.has(each)) ||
1002
+ (one.compounds.length > 1 && one.compounds.some(readsPlace));
1003
+ /**
1004
+ * Whether a selector reads the place of an element above the one it ends at: a run of compounds
1005
+ * joined by `+` and `~` before a descendant or child combinator, `.a + .b .c`, or a compound
1006
+ * there that asks its place, `:first-child > .c`.
1007
+ */
1008
+ function readFromUnder(one) {
1009
+ let start = 0;
1010
+ for (let at = 0; at < one.combinators.length; at++) {
1011
+ if (SIBLING_COMBINATORS.has(one.combinators[at]))
1012
+ continue;
1013
+ const run = one.compounds.slice(start, at + 1);
1014
+ if (run.length > 1 || run.some(readsPlace))
1015
+ return true;
1016
+ start = at + 1;
1017
+ }
1018
+ return false;
1019
+ }
1020
+ /** The selectors of more than a compound nested in one, in its `:is()` and its `:not()`. */
1021
+ const nestedSelectors = (compound) => [
1022
+ ...(compound.notComplex ?? []),
1023
+ ...(compound.isComplex ?? []).flat(),
1024
+ ];
699
1025
  /** The compounds nested in one, a `:has()`'s apart. */
700
1026
  const nestedCompounds = (compound) => [
701
1027
  ...(compound.not ?? []),
@@ -703,10 +1029,16 @@ const nestedCompounds = (compound) => [
703
1029
  ...(compound.ancestors ?? []),
704
1030
  ...(compound.parents ?? []),
705
1031
  ...(compound.hostContext ?? []),
1032
+ ...nestedSelectors(compound).flatMap((selector) => selector.compounds),
706
1033
  ];
1034
+ /** The compounds of a compound's `:has()` arguments. */
1035
+ const hasCompounds = (compound) => (compound.has ?? []).flatMap((test) => [
1036
+ ...test.any,
1037
+ ...(test.relative ?? []).flatMap((relative) => relative.compounds),
1038
+ ]);
707
1039
  /** The element before this one, skipping everything that is not one. */
708
1040
  function previousElement(node) {
709
- const siblings = node.parent?.children;
1041
+ const siblings = node.parent?.childList;
710
1042
  if (!siblings)
711
1043
  return null;
712
1044
  for (let i = siblings.indexOf(node) - 1; i >= 0; i--) {
@@ -723,7 +1055,7 @@ function previousElement(node) {
723
1055
  * counting, and the rest wait for a list that is actually slow.
724
1056
  */
725
1057
  function matchesNth(node, test) {
726
- const siblings = node.parent?.children;
1058
+ const siblings = node.parent?.childList;
727
1059
  if (!siblings)
728
1060
  return false;
729
1061
  if (test.a === 0 && test.b === 1 && !test.ofType) {
@@ -795,6 +1127,148 @@ const TWIN = {
795
1127
  ltr: twins(SIDES_LTR),
796
1128
  rtl: twins(SIDES_LTR.map(([logical, physical]) => [logical, mirrored(physical)])),
797
1129
  };
1130
+ /** The props of each edge of a box across and down, in each form Yoga or React Native reads. */
1131
+ const across = (box) => ['Left', 'Right', 'Start', 'End', 'InlineStart', 'InlineEnd'].map((side) => `${box}${side}`);
1132
+ const down = (box) => ['Top', 'Bottom', 'BlockStart', 'BlockEnd'].map((side) => `${box}${side}`);
1133
+ /** A box's shorthand for every edge, across and down, keyed by the name an inline style uses. */
1134
+ function boxShorthands(all, edges, axes) {
1135
+ return [
1136
+ [all, [...edges.across, ...edges.down, ...axes.across, ...axes.down]],
1137
+ // An axis's own name among them is skipped as one the element sets itself.
1138
+ ...axes.across.map((axis) => [axis, [...edges.across, ...axes.across]]),
1139
+ ...axes.down.map((axis) => [axis, [...edges.down, ...axes.down]]),
1140
+ ];
1141
+ }
1142
+ /** Every corner, physical and logical, by the name of its radius. */
1143
+ const CORNERS = [
1144
+ ...['TopLeft', 'TopRight', 'BottomLeft', 'BottomRight', 'TopStart', 'TopEnd'],
1145
+ ...['BottomStart', 'BottomEnd', 'StartStart', 'StartEnd', 'EndStart', 'EndEnd'],
1146
+ ].map((corner) => `border${corner}Radius`);
1147
+ /**
1148
+ * The props each shorthand an inline style can set covers, in every form a native view reads
1149
+ * them: physical, logical and the aliases Yoga takes. A stylesheet's shorthand is compiled to its
1150
+ * longhands, so only an element's own style commits one as it is written, and there CSS has it
1151
+ * win over a rule's longhand, where Yoga and React Native read the more specific edge or corner.
1152
+ */
1153
+ const COVERS = new Map([
1154
+ ...['margin', 'padding'].flatMap((box) => boxShorthands(box, { across: across(box), down: down(box) }, { across: [`${box}Horizontal`, `${box}Inline`], down: [`${box}Vertical`, `${box}Block`] })),
1155
+ ...boxShorthands('inset', {
1156
+ across: ['left', 'right', 'start', 'end', 'insetInlineStart', 'insetInlineEnd'],
1157
+ down: ['top', 'bottom', 'insetBlockStart', 'insetBlockEnd'],
1158
+ }, { across: ['insetInline'], down: ['insetBlock'] }),
1159
+ ['gap', ['rowGap', 'columnGap']],
1160
+ ['borderWidth', [...across('border'), ...down('border')].map((side) => `${side}Width`)],
1161
+ ['borderColor', [...across('border'), ...down('border'), 'borderBlock'].map((s) => `${s}Color`)],
1162
+ ['borderBlockColor', down('border').map((side) => `${side}Color`)],
1163
+ ['borderRadius', CORNERS],
1164
+ ['flex', ['flexGrow', 'flexShrink', 'flexBasis']],
1165
+ ]);
1166
+ /**
1167
+ * The longhands Yoga reads `flex` as when none is set (`Node::resolveFlexGrow`,
1168
+ * `resolveFlexShrink`, `processFlexBasis`), so an inline `flex` means the same over a class's.
1169
+ * Null for a value that is no finite number, which covers nothing.
1170
+ */
1171
+ function flexParts(n) {
1172
+ if (typeof n !== 'number' || !Number.isFinite(n))
1173
+ return null;
1174
+ return { flexGrow: n > 0 ? n : 0, flexShrink: n < 0 ? -n : 0, flexBasis: n > 0 ? 0 : 'auto' };
1175
+ }
1176
+ /**
1177
+ * Whether a value is several CSS values, `4px 8px` or `4px / 8px`, split on whitespace outside
1178
+ * brackets. Native takes none of them for a side, so a side keeps its own.
1179
+ *
1180
+ * ponytail: a side keeps its own rather than taking its part of the value. The renderer's
1181
+ * `longhands` splits `padding`, `margin`, `inset` and `gap` into their parts; the others can join it.
1182
+ */
1183
+ function severalValues(value) {
1184
+ if (typeof value !== 'string')
1185
+ return false;
1186
+ let flat = value;
1187
+ for (let inner = flat; (inner = flat.replace(/\([^()]*\)/g, '()')) !== flat;)
1188
+ flat = inner;
1189
+ return flat.split(CSS_SPACE).filter(Boolean).length > 1;
1190
+ }
1191
+ /**
1192
+ * What a shorthand's value gives each side it covers: undefined for the value itself, the parts
1193
+ * of a `flex`, or null for nothing, as for several values.
1194
+ */
1195
+ function partsOf(shorthand, value) {
1196
+ if (severalValues(value))
1197
+ return null;
1198
+ return shorthand === 'flex' && value != null ? flexParts(value) : undefined;
1199
+ }
1200
+ /**
1201
+ * How late React Native applies a shorthand to an edge it shares with another
1202
+ * (`YogaLayoutableShadowNode`): the box's own name, then its axis, then the logical axis.
1203
+ */
1204
+ function rankOf(shorthand) {
1205
+ if (/(Inline|Block)$/.test(shorthand))
1206
+ return 2;
1207
+ return /(Horizontal|Vertical|BlockColor)$/.test(shorthand) ? 1 : 0;
1208
+ }
1209
+ /** The shorthands covering each prop, the one React Native reads first ahead of the rest. */
1210
+ const COVERED_BY = (() => {
1211
+ const by = new Map();
1212
+ for (const [shorthand, keys] of COVERS) {
1213
+ for (const key of keys)
1214
+ by.set(key, [...(by.get(key) ?? []), shorthand]);
1215
+ }
1216
+ for (const shorthands of by.values())
1217
+ shorthands.sort((a, b) => rankOf(b) - rankOf(a));
1218
+ return by;
1219
+ })();
1220
+ /** Whether an element sets a shorthand itself, inline or bound from a token. */
1221
+ const setsItself = (shorthand, inline, bound) => inline[shorthand] != null || bound?.has(shorthand) === true;
1222
+ /** Whether an element sets any shorthand that covers a prop, so that there is anything to do. */
1223
+ function setsAnyShorthand(inline, bound) {
1224
+ for (const key in inline)
1225
+ if (inline[key] != null && COVERS.has(key))
1226
+ return true;
1227
+ if (bound)
1228
+ for (const key of bound.keys())
1229
+ if (COVERS.has(key))
1230
+ return true;
1231
+ return false;
1232
+ }
1233
+ const UNCOVERED = Symbol('uncovered');
1234
+ /** What `key` takes from the first of its shorthands the element sets and that gives it one. */
1235
+ function coveredValue(key, shorthands, style, inline, bound) {
1236
+ for (const shorthand of shorthands) {
1237
+ if (!setsItself(shorthand, inline, bound))
1238
+ continue;
1239
+ const parts = partsOf(shorthand, style[shorthand]);
1240
+ if (parts !== null)
1241
+ return parts ? parts[key] : style[shorthand];
1242
+ }
1243
+ return UNCOVERED;
1244
+ }
1245
+ /**
1246
+ * Gives each prop in `style` that a shorthand the element sets itself covers the shorthand's
1247
+ * value: one in its inline style, `inline`, or bound from a token, `bound`. The value rather than
1248
+ * nothing, so a transition of the side eases to it, as in a browser. Not a prop the element sets
1249
+ * as well, which Yoga reads over its shorthand as before, nor one a rule marked `!important`,
1250
+ * which beats the element's own declaration. Of two shorthands over one prop, the one React Native
1251
+ * reads: `paddingInline` over `paddingHorizontal`, whichever was written first.
1252
+ *
1253
+ * Per commit for every element with a style of its own, so it walks the props in `style`, a few,
1254
+ * rather than every side and corner a shorthand covers.
1255
+ */
1256
+ export function underOwnShorthands(style, inline, bound, important) {
1257
+ if (!setsAnyShorthand(inline, bound))
1258
+ return;
1259
+ for (const key in style) {
1260
+ const shorthands = COVERED_BY.get(key);
1261
+ if (!shorthands || key in inline || bound?.has(key) || important?.has(key))
1262
+ continue;
1263
+ const next = coveredValue(key, shorthands, style, inline, bound);
1264
+ if (next === UNCOVERED)
1265
+ continue;
1266
+ if (next == null)
1267
+ delete style[key];
1268
+ else
1269
+ style[key] = next;
1270
+ }
1271
+ }
798
1272
  const sidedOf = new WeakMap();
799
1273
  function sided(rule) {
800
1274
  let found = sidedOf.get(rule);
@@ -951,7 +1425,7 @@ function countPosition(node, siblings, fromEnd, ofType) {
951
1425
  let position = 0;
952
1426
  let seen = 0;
953
1427
  for (const sibling of siblings) {
954
- if (!isElement(sibling) || (ofType && sibling.tag !== node.tag))
1428
+ if (!isElement(sibling) || (ofType && typeOf(sibling) !== typeOf(node)))
955
1429
  continue;
956
1430
  seen++;
957
1431
  if (sibling === node)
@@ -1028,8 +1502,11 @@ export function ruleKey(rule) {
1028
1502
  * `:is()` of compounds that each have a key: it is filed under each of theirs, since a node it
1029
1503
  * matches has one of them.
1030
1504
  *
1031
- * A rule that says nothing of the node's own name can still say whose child it is, `.row > *`.
1032
- * It is filed under the parent's class, and offered to the children of an element that has it.
1505
+ * Two kinds of rule say nothing of the node's own name and still say whose child it is: one for
1506
+ * a generated box, `.card::before`, which is `.card > ::before` here, and one for any child,
1507
+ * `.row > *`. Each is filed under the parent's class, and offered to the children of an element
1508
+ * that has it. Filed by the box's own name, every `::before` rule there is would be offered to
1509
+ * every such box.
1033
1510
  */
1034
1511
  export function ruleKeys(rule) {
1035
1512
  const key = rule.compounds[rule.compounds.length - 1];
@@ -1039,9 +1516,10 @@ export function ruleKeys(rule) {
1039
1516
  return [`class:${key.classes[0]}`];
1040
1517
  if (key.id !== undefined)
1041
1518
  return [`id:${key.id}`];
1042
- if (key.type !== undefined)
1043
- return [`type:${key.type}`];
1044
1519
  const held = parentClass(rule, key);
1520
+ if (key.type !== undefined && !(held !== undefined && isGenerated(key.type))) {
1521
+ return [`type:${key.type}`];
1522
+ }
1045
1523
  if (held !== undefined)
1046
1524
  return [`in:${held}`];
1047
1525
  return alternativeKeys(key) ?? attributeKey(key) ?? ancestorKeys(rule, key) ?? EVERY_NODE;
@@ -1148,7 +1626,7 @@ function reach(node, index, found) {
1148
1626
  };
1149
1627
  if (index.universal.length)
1150
1628
  found.push(...index.universal);
1151
- take(`type:${node.tag}`);
1629
+ take(`type:${node.typeName ?? node.tag}`);
1152
1630
  const id = node.props['nativeID'];
1153
1631
  if (typeof id === 'string')
1154
1632
  take(`id:${id}`);
@@ -1175,12 +1653,20 @@ function reachUnder(node, take) {
1175
1653
  }
1176
1654
  /** The buckets of the attributes a node has: a prop of its own that is set. */
1177
1655
  function reachByAttribute(node, take) {
1656
+ const kept = node.keptAttributes;
1178
1657
  for (const name in node.props) {
1179
1658
  const value = node.props[name];
1180
1659
  // As `matchesAttribute` has it: a prop that is false is an attribute that is not there.
1181
1660
  if (value !== undefined && value !== null && value !== false)
1182
1661
  take(`attr:${name}`);
1662
+ else if (kept?.[name] !== undefined)
1663
+ take(`attr:${name}`);
1183
1664
  }
1665
+ // One kept by no prop's name: a prop's is taken above.
1666
+ if (kept)
1667
+ for (const name in kept)
1668
+ if (!(name in node.props))
1669
+ take(`attr:${name}`);
1184
1670
  }
1185
1671
  /**
1186
1672
  * The rules worth trying against a node, in cascade order: `order` says which of two comes
@@ -1263,21 +1749,33 @@ function subjectsOf(sheet) {
1263
1749
  const HAS_ABOVE = new WeakMap();
1264
1750
  /** Whether a compound, or one it asks of the same node, uses `:has()`. */
1265
1751
  const asksBeneath = (compound) => compound.has !== undefined ||
1266
- [...(compound.not ?? []), ...(compound.is ?? []).flat()].some(asksBeneath);
1752
+ [
1753
+ ...(compound.not ?? []),
1754
+ ...(compound.is ?? []).flat(),
1755
+ ...nestedSelectors(compound).map((selector) => selector.compounds.at(-1)),
1756
+ ].some(asksBeneath);
1267
1757
  /**
1268
- * The boxes above the styled node that a compound asks `:has()` of: the compound's own node,
1269
- * where that is not the styled one, and each box it is asked to be inside,
1270
- * `.body:is(.card:has(.x) *) .title`.
1758
+ * The boxes other than the styled node that a compound asks `:has()` of: the compound's own
1759
+ * node, where that is not the styled one, each box it is asked to be inside,
1760
+ * `.body:is(.card:has(.x) *) .title`, and each element an `:is()` or `:not()` of more than a
1761
+ * compound asks about, which can be before it as well as above it.
1271
1762
  */
1272
- function* boxesAsked(compound, styled) {
1763
+ function* boxesAsked(compound, styled, beside) {
1273
1764
  if (!styled && asksBeneath(compound))
1274
- yield compound;
1765
+ yield { box: compound, beside };
1275
1766
  for (const key of ['ancestors', 'parents', 'hostContext']) {
1276
1767
  for (const outer of compound[key] ?? [])
1277
- yield* boxesAsked(outer, false);
1768
+ yield* boxesAsked(outer, false, beside);
1769
+ }
1770
+ for (const selector of nestedSelectors(compound)) {
1771
+ const last = selector.compounds.length - 1;
1772
+ // The last is the compound's own node, which `asksBeneath` has asked of already.
1773
+ for (const [at, inner] of selector.compounds.entries()) {
1774
+ yield* boxesAsked(inner, at === last, at === last ? beside : true);
1775
+ }
1278
1776
  }
1279
1777
  }
1280
- /** Each box a sheet's rules ask `:has()` of above the node they style. */
1778
+ /** Each box a sheet's rules ask `:has()` of other than the node they style. */
1281
1779
  export function hasAbove(sheet) {
1282
1780
  let known = HAS_ABOVE.get(sheet);
1283
1781
  if (known === undefined) {
@@ -1285,10 +1783,16 @@ export function hasAbove(sheet) {
1285
1783
  for (const rule of sheet.rules) {
1286
1784
  const last = rule.compounds.length - 1;
1287
1785
  for (const [at, compound] of rule.compounds.entries()) {
1288
- for (const box of boxesAsked(compound, at === last)) {
1786
+ const before = rule.combinators.slice(at).some((one) => SIBLING_COMBINATORS.has(one));
1787
+ for (const { box, beside } of boxesAsked(compound, at === last, before)) {
1289
1788
  const subjects = noSubjects();
1290
1789
  noteSubject(subjects, rule);
1291
- found.push({ classes: box.classes, subjects });
1790
+ found.push({
1791
+ classes: classesOf(box),
1792
+ ...(box.type !== undefined ? { type: box.type } : {}),
1793
+ subjects,
1794
+ ...(beside ? { beside: true } : {}),
1795
+ });
1292
1796
  }
1293
1797
  }
1294
1798
  }
@@ -1296,6 +1800,58 @@ export function hasAbove(sheet) {
1296
1800
  }
1297
1801
  return known;
1298
1802
  }
1803
+ /** Whether `node` can be the box a `:has()` rule asks: it has the classes and is the element. */
1804
+ export function couldAsk(reader, node) {
1805
+ if (reader.type !== undefined && reader.type !== typeOf(node))
1806
+ return false;
1807
+ return reader.classes.every((name) => node.classes?.has(name));
1808
+ }
1809
+ /**
1810
+ * The classes a node needs to match a compound: its own, and those every argument of one of its
1811
+ * `:is()` or `:where()` lists names, `:where(.group)` as Tailwind's `group-has-*` writes it.
1812
+ */
1813
+ function classesOf(compound) {
1814
+ if (!compound.is && !compound.isComplex)
1815
+ return compound.classes;
1816
+ const all = new Set(compound.classes);
1817
+ const lists = [
1818
+ ...(compound.is ?? []),
1819
+ ...(compound.isComplex ?? []).map((list) => list.map((selector) => selector.compounds.at(-1))),
1820
+ ];
1821
+ for (const list of lists) {
1822
+ const [first, ...rest] = list.map(classesOf);
1823
+ for (const name of first ?? [])
1824
+ if (rest.every((other) => other.includes(name)))
1825
+ all.add(name);
1826
+ }
1827
+ return [...all];
1828
+ }
1829
+ /** Each compound in one, and each nested in it at any depth, `:has()` arguments among them. */
1830
+ function* everyCompound(compound) {
1831
+ yield compound;
1832
+ for (const inner of [...nestedCompounds(compound), ...hasCompounds(compound)]) {
1833
+ yield* everyCompound(inner);
1834
+ }
1835
+ }
1836
+ const HAS_BESIDE = new WeakMap();
1837
+ /**
1838
+ * Whether a sheet asks `:has()` about what comes after a node, `:has(+ .x)` or `:has(~ .x .y)`:
1839
+ * then a change can change what the elements before it match, and those before what it is in.
1840
+ */
1841
+ export function hasBeside(sheet) {
1842
+ let known = HAS_BESIDE.get(sheet);
1843
+ if (known === undefined) {
1844
+ const after = (test) => test.relative?.some((one) => SIBLING_COMBINATORS.has(one.combinators[0])) === true;
1845
+ known = sheet.rules.some((rule) => rule.compounds.some((outer) => {
1846
+ for (const compound of everyCompound(outer))
1847
+ if (compound.has?.some(after))
1848
+ return true;
1849
+ return false;
1850
+ }));
1851
+ HAS_BESIDE.set(sheet, known);
1852
+ }
1853
+ return known;
1854
+ }
1299
1855
  /** Note in `into` what a rule is for. */
1300
1856
  function noteSubject(into, rule) {
1301
1857
  const subject = rule.compounds.at(-1);
@@ -1420,7 +1976,7 @@ export function joinSubjects(each) {
1420
1976
  * nothing but `>` and a space between there and the element, and those of what `:is(.box *)`
1421
1977
  * and `:is(.box > *)` put over it;
1422
1978
  * - `beneath`, inside `:has()`, wherever in the rule that is: what it asks of is under the box
1423
- * that answers it;
1979
+ * that answers it, or after it;
1424
1980
  * - `parentHas`, of what `:has(> )` asks of the box's children themselves, and nowhere else in
1425
1981
  * `:has()`: the element is right under that box.
1426
1982
  *
@@ -1481,16 +2037,21 @@ function placeClasses(compound, at, inside, below) {
1481
2037
  collectClasses(rest, below.elsewhere, []);
1482
2038
  }
1483
2039
  /**
1484
- * The classes `:has()` asks of what is under the box that answers it, into `beneath`, wherever
1485
- * in the rule it is written, or into `parentHas` for what `:has(> )` asks of the box's children.
1486
- * One it can ask of what is above the box goes `elsewhere`: `:is()` in `:has()` is not relative,
1487
- * so the `.night` of `:has(:is(.night .cell))` can be any element over the cell.
2040
+ * The classes `:has()` asks of what is under the box that answers it or after it, into
2041
+ * `beneath`, wherever in the rule it is written, or into `parentHas` for what `:has(> )` asks of
2042
+ * the box's children. One it can ask of what is above the box goes `elsewhere`: `:is()` in
2043
+ * `:has()` is not relative, so the `.night` of `:has(:is(.night .cell))` can be any element over
2044
+ * the cell.
1488
2045
  */
1489
2046
  function placeHas(tests, below) {
1490
2047
  for (const test of tests ?? []) {
1491
2048
  const into = test.child ? below.parentHas : below.beneath;
1492
2049
  for (const compound of test.any)
1493
2050
  placeBeneath(compound, into, below);
2051
+ for (const one of test.relative ?? []) {
2052
+ for (const compound of one.compounds)
2053
+ placeBeneath(compound, below.beneath, below);
2054
+ }
1494
2055
  }
1495
2056
  }
1496
2057
  /** `placeHas` for one compound of a `:has()` argument, whose own classes go `into`. */
@@ -1552,15 +2113,29 @@ function collectClasses(value, into, reads, named) {
1552
2113
  * One class of the list where the test is for a whole class or for part of one; any class
1553
2114
  * where it is for how the list starts or ends, or for a part with a space in it.
1554
2115
  */
1555
- function answers(test, name) {
2116
+ function answers(test, name, attribute) {
1556
2117
  const value = test.value ?? '';
1557
2118
  const folded = test.insensitive ? name.toLowerCase() : name;
1558
2119
  if (test.operator === 'includes')
1559
2120
  return folded === value;
1560
2121
  if (test.operator === 'substring' && !/\s/.test(value))
1561
2122
  return folded.includes(value);
2123
+ // `[class^="icon-"]` reads the attribute as a whole: told it, before and after, a change that
2124
+ // leaves its answer as it was answers nothing new.
2125
+ if (attribute)
2126
+ return classHolds(test, attribute.was) !== classHolds(test, attribute.now);
1562
2127
  return true;
1563
2128
  }
2129
+ /** Whether a test of the `class` attribute holds for it written as `value`. */
2130
+ function classHolds(test, value) {
2131
+ if (value === undefined)
2132
+ return false;
2133
+ if (test.operator === undefined)
2134
+ return true;
2135
+ if (test.value === '' && !EMPTY_MATCHES.has(test.operator))
2136
+ return false;
2137
+ return matchesValue(attributeText(value, test), test.operator, test.value);
2138
+ }
1564
2139
  /**
1565
2140
  * A class of the nearest box a rule's subject has to be in: the compound before it, where that
1566
2141
  * is joined as what holds it and names a class. Nothing where it is a sibling, or has no class.
@@ -1573,7 +2148,7 @@ function boxAround(rule) {
1573
2148
  }
1574
2149
  /** Whether a rule among these could be for a node: never no where one is. */
1575
2150
  function writtenFor(of, node) {
1576
- if (of.any || of.types.has(node.tag))
2151
+ if (of.any || of.types.has(typeOf(node)))
1577
2152
  return true;
1578
2153
  for (const name of node.classes ?? [])
1579
2154
  if (of.classes.has(name))
@@ -1647,10 +2222,11 @@ function unstyledStyle(inherited, inline) {
1647
2222
  * node set itself. Copied on first write only, so a node that sets none hands the same object on
1648
2223
  * and a whole subtree can share it.
1649
2224
  */
1650
- function inheritFrom(parentInherited, own) {
1651
- let inherited = parentInherited;
2225
+ function inheritFrom(parentInherited, own, node) {
2226
+ const drawing = node?.namespace !== undefined;
2227
+ let inherited = drawing ? ownPaint(node, parentInherited, own) : parentInherited;
1652
2228
  for (const key of Object.keys(own)) {
1653
- if (!INHERITED.has(key))
2229
+ if (!(INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))))
1654
2230
  continue;
1655
2231
  // `pointer-events: inherit`, which only an inline style on an element no rule matches still
1656
2232
  // holds here: what the parent hands down stands.
@@ -1667,6 +2243,34 @@ function inheritFrom(parentInherited, own) {
1667
2243
  }
1668
2244
  return inherited;
1669
2245
  }
2246
+ /** What a node no rule matches hands down: its inline style's, or a drawing's own paint. */
2247
+ function handedOn(node, parentInherited, inline, drawing) {
2248
+ return inline || drawing ? inheritFrom(parentInherited, inline ?? {}, node) : parentInherited;
2249
+ }
2250
+ /**
2251
+ * What a shape of a drawing hands down of the paint it set itself, as an attribute its
2252
+ * `propList` names, over a paint it inherits: a `<g stroke="none">` under a stroked `<svg>`
2253
+ * strokes nothing inside it, as in a browser. A style of its own stands over either.
2254
+ */
2255
+ function ownPaint(node, parentInherited, own) {
2256
+ const named = node.props['propList'];
2257
+ if (!Array.isArray(named))
2258
+ return parentInherited;
2259
+ let inherited = parentInherited;
2260
+ for (const key of named) {
2261
+ if (!SHAPE_PAINT_INHERITED.has(key) || key in own)
2262
+ continue;
2263
+ const value = node.props[key];
2264
+ // A paint named and not set is `none`, as react-native-svg reads a list naming it.
2265
+ const paint = value ?? (key === 'strokeWidth' ? undefined : 'none');
2266
+ if (paint === undefined || inherited[key] === paint)
2267
+ continue;
2268
+ if (inherited === parentInherited)
2269
+ inherited = { ...parentInherited };
2270
+ inherited[key] = paint;
2271
+ }
2272
+ return inherited;
2273
+ }
1670
2274
  /**
1671
2275
  * A text decoration's colour, which travels with its line rather than inheriting on its own. A
1672
2276
  * decoration is drawn under the text inside the element that declares it, in that element's
@@ -1694,14 +2298,18 @@ function decorate(style, inherited, own) {
1694
2298
  function drawsLine(line) {
1695
2299
  return line !== undefined && line !== 'none';
1696
2300
  }
1697
- /** Whether an inline style sets a property children inherit. Allocates nothing: it runs per set. */
1698
- export function setsInherited(style) {
2301
+ /**
2302
+ * Whether an inline style sets a property children inherit, by a node of a library's drawing or by
2303
+ * any other. Allocates nothing, and calls nothing a key: it runs per set.
2304
+ */
2305
+ export function setsInherited(style, drawing = false) {
1699
2306
  if (!style || typeof style !== 'object')
1700
2307
  return false;
1701
2308
  if (Array.isArray(style))
1702
- return style.some(setsInherited);
2309
+ return style.some((entry) => setsInherited(entry, drawing));
1703
2310
  for (const key in style) {
1704
- if (INHERITED.has(key) && style[key] != null)
2311
+ if ((INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))) &&
2312
+ style[key] != null)
1705
2313
  return true;
1706
2314
  }
1707
2315
  return false;
@@ -1711,11 +2319,13 @@ export function setsInherited(style) {
1711
2319
  * strongest normal declaration on its element, so what it sets is inherited, and read by
1712
2320
  * `color: inherit` and `currentColor`, as a rule's would be.
1713
2321
  */
1714
- export function inlineInherited(style) {
2322
+ export function inlineInherited(style, drawing = false) {
1715
2323
  let out = null;
1716
2324
  // Flattened first, as native applies it, so a later entry's null clears an earlier value.
1717
2325
  for (const [key, value] of Object.entries(flattenInline(style, {}))) {
1718
- if (INHERITED.has(key) && value !== undefined && value !== null)
2326
+ if ((INHERITED.has(key) || (drawing && SHAPE_PAINT_INHERITED.has(key))) &&
2327
+ value !== undefined &&
2328
+ value !== null)
1719
2329
  (out ??= {})[key] = value;
1720
2330
  }
1721
2331
  return out;
@@ -1742,7 +2352,7 @@ function flattenInline(style, into) {
1742
2352
  *
1743
2353
  * Correctness first. Walking up meant matching every ancestor against the *starting node's*
1744
2354
  * sheet, but an ancestor almost always belongs to a different component, so a component's rules
1745
- * reached nodes it did not own while a parent's real values never arrived. Reading `sheet` off
2355
+ * reached nodes it did not own while a parent's real values never arrived. Reading `templateSheet` off
1746
2356
  * each node fixes both directions at once.
1747
2357
  *
1748
2358
  * Cost second. Walking up re-derived, for every node, everything its parent had already worked
@@ -1862,9 +2472,9 @@ export class StyleResolver {
1862
2472
  * them could match. Emptied by anything else that moves the generation on.
1863
2473
  */
1864
2474
  additions = [];
1865
- /** Whether a sheet could match a node: one of its rules is written for it. */
1866
- couldMatch(sheet, node) {
1867
- return writtenFor(subjectsOf(sheet), node);
2475
+ /** What the rules of several sheets are for, as one: see `isFor`. */
2476
+ subjectsOfAll(sheets) {
2477
+ return sheets.length === 1 ? subjectsOf(sheets[0]) : joinSubjects(sheets.map(subjectsOf));
1868
2478
  }
1869
2479
  /** How far each class named by the sheets met so far reaches: see `classReach`. */
1870
2480
  reach = new Map();
@@ -1877,6 +2487,8 @@ export class StyleResolver {
1877
2487
  */
1878
2488
  deepSheets = new Set();
1879
2489
  deepIndexes = new WeakMap();
2490
+ /** Each component sheet's rules for a box of its host: see `hostBoxIndex`. */
2491
+ hostBoxIndexes = new WeakMap();
1880
2492
  /**
1881
2493
  * Read the classes a sheet names, once, as soon as any node is given it. One `scoped` to a
1882
2494
  * component or a host asks of its elements for good, global or not.
@@ -1962,10 +2574,10 @@ export class StyleResolver {
1962
2574
  * elements under it to match again, by what rules are for; or null, where no rule names any
1963
2575
  * of them and nothing is styled again.
1964
2576
  */
1965
- reachOf(classes) {
2577
+ reachOf(classes, attribute) {
1966
2578
  let found = null;
1967
2579
  for (const name of classes) {
1968
- for (const how of this.reachesOf(name)) {
2580
+ for (const how of this.reachesOf(name, attribute)) {
1969
2581
  if (how === true)
1970
2582
  return true;
1971
2583
  addSubjects((found ??= noSubjects()), how);
@@ -1974,15 +2586,19 @@ export class StyleResolver {
1974
2586
  return found;
1975
2587
  }
1976
2588
  /** How far a class reaches by its name, and by each test of the class attribute it answers. */
1977
- *reachesOf(name) {
2589
+ *reachesOf(name, attribute) {
1978
2590
  const how = this.reach.get(name);
1979
2591
  if (how)
1980
2592
  yield how;
1981
2593
  for (const [key, test] of this.classTests) {
1982
- if (answers(test, name))
2594
+ if (answers(test, name, attribute))
1983
2595
  yield this.reach.get(key) ?? true;
1984
2596
  }
1985
2597
  }
2598
+ /** Whether a sheet tests the `class` attribute as a whole: see `ClassChange`. */
2599
+ get testsClassAttribute() {
2600
+ return this.classTests.size > 0;
2601
+ }
1986
2602
  /** Whether one of the rules `subjects` stands for could be for a node. */
1987
2603
  isFor(subjects, node) {
1988
2604
  styleStats.subjectTests++;
@@ -2129,9 +2745,9 @@ export class StyleResolver {
2129
2745
  return cache;
2130
2746
  }
2131
2747
  /**
2132
- * Set by the engine once a sheet uses `:has()`, styles a pressed element or asks about a place
2133
- * in a child list. Until then no cache keeps the rules it matched, so an app that never asks
2134
- * pays nothing for it.
2748
+ * Set by the engine while a sheet in play uses `:has()` or styles a pressed element, and once
2749
+ * one asks about a place in a child list. Otherwise no cache keeps the rules it matched, so an
2750
+ * app that never asks pays nothing for it.
2135
2751
  */
2136
2752
  tracksHas = false;
2137
2753
  /**
@@ -2228,8 +2844,10 @@ export class StyleResolver {
2228
2844
  * is whatever it inherits.
2229
2845
  */
2230
2846
  hasNoRules(node) {
2231
- return (node.sheet === null &&
2847
+ return (node.templateSheet === null &&
2232
2848
  node.hostSheet === null &&
2849
+ // A generated box matches the `:host::before` of the component its element hosts.
2850
+ !(boxesDrawn && node.tag.charCodeAt(0) === COLON && node.parent?.hostSheet) &&
2233
2851
  this.deepSheets.size === 0 &&
2234
2852
  this.globalSheet === null &&
2235
2853
  this.addedSheets.length === 0 &&
@@ -2250,7 +2868,8 @@ export class StyleResolver {
2250
2868
  const tokens = parent ? parent.tokens : this.tokensOnRoot;
2251
2869
  const parentContext = parent ? parent.context : ROOT_CONTEXT;
2252
2870
  // What inline style sets is inherited too, with no rule to read it against.
2253
- const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
2871
+ const drawing = node.namespace !== undefined;
2872
+ const inline = node.inlineInherits ? inlineInherited(node.props['style'], drawing) : null;
2254
2873
  const nothing = !inline &&
2255
2874
  parentInherited === EMPTY &&
2256
2875
  parentContext === ROOT_CONTEXT &&
@@ -2266,7 +2885,7 @@ export class StyleResolver {
2266
2885
  scope: scope ?? context,
2267
2886
  parentScope,
2268
2887
  style: unstyledStyle(parentInherited, inline),
2269
- inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
2888
+ inherited: handedOn(node, parentInherited, inline, drawing),
2270
2889
  tokens,
2271
2890
  };
2272
2891
  node.styleCache = passthrough;
@@ -2309,8 +2928,13 @@ export class StyleResolver {
2309
2928
  const indexes = [];
2310
2929
  if (node.hostSheet)
2311
2930
  indexes.push(this.ownIndex(node.hostSheet, HOSTS));
2312
- if (node.sheet)
2313
- indexes.push(this.ownIndex(node.sheet, CREATED));
2931
+ // A generated box is tried against the rules of the component its element hosts too, where
2932
+ // `:host::before` is written: it is no host itself, so a bare `:host` rule still misses it.
2933
+ const host = boxesDrawn && node.tag.charCodeAt(0) === COLON ? node.parent?.hostSheet : undefined;
2934
+ if (host)
2935
+ indexes.push(this.hostBoxIndex(host));
2936
+ if (node.templateSheet)
2937
+ indexes.push(this.ownIndex(node.templateSheet, CREATED));
2314
2938
  if (this.deepSheets.size)
2315
2939
  this.reachedFromAbove(node, indexes);
2316
2940
  indexes.push(this.sharedIndex());
@@ -2323,12 +2947,26 @@ export class StyleResolver {
2323
2947
  reachedFromAbove(node, indexes) {
2324
2948
  for (let up = node.parent; up; up = up.parent) {
2325
2949
  const sheet = up.hostSheet;
2326
- if (!sheet || sheet === node.sheet || sheet === node.hostSheet)
2950
+ if (!sheet || sheet === node.templateSheet || sheet === node.hostSheet)
2327
2951
  continue;
2328
2952
  if (this.deepSheets.has(sheet))
2329
2953
  indexes.push(this.deepIndex(sheet));
2330
2954
  }
2331
2955
  }
2956
+ /**
2957
+ * A sheet's rules for a box of its host alone, `:host::before` and `:host(.x)::after`, filed as
2958
+ * a component's own are. Its other rules for a box are for the elements of its template, which
2959
+ * is how emulated encapsulation scopes them on the web: `.icon::before` in it never reaches the
2960
+ * box of a host its parent puts the class `icon` on.
2961
+ */
2962
+ hostBoxIndex(sheet) {
2963
+ let index = this.hostBoxIndexes.get(sheet);
2964
+ if (!index) {
2965
+ const boxes = this.entries(sheet, COMPONENT_SPECIFICITY_BUMP).filter(({ rule }) => rule.compounds.at(-2)?.host !== undefined);
2966
+ this.hostBoxIndexes.set(sheet, (index = indexRules(boxes, undefined, HOSTS * SHEET_ROOM)));
2967
+ }
2968
+ return index;
2969
+ }
2332
2970
  /** A sheet's `::ng-deep` rules alone, filed as a component's own are. */
2333
2971
  deepIndex(sheet) {
2334
2972
  let index = this.deepIndexes.get(sheet);
@@ -2413,9 +3051,15 @@ export class StyleResolver {
2413
3051
  */
2414
3052
  styled(node, matched, parentTokens, parentInherited) {
2415
3053
  // Shared between nodes that match alike, unless the node sets something of its own.
2416
- const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
2417
- // Nor one that takes a value from its parent's own style, which two parents differ in.
2418
- if (node.customProperties || inline || node.boundStyle || matched.some(takesParents)) {
3054
+ const drawing = node.namespace !== undefined;
3055
+ const inline = node.inlineInherits ? inlineInherited(node.props['style'], drawing) : null;
3056
+ // Nor one that takes a value from its parent's own style, which two parents differ in, nor
3057
+ // a node of a drawing, which hands down paint another node with its rules does not.
3058
+ if (node.customProperties ||
3059
+ inline ||
3060
+ node.boundStyle ||
3061
+ drawing ||
3062
+ matched.some(takesParents)) {
2419
3063
  return this.style(node, matched, parentTokens, parentInherited, inline);
2420
3064
  }
2421
3065
  if (this.sharedGeneration !== this.generation) {
@@ -2469,7 +3113,7 @@ export class StyleResolver {
2469
3113
  delete own['pointerEvents'];
2470
3114
  const style = { ...parentInherited, ...own };
2471
3115
  settleLineHeight(style);
2472
- const inherited = decorate(style, inheritFrom(parentInherited, own), own);
3116
+ const inherited = decorate(style, inheritFrom(parentInherited, own, node), own);
2473
3117
  const rtl = (style['direction'] ?? this.conditions.direction) === 'rtl';
2474
3118
  const important = importantNames(result, () => TWIN[rtl ? 'rtl' : 'ltr']);
2475
3119
  return important ? { style, inherited, tokens, important } : { style, inherited, tokens };
@@ -3306,31 +3950,46 @@ function referenced(declaration, tokens) {
3306
3950
  return declaration.adjust ? adjusted(base, declaration.adjust) : base;
3307
3951
  }
3308
3952
  /**
3309
- * What native displays for a keyword: flex, none or contents. A block, inline, inline-block,
3310
- * flow-root or inline-flex box is flex, as the compiler reads one written out, and so is `flow`,
3311
- * which is a block. Anything else, a grid or a table, is no display native has, so the property
3312
- * is unset.
3953
+ * What a display keyword is laid out as: flex, none, contents, or one of the block containers,
3954
+ * which the engine lays out as a flex column and commits as flex (`flow.ts`). An inline box is
3955
+ * flex, as the compiler reads one written out, an inline-flex box sits in a line, and `flow` is
3956
+ * a block.
3957
+ * Anything else, a grid or a table, is no display native has, so the property is unset.
3313
3958
  */
3314
3959
  const DISPLAYS = new Map([
3315
- ...['flex', 'block', 'inline', 'inline-block', 'flow-root', 'inline-flex', 'flow'].map((word) => [word, 'flex']),
3316
- ['none', 'none'],
3317
- ['contents', 'contents'],
3960
+ ...['flex', 'inline'].map((word) => [word, 'flex']),
3961
+ ...['block', 'flow-root', 'inline-block', 'inline-flex', 'none', 'contents'].map((word) => [word, word]),
3962
+ ['grid', 'grid'],
3963
+ ['inline-grid', 'inline-grid'],
3964
+ ['flow', 'block'],
3318
3965
  ]);
3319
3966
  /**
3320
- * The two-keyword form, an outer display and an inner one in either order: `inline flex` is
3321
- * `inline-flex` and `block flow` is `block`, as Chrome computes them, so each is flex here.
3967
+ * The two-keyword form, an outer display and an inner one in either order, as Chrome computes
3968
+ * it: `inline flex` is `inline-flex`, `block flow` is `block`, `inline flow-root` is
3969
+ * `inline-block`.
3322
3970
  */
3323
3971
  const OUTER = new Set(['block', 'inline']);
3324
- const INNER = new Set(['flow', 'flow-root', 'flex']);
3972
+ const INNER = new Set(['flow', 'flow-root', 'flex', 'grid']);
3973
+ const PAIRS = {
3974
+ 'block flow': 'block',
3975
+ 'block flow-root': 'flow-root',
3976
+ 'inline flow-root': 'inline-block',
3977
+ 'block grid': 'grid',
3978
+ 'inline grid': 'inline-grid',
3979
+ 'inline flex': 'inline-flex',
3980
+ };
3325
3981
  function displayOf(value) {
3326
3982
  if (typeof value !== 'string')
3327
3983
  return undefined;
3328
3984
  const words = value.toLowerCase().split(CSS_SPACE);
3329
3985
  if (words.length === 1)
3330
3986
  return DISPLAYS.get(words[0]);
3331
- const [first, second] = words;
3332
- const pair = (OUTER.has(first) && INNER.has(second)) || (INNER.has(first) && OUTER.has(second));
3333
- return words.length === 2 && pair ? 'flex' : undefined;
3987
+ if (words.length !== 2)
3988
+ return undefined;
3989
+ const [outer, inner] = (OUTER.has(words[0]) ? words : [words[1], words[0]]);
3990
+ if (!OUTER.has(outer) || !INNER.has(inner))
3991
+ return undefined;
3992
+ return PAIRS[`${outer} ${inner}`] ?? 'flex';
3334
3993
  }
3335
3994
  /**
3336
3995
  * A set token's value in the form wanted. A token of `currentColor` is the colour in scope where
@@ -3867,7 +4526,9 @@ function wholePercentage(marker, tokens) {
3867
4526
  return undefined;
3868
4527
  const leaf = marker.expression;
3869
4528
  const value = formOf(tokens[leaf.reference], 'length');
3870
- return typeof value === 'string' && value.endsWith('%') ? value : undefined;
4529
+ // And one holding a percentage with a length added, which the engine finishes: `calc(100% + -2px)`.
4530
+ const whole = typeof value === 'string' && (value.endsWith('%') || value.startsWith('calc('));
4531
+ return whole ? value : undefined;
3871
4532
  }
3872
4533
  function calculated(expression, kind, tokens) {
3873
4534
  if (typeof expression === 'number')
@@ -4050,6 +4711,25 @@ function insetOrColour(token) {
4050
4711
  function colourMarker(value) {
4051
4712
  return value?.__colour;
4052
4713
  }
4714
+ /**
4715
+ * A token added to a percentage, `calc(100% - var(--handle))`: a length in points or a share of
4716
+ * the same box adds up to one value. A length of the box's own font does not, here, so the box is
4717
+ * given none rather than one that is not what the rule says.
4718
+ */
4719
+ function withPercent(value, adjust) {
4720
+ const percent = adjust.percent;
4721
+ if (typeof value === 'string' && value.endsWith('%')) {
4722
+ const share = percent + Number.parseFloat(value) * (adjust.scale ?? 1);
4723
+ const offset = adjust.offset ?? 0;
4724
+ return offset === 0 ? `${share}%` : `calc(${share}% + ${offset}px)`;
4725
+ }
4726
+ // A length of its own font, deferred, is no number here.
4727
+ if (typeof value !== 'number')
4728
+ return undefined;
4729
+ const scaled = value * (adjust.scale ?? 1) + (adjust.offset ?? 0);
4730
+ // With nothing to add, the percentage alone, which needs no second pass.
4731
+ return scaled === 0 ? `${percent}%` : `calc(${percent}% + ${scaled}px)`;
4732
+ }
4053
4733
  /**
4054
4734
  * A resolved length, moved by the arithmetic that surrounded its `var()`.
4055
4735
  *
@@ -4060,6 +4740,8 @@ function adjusted(value, adjust) {
4060
4740
  if (adjust.alpha !== undefined)
4061
4741
  return faded(value, adjust.alpha);
4062
4742
  const em = value?.__defer;
4743
+ if (adjust.percent !== undefined)
4744
+ return withPercent(value, adjust);
4063
4745
  if (em && adjust.floor === undefined)
4064
4746
  return adjustedEm(em, adjust);
4065
4747
  if (typeof value !== 'number')