@ng-native/fabric 0.8.0 → 0.9.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.d.ts CHANGED
@@ -30,7 +30,7 @@ export interface Compound {
30
30
  readonly not?: readonly Compound[];
31
31
  /** `:host`: the node must be the host of the component whose sheet is being evaluated. */
32
32
  readonly host?: true;
33
- /** `:host-context()`: the host, or one of its ancestors, must match one of these. */
33
+ /** `:host-context()`, once for each written: the host, or one of its ancestors, must match it. */
34
34
  readonly hostContext?: readonly Compound[];
35
35
  /**
36
36
  * `:is(<compound> *)`: every one of these must match some ancestor, the node itself excluded.
@@ -485,6 +485,18 @@ export interface StyleCache {
485
485
  context: object;
486
486
  /** The parent's `context` this was computed from. */
487
487
  parentContext: object;
488
+ /**
489
+ * Identity token standing for what an element under this node could match differently: minted
490
+ * when the node is styled for a change that can alter that, and kept when it is styled again
491
+ * for one that cannot, a custom property of its own or an inherited value from above. An
492
+ * element whose parent's is the one it was last styled under matches the rules it did, so it
493
+ * is styled from those it kept and they are not found again.
494
+ */
495
+ scope: object;
496
+ /** The parent's `scope` this was computed from. */
497
+ parentScope: object;
498
+ /** The rules the node matched, for a styling that finds its parent's `scope` as it was. */
499
+ rules?: readonly StyleRule[];
488
500
  /** The conditions version this was resolved under. */
489
501
  generation: number;
490
502
  style: Record<string, unknown>;
@@ -570,6 +582,12 @@ export interface StyleTarget {
570
582
  styleCache: StyleCache | null;
571
583
  /** Set when something that could change what this node matches has changed. */
572
584
  styleDirty: boolean;
585
+ /**
586
+ * Set when what this node matches, or a custom property it holds, may have changed, and nothing
587
+ * that an element under it is matched by: the node is matched and styled again, and those under
588
+ * it are styled from the rules they matched. Absent on a hand-built target.
589
+ */
590
+ ownDirty?: boolean;
573
591
  /**
574
592
  * Set when something beneath this node changed and a sheet uses `:has()`: the node is matched
575
593
  * again, and restyled only if the rules it matches came out different. Absent on a hand-built
@@ -733,6 +751,11 @@ export interface Subjects {
733
751
  readonly inside: Set<string>;
734
752
  /** A rule for anything anywhere: no class, no name, and no such box to be in. */
735
753
  any: boolean;
754
+ /**
755
+ * In what a class reaches: a rule is for the element that has the class, `.on`, which is then
756
+ * matched again itself. Unset where the rules are only for elements under it.
757
+ */
758
+ own?: boolean;
736
759
  }
737
760
  /** Whether an inline style sets a property children inherit. Allocates nothing: it runs per set. */
738
761
  export declare function setsInherited(style: unknown): boolean;
@@ -865,10 +888,13 @@ export declare class StyleResolver {
865
888
  */
866
889
  reads(prop: string): boolean;
867
890
  private noteReach;
891
+ /** Whether a class's reach names any element under the one that has it. */
892
+ reachesUnder(subjects: Subjects): boolean;
868
893
  /**
869
894
  * What classes coming to an element or going from it can restyle: `true` for it and all
870
- * under it and after it, as any change to it does; the elements under it to restyle, by what
871
- * rules are for; or null, where no rule names any of them and nothing is styled again.
895
+ * under it and after it, as any change to it does; the element itself, where `own`, and the
896
+ * elements under it to match again, by what rules are for; or null, where no rule names any
897
+ * of them and nothing is styled again.
872
898
  */
873
899
  reachOf(classes: Iterable<string>): true | Subjects | null;
874
900
  /** Whether one of the rules `subjects` stands for could be for a node. */
@@ -889,6 +915,15 @@ export declare class StyleResolver {
889
915
  * resolves at most once however many descendants ask for its inherited values.
890
916
  */
891
917
  resolve(node: StyleTarget, epoch: number): StyleCache;
918
+ /** A node styled now, from the rules found for it or from those it kept: see `matchesStand`. */
919
+ private styledAgain;
920
+ /**
921
+ * Whether what an element could match is as it was when it was last styled: nothing that can
922
+ * change it was marked on the element, its parent's `scope` is the one it was styled under,
923
+ * and no sheet that could match it has come since. It is being styled again all the same, for
924
+ * a change of its own that alters nothing under it, or for what it inherits.
925
+ */
926
+ private matchesStand;
892
927
  /**
893
928
  * A cache of a node just styled, with a context of its own. It keeps the rules the node
894
929
  * matched, for the node to be compared with, once a sheet has a use for them: see `tracksHas`.
@@ -925,6 +960,12 @@ export declare class StyleResolver {
925
960
  * without resolving it. What lets a commit step over a clean child in one comparison.
926
961
  */
927
962
  holds(node: StyleTarget, parentContext: object): boolean;
963
+ /**
964
+ * Whether a node was last styled under a box holding the one cache shared by every box with
965
+ * nothing to style. Such boxes hand the same `context` down, so the node cannot tell one from
966
+ * another by it: moved under another box, it has to be told.
967
+ */
968
+ styledUnderShared(node: StyleTarget): boolean;
928
969
  /**
929
970
  * Whether a previous result still stands: nothing about the node changed, nothing above it
930
971
  * moved, and the conditions have not.
@@ -945,6 +986,8 @@ export declare class StyleResolver {
945
986
  * token the device had seeded.
946
987
  */
947
988
  private unstyled;
989
+ /** The one cache of every node with nothing to style and nothing to hand down, given to one more. */
990
+ private sharedEmpty;
948
991
  /**
949
992
  * The rules worth trying against a node, in cascade order.
950
993
  *
@@ -1030,6 +1073,11 @@ export declare class StyleResolver {
1030
1073
  * A declaration with no token and no usable fallback is dropped rather than written as
1031
1074
  * undefined, which is what a browser does with a value it cannot resolve.
1032
1075
  */
1076
+ /**
1077
+ * What declarations that wait for a node's tokens come to on it, where no rule of the node's
1078
+ * carries them: a keyframe's, which is settled against the element that plays it.
1079
+ */
1080
+ settleFor(node: StyleTarget, deferred: readonly DeferredDeclaration[], epoch: number): Record<string, unknown>;
1033
1081
  private applyDeferred;
1034
1082
  /** Note what the node being resolved takes an `inherit` from, and that its parent has an heir. */
1035
1083
  private heir;
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. */
@@ -336,13 +339,15 @@ function matchesCompound(node, compound, sheet) {
336
339
  }
337
340
  }
338
341
  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));
342
+ for (const context of compound.hostContext) {
343
+ // The host itself counts, as it does on the web.
344
+ let found = false;
345
+ for (let n = node; n && !found; n = n.parent) {
346
+ found = matchesCompound(n, context, sheet);
347
+ }
348
+ if (!found)
349
+ return false;
343
350
  }
344
- if (!found)
345
- return false;
346
351
  }
347
352
  return true;
348
353
  }
@@ -1094,6 +1099,13 @@ const DEFAULT_FONT_SIZE = 16;
1094
1099
  const EMPTY = {};
1095
1100
  /** Stands in for the parent of a root node, so the comparison needs no null case. */
1096
1101
  const ROOT_CONTEXT = {};
1102
+ /** The `scope` over a node with no parent. */
1103
+ const ROOT_SCOPE = {};
1104
+ /**
1105
+ * The `scope` of the one cache every node with nothing to style shares. It is no one node's, so
1106
+ * nothing styled under it, or holding it, is taken to match what it did.
1107
+ */
1108
+ const SHARED_SCOPE = {};
1097
1109
  /** Shared empty token map, for the overwhelmingly common case of a subtree defining none. */
1098
1110
  const NO_TOKENS = {};
1099
1111
  /**
@@ -1147,17 +1159,29 @@ function classReach(sheet) {
1147
1159
  for (const rule of sheet.rules) {
1148
1160
  const every = new Set();
1149
1161
  collectClasses(rule, every, reads, named);
1150
- const inside = classesInside(rule);
1162
+ const { own, inside } = classPlaces(rule);
1151
1163
  for (const name of every) {
1152
- if (!inside.has(name))
1164
+ if (!own.has(name) && !inside.has(name))
1153
1165
  classes.set(name, true);
1154
- else
1166
+ if (own.has(name))
1167
+ noteOwn(classes, name);
1168
+ if (inside.has(name))
1155
1169
  noteInside(classes, name, rule);
1156
1170
  }
1157
1171
  }
1158
1172
  reaches.set(sheet, (found = { classes, reads, named }));
1159
1173
  return found;
1160
1174
  }
1175
+ /** Note a rule as one for the element that has a class. */
1176
+ function noteOwn(found, name) {
1177
+ const subjects = found.get(name);
1178
+ if (subjects === true)
1179
+ return;
1180
+ if (subjects)
1181
+ subjects.own = true;
1182
+ else
1183
+ found.set(name, { ...noSubjects(), own: true });
1184
+ }
1161
1185
  /** Note a rule as one that names a class as what its element is inside. */
1162
1186
  function noteInside(found, name, rule) {
1163
1187
  let subjects = found.get(name);
@@ -1181,6 +1205,8 @@ const noSubjects = () => ({
1181
1205
  inside: new Set(),
1182
1206
  any: false,
1183
1207
  });
1208
+ /** The reach of classes that rules are for the element of, and for nothing under it. */
1209
+ const OWN_ALONE = { ...noSubjects(), own: true };
1184
1210
  /** Add to `into` what `from` is for. */
1185
1211
  function addSubjects(into, from) {
1186
1212
  for (const name of from.classes)
@@ -1190,30 +1216,67 @@ function addSubjects(into, from) {
1190
1216
  for (const name of from.inside)
1191
1217
  into.inside.add(name);
1192
1218
  into.any ||= from.any;
1219
+ if (from.own)
1220
+ into.own = true;
1193
1221
  }
1194
1222
  /**
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.
1223
+ * Where a rule names each class it names, for the classes it names in no other place than these:
1224
+ *
1225
+ * - `own`, on the element the rule is for: its own classes, and those in `:is()` and `:not()` of
1226
+ * it, which are asked of that element;
1227
+ * - `inside`, as what that element is inside: the classes of a compound before the last, with
1228
+ * nothing but `>` and a space between there and the element, and those of what `:is(.box *)`
1229
+ * and `:is(.box > *)` put over it.
1230
+ *
1231
+ * A class named anywhere else in the rule is in neither, whatever else names it: beside the
1232
+ * element, inside `:has()` or `:host-context()`, or in `:not()` of a box the element is in.
1197
1233
  */
1198
- function classesInside(rule) {
1234
+ function classPlaces(rule) {
1235
+ const own = new Set();
1199
1236
  const inside = new Set();
1200
1237
  const elsewhere = new Set();
1201
1238
  const last = rule.compounds.length - 1;
1202
1239
  let under = true;
1203
1240
  for (let index = last; index >= 0; index--) {
1204
1241
  const compound = rule.compounds[index];
1205
- if (index < last) {
1206
- const joined = rule.combinators[index];
1207
- under &&= joined === 'child' || joined === 'descendant';
1242
+ if (index === last) {
1243
+ placeClasses(compound, own, inside, elsewhere);
1244
+ continue;
1208
1245
  }
1246
+ const joined = rule.combinators[index];
1247
+ under &&= joined === 'child' || joined === 'descendant';
1209
1248
  const { classes, ...rest } = compound;
1210
1249
  collectClasses(rest, elsewhere, []);
1211
1250
  for (const name of classes)
1212
- (index < last && under ? inside : elsewhere).add(name);
1251
+ (under ? inside : elsewhere).add(name);
1213
1252
  }
1214
- for (const name of elsewhere)
1253
+ for (const name of elsewhere) {
1254
+ own.delete(name);
1215
1255
  inside.delete(name);
1216
- return inside;
1256
+ }
1257
+ return { own, inside };
1258
+ }
1259
+ /**
1260
+ * The classes of a compound asked of the element a rule is for, into `at`, and of the compounds
1261
+ * it puts over that element, into `inside`. What any other part of it names goes `elsewhere`:
1262
+ * a part read here by name is one this knows the reach of, and one added later is not.
1263
+ */
1264
+ function placeClasses(compound, at, inside, elsewhere) {
1265
+ const { classes, is, not, ancestors, parents, type: _t, id: _i, root: _r, pseudo: _p, ...rest } = compound;
1266
+ for (const name of classes)
1267
+ at.add(name);
1268
+ for (const alternatives of is ?? []) {
1269
+ for (const option of alternatives)
1270
+ placeClasses(option, at, inside, elsewhere);
1271
+ }
1272
+ for (const excluded of not ?? [])
1273
+ placeClasses(excluded, at, inside, elsewhere);
1274
+ // What is over the element is over it whichever of these said so, and what is over that is too.
1275
+ for (const above of [...(ancestors ?? []), ...(parents ?? [])]) {
1276
+ placeClasses(above, inside, inside, elsewhere);
1277
+ }
1278
+ // `attributes` among them: a test of the `class` attribute is answered through `classTests`.
1279
+ collectClasses(rest, elsewhere, []);
1217
1280
  }
1218
1281
  /** What a rule declares, which names no element. */
1219
1282
  const DECLARED = new Set(['declarations', 'important', 'tokens']);
@@ -1283,12 +1346,22 @@ function writtenFor(of, node) {
1283
1346
  }
1284
1347
  return false;
1285
1348
  }
1349
+ /**
1350
+ * Whether a node has its answer for this commit already. Not one marked since it was given the
1351
+ * one cache every box with nothing to style shares, see `unstyled`: another of them resolved in
1352
+ * this commit has set that cache's epoch, and it is not this node's answer.
1353
+ */
1354
+ function resolvedIn(epoch, cache, node) {
1355
+ return (cache !== null && cache.epoch === epoch && (!node.styleDirty || cache.context !== ROOT_CONTEXT));
1356
+ }
1286
1357
  function emptyCacheFor(epoch, generation) {
1287
1358
  return {
1288
1359
  epoch,
1289
1360
  generation,
1290
1361
  context: ROOT_CONTEXT,
1291
1362
  parentContext: ROOT_CONTEXT,
1363
+ scope: SHARED_SCOPE,
1364
+ parentScope: SHARED_SCOPE,
1292
1365
  style: EMPTY,
1293
1366
  inherited: EMPTY,
1294
1367
  tokens: NO_TOKENS,
@@ -1584,20 +1657,38 @@ export class StyleResolver {
1584
1657
  else
1585
1658
  addSubjects(had ?? this.reach.set(name, noSubjects()).get(name), how);
1586
1659
  }
1660
+ /** Whether a class's reach names any element under the one that has it. */
1661
+ reachesUnder(subjects) {
1662
+ return (subjects.any ||
1663
+ subjects.classes.size > 0 ||
1664
+ subjects.types.size > 0 ||
1665
+ subjects.inside.size > 0);
1666
+ }
1587
1667
  /**
1588
1668
  * 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.
1669
+ * under it and after it, as any change to it does; the element itself, where `own`, and the
1670
+ * elements under it to match again, by what rules are for; or null, where no rule names any
1671
+ * of them and nothing is styled again.
1591
1672
  */
1592
1673
  reachOf(classes) {
1593
1674
  let under = null;
1675
+ let own = false;
1594
1676
  for (const name of classes) {
1595
1677
  const how = this.reach.get(name);
1596
1678
  if (how === true || this.classTests.some((test) => answers(test, name)))
1597
1679
  return true;
1598
- if (how)
1680
+ if (!how)
1681
+ continue;
1682
+ own ||= how.own === true;
1683
+ // Made only for a class that reaches under: most are a rule's own, and this is asked for
1684
+ // every element as it is given its classes.
1685
+ if (this.reachesUnder(how))
1599
1686
  addSubjects((under ??= noSubjects()), how);
1600
1687
  }
1688
+ if (!under)
1689
+ return own ? OWN_ALONE : null;
1690
+ if (own)
1691
+ under.own = true;
1601
1692
  return under;
1602
1693
  }
1603
1694
  /** Whether one of the rules `subjects` stands for could be for a node. */
@@ -1658,7 +1749,7 @@ export class StyleResolver {
1658
1749
  const cached = node.styleCache;
1659
1750
  // Within one commit a node is asked for its inherited map once per descendant, so this is the
1660
1751
  // hot path and it must not walk anywhere.
1661
- if (cached !== null && cached.epoch === epoch)
1752
+ if (resolvedIn(epoch, cached, node))
1662
1753
  return cached;
1663
1754
  const parent = node.parent ? this.resolve(node.parent, epoch) : null;
1664
1755
  const parentContext = parent ? parent.context : ROOT_CONTEXT;
@@ -1676,33 +1767,66 @@ export class StyleResolver {
1676
1767
  this.justMatched = null;
1677
1768
  if (kept)
1678
1769
  return kept;
1770
+ return this.styledAgain(node, epoch, parent, found);
1771
+ }
1772
+ /** A node styled now, from the rules found for it or from those it kept: see `matchesStand`. */
1773
+ styledAgain(node, epoch, parent, found) {
1774
+ const cached = node.styleCache;
1775
+ const parentContext = parent ? parent.context : ROOT_CONTEXT;
1679
1776
  const parentInherited = parent ? parent.inherited : EMPTY;
1777
+ const parentScope = parent ? parent.scope : ROOT_SCOPE;
1778
+ const stands = this.matchesStand(cached, node, parentScope);
1779
+ // The scope it keeps, or none: it then has a new one, which its new `context` serves as.
1780
+ const scope = stands ? cached.scope : null;
1680
1781
  // A box the engine made around loose text is in no template, so no rule matches it.
1681
1782
  if (node.anonymous || this.hasNoRules(node)) {
1682
- return this.unstyled(node, epoch, parent, parentContext, parentInherited);
1783
+ return this.unstyled(node, epoch, parent, parentInherited, scope, parentScope);
1683
1784
  }
1684
1785
  styleStats.nodesResolved++;
1685
- const matched = found ?? this.matchedBy(node);
1786
+ // Its own rules are found again for a change of its own, and not for one above it.
1787
+ const rules = stands && !node.ownDirty ? cached.rules : undefined;
1788
+ const matched = found ?? rules ?? this.matchedBy(node);
1686
1789
  const parentTokens = parent ? parent.tokens : this.tokensOnRoot;
1687
1790
  this.heir(node, parent, matched);
1688
1791
  const styled = this.styled(node, matched, parentTokens, parentInherited);
1689
- const cache = this.cached(epoch, parentContext, styled, matched);
1792
+ const cache = this.cached(epoch, parentContext, styled, matched, scope, parentScope);
1690
1793
  node.styleCache = cache;
1691
1794
  node.styleDirty = false;
1795
+ node.ownDirty = false;
1692
1796
  node.hasDirty = false;
1693
1797
  node.stateDirty = false;
1694
1798
  return cache;
1695
1799
  }
1800
+ /**
1801
+ * Whether what an element could match is as it was when it was last styled: nothing that can
1802
+ * change it was marked on the element, its parent's `scope` is the one it was styled under,
1803
+ * and no sheet that could match it has come since. It is being styled again all the same, for
1804
+ * a change of its own that alters nothing under it, or for what it inherits.
1805
+ */
1806
+ matchesStand(cached, node, parentScope) {
1807
+ return (cached !== null &&
1808
+ !node.styleDirty &&
1809
+ !node.hasDirty &&
1810
+ !node.stateDirty &&
1811
+ cached.scope !== SHARED_SCOPE &&
1812
+ parentScope !== SHARED_SCOPE &&
1813
+ cached.parentScope === parentScope &&
1814
+ this.current(cached, node));
1815
+ }
1696
1816
  /**
1697
1817
  * A cache of a node just styled, with a context of its own. It keeps the rules the node
1698
1818
  * matched, for the node to be compared with, once a sheet has a use for them: see `tracksHas`.
1699
1819
  */
1700
- cached(epoch, parentContext, styled, matched) {
1820
+ cached(epoch, parentContext, styled, matched, scope, parentScope) {
1821
+ const context = {};
1701
1822
  const cache = {
1702
1823
  epoch,
1703
1824
  generation: this.generation,
1704
- context: {},
1825
+ context,
1705
1826
  parentContext,
1827
+ scope: scope ?? context,
1828
+ parentScope,
1829
+ rules: matched,
1706
1830
  ...styled,
1707
1831
  };
1708
1832
  if (this.tracksHas)
@@ -1725,6 +1849,7 @@ export class StyleResolver {
1725
1849
  const stands = (node.hasDirty === true || node.stateDirty === true) &&
1726
1850
  cached?.matched !== undefined &&
1727
1851
  !node.styleDirty &&
1852
+ !node.ownDirty &&
1728
1853
  cached.parentContext === parentContext &&
1729
1854
  this.current(cached, node);
1730
1855
  if (!stands)
@@ -1764,6 +1889,7 @@ export class StyleResolver {
1764
1889
  const stands = node.stateDirty === true &&
1765
1890
  cached !== null &&
1766
1891
  !node.styleDirty &&
1892
+ !node.ownDirty &&
1767
1893
  !node.hasDirty &&
1768
1894
  cached.parentContext === parentContext &&
1769
1895
  this.current(cached, node) &&
@@ -1781,6 +1907,14 @@ export class StyleResolver {
1781
1907
  holds(node, parentContext) {
1782
1908
  return this.reusable(node.styleCache, node, parentContext);
1783
1909
  }
1910
+ /**
1911
+ * Whether a node was last styled under a box holding the one cache shared by every box with
1912
+ * nothing to style. Such boxes hand the same `context` down, so the node cannot tell one from
1913
+ * another by it: moved under another box, it has to be told.
1914
+ */
1915
+ styledUnderShared(node) {
1916
+ return node.styleCache !== null && node.styleCache.parentContext === ROOT_CONTEXT;
1917
+ }
1784
1918
  /**
1785
1919
  * Whether a previous result still stands: nothing about the node changed, nothing above it
1786
1920
  * moved, and the conditions have not.
@@ -1788,6 +1922,7 @@ export class StyleResolver {
1788
1922
  reusable(cached, node, parentContext) {
1789
1923
  return (cached !== null &&
1790
1924
  !node.styleDirty &&
1925
+ !node.ownDirty &&
1791
1926
  !node.hasDirty &&
1792
1927
  !node.stateDirty &&
1793
1928
  cached.parentContext === parentContext &&
@@ -1815,35 +1950,43 @@ export class StyleResolver {
1815
1950
  * carry: the shared object holds none, and the surface root answering with it dropped every
1816
1951
  * token the device had seeded.
1817
1952
  */
1818
- unstyled(node, epoch, parent, parentContext, parentInherited) {
1953
+ unstyled(node, epoch, parent, parentInherited, scope, parentScope) {
1819
1954
  const tokens = parent ? parent.tokens : this.tokensOnRoot;
1955
+ const parentContext = parent ? parent.context : ROOT_CONTEXT;
1820
1956
  // What inline style sets is inherited too, with no rule to read it against.
1821
1957
  const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
1822
- if (!inline &&
1958
+ const nothing = !inline &&
1823
1959
  parentInherited === EMPTY &&
1824
1960
  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
- }
1961
+ tokens === NO_TOKENS;
1962
+ if (nothing && !node.styleDirty && !node.ownDirty)
1963
+ return this.sharedEmpty(node, epoch);
1964
+ const context = {};
1833
1965
  const passthrough = {
1834
1966
  epoch,
1835
1967
  generation: this.generation,
1836
- context: {},
1968
+ context,
1837
1969
  parentContext,
1970
+ scope: scope ?? context,
1971
+ parentScope,
1838
1972
  style: unstyledStyle(parentInherited, inline),
1839
1973
  inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
1840
1974
  tokens,
1841
1975
  };
1842
1976
  node.styleCache = passthrough;
1843
1977
  node.styleDirty = false;
1978
+ node.ownDirty = false;
1844
1979
  node.stateDirty = false;
1845
1980
  return passthrough;
1846
1981
  }
1982
+ /** The one cache of every node with nothing to style and nothing to hand down, given to one more. */
1983
+ sharedEmpty(node, epoch) {
1984
+ if (this.emptyCache.epoch !== epoch || this.emptyCache.generation !== this.generation) {
1985
+ this.emptyCache = emptyCacheFor(epoch, this.generation);
1986
+ }
1987
+ node.styleCache = this.emptyCache;
1988
+ return this.emptyCache;
1989
+ }
1847
1990
  /**
1848
1991
  * The rules worth trying against a node, in cascade order.
1849
1992
  *
@@ -2150,6 +2293,17 @@ export class StyleResolver {
2150
2293
  * A declaration with no token and no usable fallback is dropped rather than written as
2151
2294
  * undefined, which is what a browser does with a value it cannot resolve.
2152
2295
  */
2296
+ /**
2297
+ * What declarations that wait for a node's tokens come to on it, where no rule of the node's
2298
+ * carries them: a keyframe's, which is settled against the element that plays it.
2299
+ */
2300
+ settleFor(node, deferred, epoch) {
2301
+ const { tokens } = this.resolve(node, epoch);
2302
+ const above = node.parent ? this.resolve(node.parent, epoch).inherited : EMPTY;
2303
+ const own = {};
2304
+ this.applyDeferred(deferred, own, tokens, above, null);
2305
+ return own;
2306
+ }
2153
2307
  applyDeferred(deferred, own, tokens, parentInherited, important) {
2154
2308
  for (const declaration of settlingOrder(deferred)) {
2155
2309
  if (declaration.inherit) {