@ng-native/fabric 0.8.0 → 0.10.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
@@ -222,10 +222,11 @@ const attributeText = (actual, test) => test.insensitive ? String(actual).toLowe
222
222
  *
223
223
  * A prop set to `false` or `null` counts as absent, matching the web, where an attribute is either
224
224
  * present or not and a `disabled` binding that evaluates false removes it. Everything else is
225
- * compared as a string, so `[tabIndex="0"]` works against a numeric prop.
225
+ * compared as a string, so `[tabIndex="0"]` works against a numeric prop. `class` is no prop: it
226
+ * is the element's class list, as the attribute would spell it.
226
227
  */
227
228
  function matchesAttribute(node, test) {
228
- const actual = node.props[test.name];
229
+ const actual = test.name === 'class' ? classAttribute(node) : node.props[test.name];
229
230
  if (actual === undefined || actual === null || actual === false)
230
231
  return false;
231
232
  if (test.operator === undefined)
@@ -235,6 +236,8 @@ function matchesAttribute(node, test) {
235
236
  return false;
236
237
  return matchesValue(attributeText(actual, test), test.operator, test.value);
237
238
  }
239
+ /** The classes of a node in the order they were set, one space between each; none is absent. */
240
+ const classAttribute = (node) => node.classes?.size ? [...node.classes].join(' ') : undefined;
238
241
  /** The operators an empty value can still match by: `=` and `|=`. */
239
242
  const EMPTY_MATCHES = new Set(['equal', 'dash-match']);
240
243
  /** Whether an attribute's value, as `attributeText` gives it, passes an operator's test. */
@@ -256,6 +259,16 @@ function matchesValue(value, operator, expected) {
256
259
  return value === expected || value.startsWith(`${expected}-`);
257
260
  }
258
261
  }
262
+ /**
263
+ * Whether a node is one a compound written before `::ng-deep` may be: an element of the view of
264
+ * the component the sheet is of. Its host is one only where the compound says `:host`, which is
265
+ * asked of it already: a class a parent gives the host is no anchor.
266
+ */
267
+ function ownedBy(node, compound, sheet) {
268
+ if (compound.host !== undefined || compound.hostContext !== undefined)
269
+ return true;
270
+ return node.sheet === sheet;
271
+ }
259
272
  /*
260
273
  * Exempt from the complexity limit deliberately. This is a flat conjunction of independent
261
274
  * guards, not nested logic: cyclomatic complexity scores the two the same, and they are not the
@@ -277,6 +290,8 @@ function matchesCompound(node, compound, sheet) {
277
290
  }
278
291
  if (compound.type !== undefined && compound.type !== node.name)
279
292
  return false;
293
+ if (compound.own !== undefined && !ownedBy(node, compound, sheet))
294
+ return false;
280
295
  if (compound.id !== undefined && node.props['nativeID'] !== compound.id)
281
296
  return false;
282
297
  for (const className of compound.classes) {
@@ -336,13 +351,15 @@ function matchesCompound(node, compound, sheet) {
336
351
  }
337
352
  }
338
353
  if (compound.hostContext) {
339
- // The host itself counts, as it does on the web.
340
- let found = false;
341
- for (let n = node; n && !found; n = n.parent) {
342
- found = compound.hostContext.some((option) => matchesCompound(n, option, sheet));
354
+ for (const context of compound.hostContext) {
355
+ // The host itself counts, as it does on the web.
356
+ let found = false;
357
+ for (let n = node; n && !found; n = n.parent) {
358
+ found = matchesCompound(n, context, sheet);
359
+ }
360
+ if (!found)
361
+ return false;
343
362
  }
344
- if (!found)
345
- return false;
346
363
  }
347
364
  return true;
348
365
  }
@@ -428,6 +445,82 @@ function matchAny(first, next, rule, index, compound, sheet) {
428
445
  }
429
446
  return false;
430
447
  }
448
+ const BIT_OF = new Map();
449
+ /** Past this many names the bits are worked out again: ids made per row would be kept for ever. */
450
+ const BITS_KEPT = 4096;
451
+ /** The bit a class, a name or an id has among the sixty-four. */
452
+ function bitOf(kind, name) {
453
+ const key = kind + name;
454
+ let bit = BIT_OF.get(key);
455
+ if (bit === undefined) {
456
+ let hash = 0;
457
+ for (let at = 0; at < key.length; at++)
458
+ hash = (Math.imul(hash, 31) + key.charCodeAt(at)) | 0;
459
+ bit = (hash ^ (hash >>> 16)) & 63;
460
+ // A bit is its name's hash, so one forgotten is the same when it is asked for again.
461
+ if (BIT_OF.size >= BITS_KEPT)
462
+ BIT_OF.clear();
463
+ BIT_OF.set(key, bit);
464
+ }
465
+ return bit;
466
+ }
467
+ const NAMED_ABOVE = new WeakMap();
468
+ /**
469
+ * The classes, names and ids a rule names of the node's ancestors: of each part joined to what
470
+ * is on its right by a descendant or a child combinator. One joined by a sibling combinator is
471
+ * no ancestor of the node, though the parts to its left that are ancestors of it are ancestors
472
+ * of the node as well. Null where it names none, a rule of one part among them.
473
+ */
474
+ function namedAbove(rule) {
475
+ let named = NAMED_ABOVE.get(rule);
476
+ if (named !== undefined)
477
+ return named;
478
+ let [low, high] = [0, 0];
479
+ const add = (bit) => {
480
+ if (bit < 32)
481
+ low |= 1 << bit;
482
+ else
483
+ high |= 1 << (bit - 32);
484
+ };
485
+ for (let at = 0; at < rule.compounds.length - 1; at++) {
486
+ const joined = rule.combinators[at];
487
+ if (joined === 'next-sibling' || joined === 'later-sibling')
488
+ continue;
489
+ const compound = rule.compounds[at];
490
+ for (const className of compound.classes)
491
+ add(bitOf('.', className));
492
+ if (compound.type !== undefined)
493
+ add(bitOf('<', compound.type));
494
+ if (compound.id !== undefined)
495
+ add(bitOf('#', compound.id));
496
+ }
497
+ named = low === 0 && high === 0 ? null : [low, high];
498
+ NAMED_ABOVE.set(rule, named);
499
+ return named;
500
+ }
501
+ /** The classes, names and ids of a node's ancestors, as bits. */
502
+ function bitsAbove(node) {
503
+ let [low, high] = [0, 0];
504
+ const add = (bit) => {
505
+ if (bit < 32)
506
+ low |= 1 << bit;
507
+ else
508
+ high |= 1 << (bit - 32);
509
+ };
510
+ for (let up = node.parent; up; up = up.parent) {
511
+ if (up.classes)
512
+ for (const className of up.classes)
513
+ add(bitOf('.', className));
514
+ if (up.name !== undefined)
515
+ add(bitOf('<', up.name));
516
+ const id = up.props?.['nativeID'];
517
+ if (typeof id === 'string')
518
+ add(bitOf('#', id));
519
+ }
520
+ return [low, high];
521
+ }
522
+ /** Whether what is above a node lacks something a rule names there. */
523
+ const lacks = (above, named) => (named[0] & ~above[0]) !== 0 || (named[1] & ~above[1]) !== 0;
431
524
  const TAKES_PARENTS = new WeakMap();
432
525
  /** Whether a rule has an `inherit` in it, which is settled from the parent's own style. */
433
526
  function takesParents(rule) {
@@ -491,8 +584,9 @@ function stepsAlong(combinators) {
491
584
  * matched again by other means, and which way it reaches is not worked out.
492
585
  */
493
586
  function countSiblings(compound, reach, withinHas) {
494
- // Which is first and which is last is the ends, and no count.
495
- const counts = (compound.nth ?? []).filter((test) => test.a !== 0 || test.b !== 1);
587
+ // Which is first and which is last is the ends, and no count. Not of one name: the first of
588
+ // its name is wherever the others of it are not.
589
+ const counts = (compound.nth ?? []).filter((test) => test.a !== 0 || test.b !== 1 || test.ofType);
496
590
  if (counts.some((test) => withinHas || !test.fromEnd))
497
591
  reach.after = Infinity;
498
592
  if (counts.some((test) => withinHas || test.fromEnd))
@@ -532,10 +626,13 @@ export function placeReadElsewhere(sheet) {
532
626
  if (joined === 'next-sibling' || joined === 'later-sibling')
533
627
  return;
534
628
  const run = rule.compounds.slice(start, at + 1);
629
+ // What the run is in: the compound before it, which every element of it is under.
630
+ const list = rule.compounds[start - 1];
631
+ const child = rule.combinators[start - 1] === 'child';
535
632
  start = at + 1;
536
633
  const above = at < rule.compounds.length - 1;
537
634
  if (above && (run.length > 1 || run.some(readsPlace)))
538
- elsewhere.push(compound);
635
+ elsewhere.push({ compound, above: list?.classes, child });
539
636
  });
540
637
  }
541
638
  known = elsewhere;
@@ -562,7 +659,7 @@ function placeReadAbove(compound, elsewhere) {
562
659
  ];
563
660
  for (const inner of above)
564
661
  if (readsPlace(inner))
565
- elsewhere.push(inner);
662
+ elsewhere.push({ compound: inner });
566
663
  for (const inner of nestedCompounds(compound))
567
664
  placeReadAbove(inner, elsewhere);
568
665
  }
@@ -596,10 +693,10 @@ function matchesNth(node, test) {
596
693
  const siblings = node.parent?.children;
597
694
  if (!siblings)
598
695
  return false;
599
- if (test.a === 0 && test.b === 1) {
696
+ if (test.a === 0 && test.b === 1 && !test.ofType) {
600
697
  return (test.fromEnd ? lastElement(siblings) : firstElement(siblings)) === node;
601
698
  }
602
- const from = countPosition(node, siblings, test.fromEnd === true);
699
+ const from = countPosition(node, siblings, test.fromEnd === true, test.ofType === true);
603
700
  if (from === 0)
604
701
  return false;
605
702
  // `an + b` for some whole number of steps, which is what CSS counts from.
@@ -817,11 +914,11 @@ function byImportance(matched, places) {
817
914
  }));
818
915
  return [...plain, ...important];
819
916
  }
820
- function countPosition(node, siblings, fromEnd) {
917
+ function countPosition(node, siblings, fromEnd, ofType) {
821
918
  let position = 0;
822
919
  let seen = 0;
823
920
  for (const sibling of siblings) {
824
- if (!isElement(sibling))
921
+ if (!isElement(sibling) || (ofType && sibling.name !== node.name))
825
922
  continue;
826
923
  seen++;
827
924
  if (sibling === node)
@@ -1094,6 +1191,13 @@ const DEFAULT_FONT_SIZE = 16;
1094
1191
  const EMPTY = {};
1095
1192
  /** Stands in for the parent of a root node, so the comparison needs no null case. */
1096
1193
  const ROOT_CONTEXT = {};
1194
+ /** The `scope` over a node with no parent. */
1195
+ const ROOT_SCOPE = {};
1196
+ /**
1197
+ * The `scope` of the one cache every node with nothing to style shares. It is no one node's, so
1198
+ * nothing styled under it, or holding it, is taken to match what it did.
1199
+ */
1200
+ const SHARED_SCOPE = {};
1097
1201
  /** Shared empty token map, for the overwhelmingly common case of a subtree defining none. */
1098
1202
  const NO_TOKENS = {};
1099
1203
  /**
@@ -1116,6 +1220,42 @@ function subjectsOf(sheet) {
1116
1220
  subjects.set(sheet, (found = all));
1117
1221
  return found;
1118
1222
  }
1223
+ const HAS_ABOVE = new WeakMap();
1224
+ /** Whether a compound, or one it asks of the same node, uses `:has()`. */
1225
+ const asksBeneath = (compound) => compound.has !== undefined ||
1226
+ [...(compound.not ?? []), ...(compound.is ?? []).flat()].some(asksBeneath);
1227
+ /**
1228
+ * The boxes above the styled node that a compound asks `:has()` of: the compound's own node,
1229
+ * where that is not the styled one, and each box it is asked to be inside,
1230
+ * `.body:is(.card:has(.x) *) .title`.
1231
+ */
1232
+ function* boxesAsked(compound, styled) {
1233
+ if (!styled && asksBeneath(compound))
1234
+ yield compound;
1235
+ for (const key of ['ancestors', 'parents', 'hostContext']) {
1236
+ for (const outer of compound[key] ?? [])
1237
+ yield* boxesAsked(outer, false);
1238
+ }
1239
+ }
1240
+ /** Each box a sheet's rules ask `:has()` of above the node they style. */
1241
+ export function hasAbove(sheet) {
1242
+ let known = HAS_ABOVE.get(sheet);
1243
+ if (known === undefined) {
1244
+ const found = [];
1245
+ for (const rule of sheet.rules) {
1246
+ const last = rule.compounds.length - 1;
1247
+ for (const [at, compound] of rule.compounds.entries()) {
1248
+ for (const box of boxesAsked(compound, at === last)) {
1249
+ const subjects = noSubjects();
1250
+ noteSubject(subjects, rule);
1251
+ found.push({ classes: box.classes, subjects });
1252
+ }
1253
+ }
1254
+ }
1255
+ HAS_ABOVE.set(sheet, (known = found));
1256
+ }
1257
+ return known;
1258
+ }
1119
1259
  /** Note in `into` what a rule is for. */
1120
1260
  function noteSubject(into, rule) {
1121
1261
  const subject = rule.compounds.at(-1);
@@ -1147,17 +1287,29 @@ function classReach(sheet) {
1147
1287
  for (const rule of sheet.rules) {
1148
1288
  const every = new Set();
1149
1289
  collectClasses(rule, every, reads, named);
1150
- const inside = classesInside(rule);
1290
+ const { own, inside } = classPlaces(rule);
1151
1291
  for (const name of every) {
1152
- if (!inside.has(name))
1292
+ if (!own.has(name) && !inside.has(name))
1153
1293
  classes.set(name, true);
1154
- else
1294
+ if (own.has(name))
1295
+ noteOwn(classes, name);
1296
+ if (inside.has(name))
1155
1297
  noteInside(classes, name, rule);
1156
1298
  }
1157
1299
  }
1158
1300
  reaches.set(sheet, (found = { classes, reads, named }));
1159
1301
  return found;
1160
1302
  }
1303
+ /** Note a rule as one for the element that has a class. */
1304
+ function noteOwn(found, name) {
1305
+ const subjects = found.get(name);
1306
+ if (subjects === true)
1307
+ return;
1308
+ if (subjects)
1309
+ subjects.own = true;
1310
+ else
1311
+ found.set(name, { ...noSubjects(), own: true });
1312
+ }
1161
1313
  /** Note a rule as one that names a class as what its element is inside. */
1162
1314
  function noteInside(found, name, rule) {
1163
1315
  let subjects = found.get(name);
@@ -1181,6 +1333,8 @@ const noSubjects = () => ({
1181
1333
  inside: new Set(),
1182
1334
  any: false,
1183
1335
  });
1336
+ /** The reach of classes that rules are for the element of, and for nothing under it. */
1337
+ const OWN_ALONE = { ...noSubjects(), own: true };
1184
1338
  /** Add to `into` what `from` is for. */
1185
1339
  function addSubjects(into, from) {
1186
1340
  for (const name of from.classes)
@@ -1190,30 +1344,67 @@ function addSubjects(into, from) {
1190
1344
  for (const name of from.inside)
1191
1345
  into.inside.add(name);
1192
1346
  into.any ||= from.any;
1347
+ if (from.own)
1348
+ into.own = true;
1193
1349
  }
1194
1350
  /**
1195
- * The classes a rule names only as what its element is inside: on a compound before the last,
1196
- * with nothing but `>` and a space between there and the element, and nowhere else in the rule.
1351
+ * Where a rule names each class it names, for the classes it names in no other place than these:
1352
+ *
1353
+ * - `own`, on the element the rule is for: its own classes, and those in `:is()` and `:not()` of
1354
+ * it, which are asked of that element;
1355
+ * - `inside`, as what that element is inside: the classes of a compound before the last, with
1356
+ * nothing but `>` and a space between there and the element, and those of what `:is(.box *)`
1357
+ * and `:is(.box > *)` put over it.
1358
+ *
1359
+ * A class named anywhere else in the rule is in neither, whatever else names it: beside the
1360
+ * element, inside `:has()` or `:host-context()`, or in `:not()` of a box the element is in.
1197
1361
  */
1198
- function classesInside(rule) {
1362
+ function classPlaces(rule) {
1363
+ const own = new Set();
1199
1364
  const inside = new Set();
1200
1365
  const elsewhere = new Set();
1201
1366
  const last = rule.compounds.length - 1;
1202
1367
  let under = true;
1203
1368
  for (let index = last; index >= 0; index--) {
1204
1369
  const compound = rule.compounds[index];
1205
- if (index < last) {
1206
- const joined = rule.combinators[index];
1207
- under &&= joined === 'child' || joined === 'descendant';
1370
+ if (index === last) {
1371
+ placeClasses(compound, own, inside, elsewhere);
1372
+ continue;
1208
1373
  }
1209
- const { classes, ...rest } = compound;
1210
- collectClasses(rest, elsewhere, []);
1211
- for (const name of classes)
1212
- (index < last && under ? inside : elsewhere).add(name);
1213
- }
1214
- for (const name of elsewhere)
1374
+ const joined = rule.combinators[index];
1375
+ under &&= joined === 'child' || joined === 'descendant';
1376
+ // A box the element is under, by its classes and by those it is asked to have or not to,
1377
+ // `.box:not(.busy) .row`: each is asked of that box, as its own are.
1378
+ const over = under ? inside : elsewhere;
1379
+ placeClasses(compound, over, over, elsewhere);
1380
+ }
1381
+ for (const name of elsewhere) {
1382
+ own.delete(name);
1215
1383
  inside.delete(name);
1216
- return inside;
1384
+ }
1385
+ return { own, inside };
1386
+ }
1387
+ /**
1388
+ * The classes of a compound asked of the element a rule is for, into `at`, and of the compounds
1389
+ * it puts over that element, into `inside`. What any other part of it names goes `elsewhere`:
1390
+ * a part read here by name is one this knows the reach of, and one added later is not.
1391
+ */
1392
+ function placeClasses(compound, at, inside, elsewhere) {
1393
+ const { classes, is, not, ancestors, parents, type: _t, id: _i, root: _r, pseudo: _p, ...rest } = compound;
1394
+ for (const name of classes)
1395
+ at.add(name);
1396
+ for (const alternatives of is ?? []) {
1397
+ for (const option of alternatives)
1398
+ placeClasses(option, at, inside, elsewhere);
1399
+ }
1400
+ for (const excluded of not ?? [])
1401
+ placeClasses(excluded, at, inside, elsewhere);
1402
+ // What is over the element is over it whichever of these said so, and what is over that is too.
1403
+ for (const above of [...(ancestors ?? []), ...(parents ?? [])]) {
1404
+ placeClasses(above, inside, inside, elsewhere);
1405
+ }
1406
+ // `attributes` among them: a test of the `class` attribute is answered through `classTests`.
1407
+ collectClasses(rest, elsewhere, []);
1217
1408
  }
1218
1409
  /** What a rule declares, which names no element. */
1219
1410
  const DECLARED = new Set(['declarations', 'important', 'tokens']);
@@ -1283,12 +1474,22 @@ function writtenFor(of, node) {
1283
1474
  }
1284
1475
  return false;
1285
1476
  }
1477
+ /**
1478
+ * Whether a node has its answer for this commit already. Not one marked since it was given the
1479
+ * one cache every box with nothing to style shares, see `unstyled`: another of them resolved in
1480
+ * this commit has set that cache's epoch, and it is not this node's answer.
1481
+ */
1482
+ function resolvedIn(epoch, cache, node) {
1483
+ return (cache !== null && cache.epoch === epoch && (!node.styleDirty || cache.context !== ROOT_CONTEXT));
1484
+ }
1286
1485
  function emptyCacheFor(epoch, generation) {
1287
1486
  return {
1288
1487
  epoch,
1289
1488
  generation,
1290
1489
  context: ROOT_CONTEXT,
1291
1490
  parentContext: ROOT_CONTEXT,
1491
+ scope: SHARED_SCOPE,
1492
+ parentScope: SHARED_SCOPE,
1292
1493
  style: EMPTY,
1293
1494
  inherited: EMPTY,
1294
1495
  tokens: NO_TOKENS,
@@ -1554,11 +1755,19 @@ export class StyleResolver {
1554
1755
  /** The tests of the `class` attribute itself in those sheets, which any class may answer. */
1555
1756
  classTests = [];
1556
1757
  noted = new WeakSet();
1758
+ /**
1759
+ * The sheets with a rule written with `::ng-deep`, which is for what is under the component
1760
+ * in the views of the components it holds. None in most apps, and nothing is asked then.
1761
+ */
1762
+ deepSheets = new Set();
1763
+ deepIndexes = new WeakMap();
1557
1764
  /** Read the classes a sheet names, once, as soon as any node is given it. */
1558
1765
  noteSheet(sheet) {
1559
1766
  if (!sheet || this.noted.has(sheet))
1560
1767
  return;
1561
1768
  this.noted.add(sheet);
1769
+ if (sheet.rules.some((rule) => rule.deep))
1770
+ this.deepSheets.add(sheet);
1562
1771
  const { classes, reads, named } = classReach(sheet);
1563
1772
  for (const [name, how] of classes)
1564
1773
  this.noteReach(name, how);
@@ -1584,20 +1793,38 @@ export class StyleResolver {
1584
1793
  else
1585
1794
  addSubjects(had ?? this.reach.set(name, noSubjects()).get(name), how);
1586
1795
  }
1796
+ /** Whether a class's reach names any element under the one that has it. */
1797
+ reachesUnder(subjects) {
1798
+ return (subjects.any ||
1799
+ subjects.classes.size > 0 ||
1800
+ subjects.types.size > 0 ||
1801
+ subjects.inside.size > 0);
1802
+ }
1587
1803
  /**
1588
1804
  * What classes coming to an element or going from it can restyle: `true` for it and all
1589
- * under it and after it, as any change to it does; the elements under it to restyle, by what
1590
- * rules are for; or null, where no rule names any of them and nothing is styled again.
1805
+ * under it and after it, as any change to it does; the element itself, where `own`, and the
1806
+ * elements under it to match again, by what rules are for; or null, where no rule names any
1807
+ * of them and nothing is styled again.
1591
1808
  */
1592
1809
  reachOf(classes) {
1593
1810
  let under = null;
1811
+ let own = false;
1594
1812
  for (const name of classes) {
1595
1813
  const how = this.reach.get(name);
1596
1814
  if (how === true || this.classTests.some((test) => answers(test, name)))
1597
1815
  return true;
1598
- if (how)
1816
+ if (!how)
1817
+ continue;
1818
+ own ||= how.own === true;
1819
+ // Made only for a class that reaches under: most are a rule's own, and this is asked for
1820
+ // every element as it is given its classes.
1821
+ if (this.reachesUnder(how))
1599
1822
  addSubjects((under ??= noSubjects()), how);
1600
1823
  }
1824
+ if (!under)
1825
+ return own ? OWN_ALONE : null;
1826
+ if (own)
1827
+ under.own = true;
1601
1828
  return under;
1602
1829
  }
1603
1830
  /** Whether one of the rules `subjects` stands for could be for a node. */
@@ -1658,7 +1885,7 @@ export class StyleResolver {
1658
1885
  const cached = node.styleCache;
1659
1886
  // Within one commit a node is asked for its inherited map once per descendant, so this is the
1660
1887
  // hot path and it must not walk anywhere.
1661
- if (cached !== null && cached.epoch === epoch)
1888
+ if (resolvedIn(epoch, cached, node))
1662
1889
  return cached;
1663
1890
  const parent = node.parent ? this.resolve(node.parent, epoch) : null;
1664
1891
  const parentContext = parent ? parent.context : ROOT_CONTEXT;
@@ -1676,33 +1903,66 @@ export class StyleResolver {
1676
1903
  this.justMatched = null;
1677
1904
  if (kept)
1678
1905
  return kept;
1906
+ return this.styledAgain(node, epoch, parent, found);
1907
+ }
1908
+ /** A node styled now, from the rules found for it or from those it kept: see `matchesStand`. */
1909
+ styledAgain(node, epoch, parent, found) {
1910
+ const cached = node.styleCache;
1911
+ const parentContext = parent ? parent.context : ROOT_CONTEXT;
1679
1912
  const parentInherited = parent ? parent.inherited : EMPTY;
1913
+ const parentScope = parent ? parent.scope : ROOT_SCOPE;
1914
+ const stands = this.matchesStand(cached, node, parentScope);
1915
+ // The scope it keeps, or none: it then has a new one, which its new `context` serves as.
1916
+ const scope = stands ? cached.scope : null;
1680
1917
  // A box the engine made around loose text is in no template, so no rule matches it.
1681
1918
  if (node.anonymous || this.hasNoRules(node)) {
1682
- return this.unstyled(node, epoch, parent, parentContext, parentInherited);
1919
+ return this.unstyled(node, epoch, parent, parentInherited, scope, parentScope);
1683
1920
  }
1684
1921
  styleStats.nodesResolved++;
1685
- const matched = found ?? this.matchedBy(node);
1922
+ // Its own rules are found again for a change of its own, and not for one above it.
1923
+ const rules = stands && !node.ownDirty ? cached.rules : undefined;
1924
+ const matched = found ?? rules ?? this.matchedBy(node);
1686
1925
  const parentTokens = parent ? parent.tokens : this.tokensOnRoot;
1687
1926
  this.heir(node, parent, matched);
1688
1927
  const styled = this.styled(node, matched, parentTokens, parentInherited);
1689
- const cache = this.cached(epoch, parentContext, styled, matched);
1928
+ const cache = this.cached(epoch, parentContext, styled, matched, scope, parentScope);
1690
1929
  node.styleCache = cache;
1691
1930
  node.styleDirty = false;
1931
+ node.ownDirty = false;
1692
1932
  node.hasDirty = false;
1693
1933
  node.stateDirty = false;
1694
1934
  return cache;
1695
1935
  }
1936
+ /**
1937
+ * Whether what an element could match is as it was when it was last styled: nothing that can
1938
+ * change it was marked on the element, its parent's `scope` is the one it was styled under,
1939
+ * and no sheet that could match it has come since. It is being styled again all the same, for
1940
+ * a change of its own that alters nothing under it, or for what it inherits.
1941
+ */
1942
+ matchesStand(cached, node, parentScope) {
1943
+ return (cached !== null &&
1944
+ !node.styleDirty &&
1945
+ !node.hasDirty &&
1946
+ !node.stateDirty &&
1947
+ cached.scope !== SHARED_SCOPE &&
1948
+ parentScope !== SHARED_SCOPE &&
1949
+ cached.parentScope === parentScope &&
1950
+ this.current(cached, node));
1951
+ }
1696
1952
  /**
1697
1953
  * A cache of a node just styled, with a context of its own. It keeps the rules the node
1698
1954
  * matched, for the node to be compared with, once a sheet has a use for them: see `tracksHas`.
1699
1955
  */
1700
- cached(epoch, parentContext, styled, matched) {
1956
+ cached(epoch, parentContext, styled, matched, scope, parentScope) {
1957
+ const context = {};
1701
1958
  const cache = {
1702
1959
  epoch,
1703
1960
  generation: this.generation,
1704
- context: {},
1961
+ context,
1705
1962
  parentContext,
1963
+ scope: scope ?? context,
1964
+ parentScope,
1965
+ rules: matched,
1706
1966
  ...styled,
1707
1967
  };
1708
1968
  if (this.tracksHas)
@@ -1725,6 +1985,7 @@ export class StyleResolver {
1725
1985
  const stands = (node.hasDirty === true || node.stateDirty === true) &&
1726
1986
  cached?.matched !== undefined &&
1727
1987
  !node.styleDirty &&
1988
+ !node.ownDirty &&
1728
1989
  cached.parentContext === parentContext &&
1729
1990
  this.current(cached, node);
1730
1991
  if (!stands)
@@ -1764,6 +2025,7 @@ export class StyleResolver {
1764
2025
  const stands = node.stateDirty === true &&
1765
2026
  cached !== null &&
1766
2027
  !node.styleDirty &&
2028
+ !node.ownDirty &&
1767
2029
  !node.hasDirty &&
1768
2030
  cached.parentContext === parentContext &&
1769
2031
  this.current(cached, node) &&
@@ -1781,6 +2043,14 @@ export class StyleResolver {
1781
2043
  holds(node, parentContext) {
1782
2044
  return this.reusable(node.styleCache, node, parentContext);
1783
2045
  }
2046
+ /**
2047
+ * Whether a node was last styled under a box holding the one cache shared by every box with
2048
+ * nothing to style. Such boxes hand the same `context` down, so the node cannot tell one from
2049
+ * another by it: moved under another box, it has to be told.
2050
+ */
2051
+ styledUnderShared(node) {
2052
+ return node.styleCache !== null && node.styleCache.parentContext === ROOT_CONTEXT;
2053
+ }
1784
2054
  /**
1785
2055
  * Whether a previous result still stands: nothing about the node changed, nothing above it
1786
2056
  * moved, and the conditions have not.
@@ -1788,6 +2058,7 @@ export class StyleResolver {
1788
2058
  reusable(cached, node, parentContext) {
1789
2059
  return (cached !== null &&
1790
2060
  !node.styleDirty &&
2061
+ !node.ownDirty &&
1791
2062
  !node.hasDirty &&
1792
2063
  !node.stateDirty &&
1793
2064
  cached.parentContext === parentContext &&
@@ -1800,6 +2071,7 @@ export class StyleResolver {
1800
2071
  hasNoRules(node) {
1801
2072
  return (node.sheet === null &&
1802
2073
  node.hostSheet === null &&
2074
+ this.deepSheets.size === 0 &&
1803
2075
  this.globalSheet === null &&
1804
2076
  this.addedSheets.length === 0 &&
1805
2077
  !node.customProperties &&
@@ -1815,35 +2087,43 @@ export class StyleResolver {
1815
2087
  * carry: the shared object holds none, and the surface root answering with it dropped every
1816
2088
  * token the device had seeded.
1817
2089
  */
1818
- unstyled(node, epoch, parent, parentContext, parentInherited) {
2090
+ unstyled(node, epoch, parent, parentInherited, scope, parentScope) {
1819
2091
  const tokens = parent ? parent.tokens : this.tokensOnRoot;
2092
+ const parentContext = parent ? parent.context : ROOT_CONTEXT;
1820
2093
  // What inline style sets is inherited too, with no rule to read it against.
1821
2094
  const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
1822
- if (!inline &&
2095
+ const nothing = !inline &&
1823
2096
  parentInherited === EMPTY &&
1824
2097
  parentContext === ROOT_CONTEXT &&
1825
- tokens === NO_TOKENS &&
1826
- !node.styleDirty) {
1827
- if (this.emptyCache.epoch !== epoch || this.emptyCache.generation !== this.generation) {
1828
- this.emptyCache = emptyCacheFor(epoch, this.generation);
1829
- }
1830
- node.styleCache = this.emptyCache;
1831
- return this.emptyCache;
1832
- }
2098
+ tokens === NO_TOKENS;
2099
+ if (nothing && !node.styleDirty && !node.ownDirty)
2100
+ return this.sharedEmpty(node, epoch);
2101
+ const context = {};
1833
2102
  const passthrough = {
1834
2103
  epoch,
1835
2104
  generation: this.generation,
1836
- context: {},
2105
+ context,
1837
2106
  parentContext,
2107
+ scope: scope ?? context,
2108
+ parentScope,
1838
2109
  style: unstyledStyle(parentInherited, inline),
1839
2110
  inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
1840
2111
  tokens,
1841
2112
  };
1842
2113
  node.styleCache = passthrough;
1843
2114
  node.styleDirty = false;
2115
+ node.ownDirty = false;
1844
2116
  node.stateDirty = false;
1845
2117
  return passthrough;
1846
2118
  }
2119
+ /** The one cache of every node with nothing to style and nothing to hand down, given to one more. */
2120
+ sharedEmpty(node, epoch) {
2121
+ if (this.emptyCache.epoch !== epoch || this.emptyCache.generation !== this.generation) {
2122
+ this.emptyCache = emptyCacheFor(epoch, this.generation);
2123
+ }
2124
+ node.styleCache = this.emptyCache;
2125
+ return this.emptyCache;
2126
+ }
1847
2127
  /**
1848
2128
  * The rules worth trying against a node, in cascade order.
1849
2129
  *
@@ -1872,9 +2152,33 @@ export class StyleResolver {
1872
2152
  indexes.push(this.ownIndex(node.hostSheet, HOSTS));
1873
2153
  if (node.sheet)
1874
2154
  indexes.push(this.ownIndex(node.sheet, CREATED));
2155
+ if (this.deepSheets.size)
2156
+ this.reachedFromAbove(node, indexes);
1875
2157
  indexes.push(this.sharedIndex());
1876
2158
  return candidateRules(node, indexes, this.cascadeOrder);
1877
2159
  }
2160
+ /**
2161
+ * Add the `::ng-deep` rules of each component a node is in whose sheet is not the node's own:
2162
+ * a node is tried against its own component's rules already, those among them.
2163
+ */
2164
+ reachedFromAbove(node, indexes) {
2165
+ for (let up = node.parent; up; up = up.parent) {
2166
+ const sheet = up.hostSheet;
2167
+ if (!sheet || sheet === node.sheet || sheet === node.hostSheet)
2168
+ continue;
2169
+ if (this.deepSheets.has(sheet))
2170
+ indexes.push(this.deepIndex(sheet));
2171
+ }
2172
+ }
2173
+ /** A sheet's `::ng-deep` rules alone, filed as a component's own are. */
2174
+ deepIndex(sheet) {
2175
+ let index = this.deepIndexes.get(sheet);
2176
+ if (!index) {
2177
+ const deep = this.entries(sheet, COMPONENT_SPECIFICITY_BUMP).filter(({ rule }) => rule.deep);
2178
+ this.deepIndexes.set(sheet, (index = indexRules(deep, undefined, HOSTS * SHEET_ROOM)));
2179
+ }
2180
+ return index;
2181
+ }
1878
2182
  /** Weakest first: by layer where any sheet has one, then by weight, then as they were filed. */
1879
2183
  cascadeOrder = (a, b) => {
1880
2184
  const layers = this.layered
@@ -2014,8 +2318,15 @@ export class StyleResolver {
2014
2318
  /** The rules that apply to a node, weakest first. */
2015
2319
  matched(node, entries) {
2016
2320
  const out = [];
2321
+ // What the node's ancestors are, gathered for the first rule that names one.
2322
+ let above;
2017
2323
  for (const { rule, sheet } of entries) {
2018
- if (this.conditionHolds(rule) && matches(node, rule, sheet))
2324
+ if (!this.conditionHolds(rule))
2325
+ continue;
2326
+ const named = namedAbove(rule);
2327
+ if (named && lacks((above ??= bitsAbove(node)), named))
2328
+ continue;
2329
+ if (matches(node, rule, sheet))
2019
2330
  out.push(rule);
2020
2331
  }
2021
2332
  return out;
@@ -2065,13 +2376,17 @@ export class StyleResolver {
2065
2376
  for (const [at, key] of written.entries()) {
2066
2377
  if (!(key in TWIN.ltr))
2067
2378
  continue;
2068
- twin ??= TWIN[(style['direction'] ?? this.conditions.direction) === 'rtl' ? 'rtl' : 'ltr'];
2379
+ twin ??= TWIN[this.readsRightToLeft(style) ? 'rtl' : 'ltr'];
2069
2380
  const other = twin[key];
2070
2381
  // Where the inline style sets both, the one it set later stands.
2071
2382
  if (written.indexOf(other) < at)
2072
2383
  delete style[other];
2073
2384
  }
2074
2385
  }
2386
+ /** Whether a settled style is of an element read right to left: by its own, or the app's. */
2387
+ readsRightToLeft(style) {
2388
+ return (style['direction'] ?? this.conditions.direction) === 'rtl';
2389
+ }
2075
2390
  /**
2076
2391
  * The direction an element is laid out in: the last `direction` its own rules give it, the one
2077
2392
  * it inherits, or the app's. A logical corner is a different physical one in each.
@@ -2150,6 +2465,17 @@ export class StyleResolver {
2150
2465
  * A declaration with no token and no usable fallback is dropped rather than written as
2151
2466
  * undefined, which is what a browser does with a value it cannot resolve.
2152
2467
  */
2468
+ /**
2469
+ * What declarations that wait for a node's tokens come to on it, where no rule of the node's
2470
+ * carries them: a keyframe's, which is settled against the element that plays it.
2471
+ */
2472
+ settleFor(node, deferred, epoch) {
2473
+ const { tokens } = this.resolve(node, epoch);
2474
+ const above = node.parent ? this.resolve(node.parent, epoch).inherited : EMPTY;
2475
+ const own = {};
2476
+ this.applyDeferred(deferred, own, tokens, above, null);
2477
+ return own;
2478
+ }
2153
2479
  applyDeferred(deferred, own, tokens, parentInherited, important) {
2154
2480
  for (const declaration of settlingOrder(deferred)) {
2155
2481
  if (declaration.inherit) {
@@ -2528,6 +2854,9 @@ function substitutable(token, tokens) {
2528
2854
  return hslSubstitutable(token.hsl, tokens);
2529
2855
  if (token.deferredColour)
2530
2856
  return colourSubstitutable(token.deferredColour, tokens);
2857
+ if (token.deferredLengths) {
2858
+ return token.deferredLengths.every((part) => typeof part !== 'object' || substitutedIn(part, tokens) !== undefined);
2859
+ }
2531
2860
  // Each marker is one reading of the same text, and a fallback one cannot read is dropped from it.
2532
2861
  return token.deferredCalc.some((marker) => calcSubstitutable(marker.expression, tokens));
2533
2862
  }
@@ -2606,7 +2935,7 @@ function inCycles(names, waitingOn) {
2606
2935
  }
2607
2936
  /** Whether a token is made of others, and so worked out where it is defined. */
2608
2937
  function isDerived(token) {
2609
- return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc);
2938
+ return Boolean(token?.hsl || token?.deferredColour || token?.deferredCalc || token?.deferredLengths);
2610
2939
  }
2611
2940
  /** A token made of others, worked out from the tokens in scope. */
2612
2941
  function derived(token, tokens) {
@@ -2614,8 +2943,23 @@ function derived(token, tokens) {
2614
2943
  return colourToken(resolveHsl(token.hsl, tokens));
2615
2944
  if (token.deferredColour)
2616
2945
  return colourToken(resolveColour(token.deferredColour, tokens));
2946
+ if (token.deferredLengths)
2947
+ return lengthsToken(token, tokens);
2617
2948
  return calcToken(token.deferredCalc, tokens);
2618
2949
  }
2950
+ /**
2951
+ * A token of several lengths with each part that is a `var()` followed. Where a part comes to
2952
+ * no length the token is what else it reads as, a shadow whose colour is a token being several
2953
+ * lengths and a `var()` too, or nothing where it reads as nothing else.
2954
+ */
2955
+ function lengthsToken(token, tokens) {
2956
+ const { deferredLengths, ...rest } = token;
2957
+ const lengths = deferredLengths.map((part) => typeof part === 'object' ? substitutedIn(part, tokens)?.length : part);
2958
+ if (lengths.every((length) => length !== undefined)) {
2959
+ return { ...rest, lengths: lengths };
2960
+ }
2961
+ return Object.keys(rest).length ? rest : undefined;
2962
+ }
2619
2963
  /**
2620
2964
  * Each alias settled to what it names. `follow` writes back every one that resolves; one in a
2621
2965
  * cycle is removed only once all are followed, as removed any sooner, the next one round the
@@ -2789,7 +3133,7 @@ function referenced(declaration, tokens) {
2789
3133
  // property unset rather than trying the next, or the fallback.
2790
3134
  const names = [declaration.reference, ...(declaration.alternatives ?? [])];
2791
3135
  const token = firstSet(names, tokens);
2792
- let value = token ? tokenForm(token, declaration.kind) : fallbackOf(declaration, tokens);
3136
+ let value = token ? formFor(token, declaration) : fallbackOf(declaration, tokens);
2793
3137
  // `calc(var(--n) * 1px)`: the arithmetic gives a unitless token its unit, which is the usual
2794
3138
  // way to turn a count into a length. So it reads the bare number, and a length is no such thing.
2795
3139
  if (token && declaration.adjust?.number && declaration.kind === 'length') {
@@ -2837,9 +3181,52 @@ function tokenForm(token, kind) {
2837
3181
  const value = formOf(token, kind);
2838
3182
  return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
2839
3183
  }
3184
+ /** A corner's place among four as CSS writes them, before a side's: `borderTopLeftRadius`. */
3185
+ const PLACES = [
3186
+ [/TopLeft|TopStart|StartStart/, 0],
3187
+ [/TopRight|TopEnd|StartEnd/, 1],
3188
+ [/BottomRight|BottomEnd|EndEnd/, 2],
3189
+ [/BottomLeft|BottomStart|EndStart/, 3],
3190
+ [/Top/, 0],
3191
+ [/Right|End/, 1],
3192
+ [/Bottom/, 2],
3193
+ [/Left|Start/, 3],
3194
+ ];
3195
+ /** Where a side or a corner comes in a shorthand of four, whatever order its props are kept in. */
3196
+ const placeOf = (prop) => PLACES.find(([named]) => named.test(prop))?.[1] ?? 0;
3197
+ /** The lengths of a token that holds several, for the props of a shorthand to take one each. */
3198
+ class EachSide {
3199
+ lengths;
3200
+ constructor(lengths) {
3201
+ this.lengths = lengths;
3202
+ }
3203
+ /**
3204
+ * The length for one of a shorthand's four props, as CSS gives them: two are a pair repeated,
3205
+ * and of three the left takes the right's. Nothing for a property that is not such a
3206
+ * shorthand: a token of several lengths is no value for it, and it is unset.
3207
+ */
3208
+ at(props, prop) {
3209
+ if (props.length !== 4)
3210
+ return undefined;
3211
+ const index = placeOf(prop);
3212
+ return this.lengths[index] ?? this.lengths[index - 2] ?? this.lengths[0];
3213
+ }
3214
+ }
3215
+ /**
3216
+ * A set token's value in the form a declaration wants, or its several lengths where it wants a
3217
+ * length and the token has no one: for a shorthand that takes one a side, see `writeSettled`.
3218
+ * Not with arithmetic on it, which is of one length.
3219
+ */
3220
+ function formFor(token, declaration) {
3221
+ const value = tokenForm(token, declaration.kind);
3222
+ if (value !== undefined || !token.lengths || declaration.kind !== 'length')
3223
+ return value;
3224
+ return declaration.adjust ? undefined : new EachSide(token.lengths);
3225
+ }
2840
3226
  /** What a deferred declaration settled to, written to one of its props. */
2841
3227
  function writeSettled(own, prop, declaration, settled, layers) {
2842
- const value = settled ?? declaration.unset;
3228
+ const one = settled instanceof EachSide ? settled.at(declaration.props, prop) : settled;
3229
+ const value = one ?? declaration.unset;
2843
3230
  if (declaration.layers)
2844
3231
  writeLayer(own, prop, value, layers);
2845
3232
  else