@ng-native/fabric 0.3.0 → 0.5.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.
Files changed (41) hide show
  1. package/dist/aria-props.d.ts +16 -0
  2. package/dist/aria-props.js +78 -0
  3. package/dist/aria-props.js.map +1 -0
  4. package/dist/color-mix.js +1 -16
  5. package/dist/color-mix.js.map +1 -1
  6. package/dist/css.d.ts +169 -2
  7. package/dist/css.js +676 -34
  8. package/dist/css.js.map +1 -1
  9. package/dist/element-styles.d.ts +15 -0
  10. package/dist/element-styles.js +103 -0
  11. package/dist/element-styles.js.map +1 -0
  12. package/dist/engine.d.ts +252 -7
  13. package/dist/engine.js +1081 -80
  14. package/dist/engine.js.map +1 -1
  15. package/dist/font-faces.d.ts +5 -2
  16. package/dist/font-faces.js +13 -2
  17. package/dist/font-faces.js.map +1 -1
  18. package/dist/index.d.ts +3 -1
  19. package/dist/index.js +3 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/inline-derived-token.js +11 -10
  22. package/dist/inline-derived-token.js.map +1 -1
  23. package/dist/inline-token.d.ts +7 -0
  24. package/dist/inline-token.js +53 -8
  25. package/dist/inline-token.js.map +1 -1
  26. package/dist/inline-transform.d.ts +5 -1
  27. package/dist/inline-transform.js +34 -6
  28. package/dist/inline-transform.js.map +1 -1
  29. package/dist/native-drive.d.ts +21 -0
  30. package/dist/native-drive.js +60 -0
  31. package/dist/native-drive.js.map +1 -1
  32. package/dist/premultiplied-stops.d.ts +7 -0
  33. package/dist/premultiplied-stops.js +131 -0
  34. package/dist/premultiplied-stops.js.map +1 -0
  35. package/dist/scroll-animation.d.ts +11 -0
  36. package/dist/scroll-animation.js +29 -0
  37. package/dist/scroll-animation.js.map +1 -1
  38. package/dist/transition.d.ts +15 -0
  39. package/dist/transition.js +42 -1
  40. package/dist/transition.js.map +1 -1
  41. package/package.json +1 -1
package/dist/css.js CHANGED
@@ -6,6 +6,7 @@
6
6
  * framework-agnostic and knows nothing about Angular.
7
7
  */
8
8
  import { mixChannels, mixColours, } from "./color-mix.js";
9
+ import { ELEMENT_STYLES } from "./element-styles.js";
9
10
  import { relativeColour } from "./relative-colour.js";
10
11
  /** A run of CSS whitespace: spaces, tabs, newlines, carriage returns and form feeds, and no other. */
11
12
  export const CSS_SPACE = /[ \t\n\r\f]+/;
@@ -57,7 +58,15 @@ const INHERITED = new Set([
57
58
  'textShadowRadius',
58
59
  'textShadowColor',
59
60
  'selectable',
61
+ // CSS inherits it, and its `none` is the element alone: a descendant's `auto` takes touches
62
+ // again. The engine commits a computed `none` as native's `box-none`, which means that.
63
+ 'pointerEvents',
60
64
  ]);
65
+ /**
66
+ * The `pointer-events` values CSS has. Native's own `box-none` and `box-only` say what their
67
+ * children do already, so neither is handed down.
68
+ */
69
+ const INHERITED_POINTER_EVENTS = new Set(['none', 'auto']);
61
70
  /**
62
71
  * `border-style: none`, which the compiler writes as a style native does not have. On the web it
63
72
  * makes every border width 0, whichever rule set the width, so `border-hidden border-x` draws no
@@ -80,6 +89,80 @@ function drawNoBorder(own) {
80
89
  * The cost is one integer increment per comparison, against a Set lookup and several property
81
90
  * reads, so it does not distort what it measures.
82
91
  */
92
+ /**
93
+ * What a sheet asks about a pressed element. `own` is whether a rule styles one, `.button:active`,
94
+ * which matching the pressed elements again answers. `elsewhere` is each compound that asks about
95
+ * a pressed element from another one: `.card:active .title`, `.a:active + .b`, or `:active`
96
+ * inside `:not()`, `:is()` or `:has()`. An element such a compound could be is restyled with
97
+ * everything under it when it is pressed, as what it changes is not itself alone.
98
+ */
99
+ export function usesActive(sheet) {
100
+ const known = ACTIVE_USE.get(sheet);
101
+ if (known)
102
+ return known;
103
+ let own = false;
104
+ const elsewhere = [];
105
+ for (const rule of sheet.rules) {
106
+ rule.compounds.forEach((compound, at) => {
107
+ const subject = at === rule.compounds.length - 1;
108
+ // Asked of the element itself, on the compound or in its `:is()` and `:not()`: the
109
+ // subject's is its own, and any other compound's is of the element that compound is.
110
+ if (activeItself(compound, elsewhere)) {
111
+ if (subject)
112
+ own = true;
113
+ else
114
+ elsewhere.push(compound);
115
+ }
116
+ activeAround(compound, elsewhere);
117
+ });
118
+ }
119
+ const use = { own, elsewhere };
120
+ ACTIVE_USE.set(sheet, use);
121
+ return use;
122
+ }
123
+ const isActive = (compound) => compound.pseudo?.includes('active') === true;
124
+ /**
125
+ * Whether a compound asks about the pressed state of the element it matches: on itself, in a
126
+ * `:not()`, or as the last compound of an `:is()` argument. The compounds before the last in
127
+ * such an argument are other elements, and are collected as they are.
128
+ */
129
+ function activeItself(compound, elsewhere) {
130
+ let itself = isActive(compound);
131
+ for (const inner of compound.not ?? []) {
132
+ if (activeItself(inner, elsewhere))
133
+ itself = true;
134
+ activeAround(inner, elsewhere);
135
+ }
136
+ for (const chain of compound.is ?? []) {
137
+ chain.forEach((inner, at) => {
138
+ const asked = activeItself(inner, elsewhere);
139
+ if (asked && at === chain.length - 1)
140
+ itself = true;
141
+ else if (asked)
142
+ elsewhere.push(inner);
143
+ activeAround(inner, elsewhere);
144
+ });
145
+ }
146
+ return itself;
147
+ }
148
+ /** Each compound that asks about a pressed element around the one a compound matches. */
149
+ function activeAround(compound, elsewhere) {
150
+ const collect = (value) => {
151
+ if (!value || typeof value !== 'object')
152
+ return;
153
+ if (Array.isArray(value))
154
+ return value.forEach(collect);
155
+ if (isActive(value))
156
+ elsewhere.push(value);
157
+ for (const inside of Object.values(value))
158
+ collect(inside);
159
+ };
160
+ for (const [key, inside] of Object.entries(compound)) {
161
+ if (key !== 'is' && key !== 'not' && key !== 'pseudo')
162
+ collect(inside);
163
+ }
164
+ }
165
+ const ACTIVE_USE = new WeakMap();
83
166
  export const styleStats = {
84
167
  /** Calls to `matchesCompound`, the innermost unit of matching work. */
85
168
  compoundTests: 0,
@@ -181,6 +264,21 @@ function matchesCompound(node, compound, sheet) {
181
264
  return false;
182
265
  }
183
266
  }
267
+ if (compound.has) {
268
+ for (const test of compound.has) {
269
+ if (!hasBeneath(node, test, sheet))
270
+ return false;
271
+ }
272
+ }
273
+ if (compound.parents) {
274
+ const parent = node.parent;
275
+ if (!parent)
276
+ return false;
277
+ for (const above of compound.parents) {
278
+ if (!matchesCompound(parent, above, sheet))
279
+ return false;
280
+ }
281
+ }
184
282
  if (compound.ancestors) {
185
283
  for (const ancestor of compound.ancestors) {
186
284
  let found = false;
@@ -203,6 +301,18 @@ function matchesCompound(node, compound, sheet) {
203
301
  }
204
302
  return true;
205
303
  }
304
+ /** Whether some element beneath `node`, or some child of it for `:has(> x)`, matches the test. */
305
+ function hasBeneath(node, test, sheet) {
306
+ for (const child of node.children ?? []) {
307
+ if (child.kind !== undefined && child.kind !== 'element')
308
+ continue;
309
+ if (test.any.some((option) => matchesCompound(child, option, sheet)))
310
+ return true;
311
+ if (!test.child && hasBeneath(child, test, sheet))
312
+ return true;
313
+ }
314
+ return false;
315
+ }
206
316
  /**
207
317
  * Right-to-left matching, as every CSS engine does it: the rightmost compound is the cheapest
208
318
  * filter and rejects almost everything immediately.
@@ -332,6 +442,133 @@ function overriddenBy(deferred, declarations, important) {
332
442
  }
333
443
  return kept.length ? kept : null;
334
444
  }
445
+ /**
446
+ * The logical property and the physical one for the same corner or edge, left to right. CSS
447
+ * settles the two as one property, the later declaration winning. A native view has a prop for
448
+ * each and takes one whatever their order: React Native the physical corner, Yoga the logical
449
+ * edge. So a declaration of either takes the other out of the cascade.
450
+ */
451
+ const SIDES_LTR = [
452
+ ['borderStartStartRadius', 'borderTopLeftRadius'],
453
+ ['borderStartEndRadius', 'borderTopRightRadius'],
454
+ ['borderEndStartRadius', 'borderBottomLeftRadius'],
455
+ ['borderEndEndRadius', 'borderBottomRightRadius'],
456
+ ...['margin', 'padding'].flatMap((box) => [
457
+ [`${box}Start`, `${box}Left`],
458
+ [`${box}End`, `${box}Right`],
459
+ ]),
460
+ ...['Width', 'Color'].flatMap((part) => [
461
+ [`borderStart${part}`, `borderLeft${part}`],
462
+ [`borderEnd${part}`, `borderRight${part}`],
463
+ ]),
464
+ ['start', 'left'],
465
+ ['end', 'right'],
466
+ ];
467
+ const mirrored = (physical) => physical.includes('Left') || physical === 'left'
468
+ ? physical.replace('Left', 'Right').replace('left', 'right')
469
+ : physical.replace('Right', 'Left').replace('right', 'left');
470
+ /** Each property's other form, both ways, in a layout of each direction. */
471
+ const twins = (pairs) => Object.fromEntries(pairs.flatMap(([logical, physical]) => [
472
+ [logical, physical],
473
+ [physical, logical],
474
+ ]));
475
+ const TWIN = {
476
+ ltr: twins(SIDES_LTR),
477
+ rtl: twins(SIDES_LTR.map(([logical, physical]) => [logical, mirrored(physical)])),
478
+ };
479
+ const sidedOf = new WeakMap();
480
+ function sided(rule) {
481
+ let found = sidedOf.get(rule);
482
+ if (found !== undefined)
483
+ return found;
484
+ const twin = TWIN.ltr;
485
+ const plain = Object.keys(rule.declarations).filter((key) => key in twin);
486
+ const important = rule.important ? Object.keys(rule.important).filter((key) => key in twin) : [];
487
+ for (const declaration of rule.deferred ?? []) {
488
+ for (const prop of declaration.props) {
489
+ if (prop in twin)
490
+ (declaration.important ? important : plain).push(prop);
491
+ }
492
+ }
493
+ found = plain.length || important.length ? { plain, important } : null;
494
+ sidedOf.set(rule, found);
495
+ return found;
496
+ }
497
+ /** Of `keys`, in the order a rule wrote them, each whose other form comes after it. */
498
+ function writtenEarlier(keys, twin) {
499
+ return keys.filter((key, at) => keys.indexOf(twin[key]) > at);
500
+ }
501
+ /** The other form of each of `keys`, as the record `overriddenBy` reads. */
502
+ function twinsOf(keys, twin) {
503
+ const out = {};
504
+ for (const key of keys)
505
+ out[twin[key]] = true;
506
+ return out;
507
+ }
508
+ /**
509
+ * One cascade's settling of the corners and edges set in both forms: each rule's are taken out of
510
+ * what the rules before it left, in the form the rule does not use.
511
+ */
512
+ class OtherForms {
513
+ /** Worked out for the first rule that sets a corner or an edge, which most elements have none of. */
514
+ twin = null;
515
+ overImportant = null;
516
+ direction;
517
+ constructor(direction) {
518
+ this.direction = direction;
519
+ }
520
+ /** Before `rule` is applied: the other form of what it sets goes, as the same property would. */
521
+ before(rule, normal, important, deferred) {
522
+ const sides = sided(rule);
523
+ if (!sides)
524
+ return deferred;
525
+ const twin = (this.twin ??= TWIN[this.direction()]);
526
+ const plain = twinsOf(sides.plain, twin);
527
+ for (const key in plain)
528
+ delete normal[key];
529
+ let kept = deferred && overriddenBy(deferred, plain, false);
530
+ if (!sides.important.length)
531
+ return kept;
532
+ const gone = twinsOf(sides.important, twin);
533
+ for (const key in gone)
534
+ delete important[key];
535
+ kept &&= overriddenBy(kept, gone, true);
536
+ Object.assign((this.overImportant ??= {}), gone);
537
+ return kept;
538
+ }
539
+ /**
540
+ * Once `rule` is applied, its deferred values among `deferred`: where it wrote both forms, the
541
+ * one it wrote later stands, among its plain declarations and among its important ones. The
542
+ * order is the rule's own, sent with it, since a value settled on device is in another list.
543
+ */
544
+ within(rule, normal, important, deferred) {
545
+ if (!rule.sides && !rule.importantSides)
546
+ return deferred;
547
+ const twin = (this.twin ??= TWIN[this.direction()]);
548
+ let kept = deferred;
549
+ const settle = (order, into, tier) => {
550
+ const lost = {};
551
+ for (const key of writtenEarlier(order, twin)) {
552
+ delete into[key];
553
+ lost[key] = true;
554
+ }
555
+ kept &&= overriddenBy(kept, lost, tier);
556
+ };
557
+ if (rule.sides)
558
+ settle(rule.sides, normal, false);
559
+ if (rule.importantSides)
560
+ settle(rule.importantSides, important, true);
561
+ return kept;
562
+ }
563
+ /** An important one stands over the other form wherever that was set, as over its own. */
564
+ after(normal, deferred) {
565
+ if (!this.overImportant)
566
+ return deferred;
567
+ for (const key in this.overImportant)
568
+ delete normal[key];
569
+ return deferred && overriddenBy(deferred, this.overImportant, false);
570
+ }
571
+ }
335
572
  /**
336
573
  * The deferred values still standing once `rule` has been applied, plus its own.
337
574
  *
@@ -353,6 +590,44 @@ function carryDeferred(deferred, rule) {
353
590
  (kept ??= []).push(...rule.deferred);
354
591
  return kept;
355
592
  }
593
+ const NO_DECLARATIONS = Object.freeze({});
594
+ const pick = (from, names) => Object.fromEntries(names.map((name) => [name, from[name]]));
595
+ const isImportant = (rule) => rule.important !== undefined ||
596
+ rule.importantTokens !== undefined ||
597
+ rule.deferred?.some((one) => one.important) === true;
598
+ /**
599
+ * The matched rules in the order their declarations apply, weakest first.
600
+ *
601
+ * That is the order they matched in, unless a layered rule is important. Layers turn round for
602
+ * `!important`: an important declaration in a layer beats one in a later layer, and both beat one
603
+ * in no layer. So each such rule is split, its plain declarations where it matched, and its
604
+ * important ones after every plain one, in the layers' order reversed.
605
+ */
606
+ function byImportance(matched, places) {
607
+ if (!matched.some((rule) => rule.layer !== undefined && isImportant(rule)))
608
+ return matched;
609
+ const plain = matched.map((rule) => isImportant(rule)
610
+ ? {
611
+ ...rule,
612
+ important: undefined,
613
+ importantTokens: undefined,
614
+ deferred: rule.deferred?.filter((one) => !one.important),
615
+ }
616
+ : rule);
617
+ const important = matched
618
+ .filter(isImportant)
619
+ // Stable, so rules of one layer keep the order their specificity gave them. Sorted before
620
+ // they are copied, since a rule's place is kept by the rule itself.
621
+ .sort((a, b) => layerOrder(places.get(b), places.get(a)))
622
+ .map((rule) => ({
623
+ ...rule,
624
+ declarations: NO_DECLARATIONS,
625
+ // Its important custom properties alone: the plain ones were applied where it matched.
626
+ tokens: rule.importantTokens && pick(rule.tokens, rule.importantTokens),
627
+ deferred: rule.deferred?.filter((one) => one.important),
628
+ }));
629
+ return [...plain, ...important];
630
+ }
356
631
  function countPosition(node, siblings, fromEnd) {
357
632
  let position = 0;
358
633
  let seen = 0;
@@ -388,6 +663,35 @@ function lastElement(siblings) {
388
663
  * precedence people already have in their fingers rather than inventing a rule for native.
389
664
  */
390
665
  const COMPONENT_SPECIFICITY_BUMP = 1_000;
666
+ /**
667
+ * Two rules' layers, weakest first: a layer before each one named after it, a layer's nested
668
+ * layers before its own rules, and every layered rule before a rule in no layer.
669
+ */
670
+ function layerOrder(a, b) {
671
+ if (a === b)
672
+ return 0;
673
+ if (a === undefined)
674
+ return 1;
675
+ if (b === undefined)
676
+ return -1;
677
+ const shared = Math.min(a.length, b.length);
678
+ for (let depth = 0; depth < shared; depth++) {
679
+ if (a[depth] !== b[depth])
680
+ return a[depth] - b[depth];
681
+ }
682
+ // One is nested in the other, whose own rules are the stronger.
683
+ return b.length - a.length;
684
+ }
685
+ /**
686
+ * The engine's own styles for HTML's text elements, as rules to match before any a stylesheet
687
+ * holds, so every rule an author writes beats them whatever its specificity, as a browser's own
688
+ * sheet is beaten. Offered only to a node marked `styled`, which few are.
689
+ */
690
+ const ELEMENT_ENTRIES = ELEMENT_STYLES.rules.map((rule) => ({
691
+ rule,
692
+ sheet: ELEMENT_STYLES,
693
+ weight: rule.specificity,
694
+ }));
391
695
  /**
392
696
  * The bucket a rule is filed under: the most selective simple selector in its rightmost compound,
393
697
  * which is what a browser calls the key selector.
@@ -499,12 +803,82 @@ function inheritFrom(parentInherited, own) {
499
803
  for (const key of Object.keys(own)) {
500
804
  if (!INHERITED.has(key))
501
805
  continue;
806
+ // `pointer-events: inherit`, which only an inline style on an element no rule matches still
807
+ // holds here: what the parent hands down stands.
808
+ if (key === 'pointerEvents' && own[key] === 'inherit')
809
+ continue;
502
810
  if (inherited === parentInherited)
503
811
  inherited = { ...parentInherited };
504
- inherited[key] = own[key];
812
+ if (key === 'pointerEvents' && !INHERITED_POINTER_EVENTS.has(own[key])) {
813
+ delete inherited[key];
814
+ }
815
+ else {
816
+ inherited[key] = own[key];
817
+ }
505
818
  }
506
819
  return inherited;
507
820
  }
821
+ /**
822
+ * A text decoration's colour, which travels with its line rather than inheriting on its own. A
823
+ * decoration is drawn under the text inside the element that declares it, in that element's
824
+ * decoration colour or its text colour, and a text's own decoration colour only colours a line it
825
+ * declares itself. `style` is the node's, and gets the colour its line is drawn in; what its
826
+ * children inherit is returned.
827
+ */
828
+ function decorate(style, inherited, own) {
829
+ const declared = own['textDecorationLine'] !== undefined;
830
+ // With no line declared here or above, nothing is drawn and the colour is left as computed.
831
+ if (!declared && !drawsLine(inherited['textDecorationLine']))
832
+ return inherited;
833
+ const colour = declared
834
+ ? (own['textDecorationColor'] ?? style['color'])
835
+ : inherited['textDecorationColor'];
836
+ if (colour === undefined)
837
+ delete style['textDecorationColor'];
838
+ else
839
+ style['textDecorationColor'] = colour;
840
+ if (!declared || inherited['textDecorationColor'] === colour)
841
+ return inherited;
842
+ const { textDecorationColor: _parent, ...rest } = inherited;
843
+ return colour === undefined ? rest : { ...rest, textDecorationColor: colour };
844
+ }
845
+ function drawsLine(line) {
846
+ return line !== undefined && line !== 'none';
847
+ }
848
+ /** Whether an inline style sets a property children inherit. Allocates nothing: it runs per set. */
849
+ export function setsInherited(style) {
850
+ if (!style || typeof style !== 'object')
851
+ return false;
852
+ if (Array.isArray(style))
853
+ return style.some(setsInherited);
854
+ for (const key in style) {
855
+ if (INHERITED.has(key) && style[key] != null)
856
+ return true;
857
+ }
858
+ return false;
859
+ }
860
+ /**
861
+ * The inherited properties a node's inline style sets, or null for none. Inline style is the
862
+ * strongest normal declaration on its element, so what it sets is inherited, and read by
863
+ * `color: inherit` and `currentColor`, as a rule's would be.
864
+ */
865
+ export function inlineInherited(style) {
866
+ let out = null;
867
+ // Flattened first, as native applies it, so a later entry's null clears an earlier value.
868
+ for (const [key, value] of Object.entries(flattenInline(style, {}))) {
869
+ if (INHERITED.has(key) && value !== undefined && value !== null)
870
+ (out ??= {})[key] = value;
871
+ }
872
+ return out;
873
+ }
874
+ function flattenInline(style, into) {
875
+ if (Array.isArray(style))
876
+ for (const entry of style)
877
+ flattenInline(entry, into);
878
+ else if (style && typeof style === 'object')
879
+ Object.assign(into, style);
880
+ return into;
881
+ }
508
882
  /**
509
883
  * The runtime half of CSS for one engine: the global sheet, the conditions media queries are
510
884
  * evaluated against, and the memo that makes resolution cost nodes x rules rather than
@@ -542,6 +916,21 @@ export class StyleResolver {
542
916
  * single index.
543
917
  */
544
918
  merged = new WeakMap();
919
+ /**
920
+ * Whether any sheet merged so far has a rule in a cascade layer. Until one does, rules are
921
+ * ordered by weight alone and the cascade does not look for layers, which is nearly every app.
922
+ * It stays set once a layered sheet has gone, which costs a look at each cascade and no more.
923
+ */
924
+ layered = false;
925
+ /**
926
+ * Every layer the sheets seen so far have named, in the order each name was first seen, which
927
+ * is the one order a document has for them. A name keeps its place when its sheet is replaced.
928
+ */
929
+ layerNames = { place: 0, nested: new Map() };
930
+ /** Each layered rule's place among them, set when its sheet is first merged. */
931
+ layerPlaces = new WeakMap();
932
+ layeredSheets = new WeakSet();
933
+ unnamedLayers = 0;
545
934
  /**
546
935
  * The application-level sheet, if any: the one set of rules allowed to match a node whatever
547
936
  * component created it, which is what resets, utility classes and `:root` tokens all need.
@@ -640,12 +1029,17 @@ export class StyleResolver {
640
1029
  cached.epoch = epoch;
641
1030
  return cached;
642
1031
  }
1032
+ const kept = this.kept(node, parentContext, epoch);
1033
+ if (kept)
1034
+ return kept;
643
1035
  const parentInherited = parent ? parent.inherited : EMPTY;
644
- if (this.hasNoRules(node)) {
1036
+ // A box the engine made around loose text is in no template, so no rule matches it.
1037
+ if (node.anonymous || this.hasNoRules(node)) {
645
1038
  return this.unstyled(node, epoch, parent, parentContext, parentInherited);
646
1039
  }
647
1040
  styleStats.nodesResolved++;
648
- const matched = this.matched(node, this.rulesFor(node));
1041
+ const candidates = this.rulesFor(node);
1042
+ const matched = this.matched(node, node.styled ? ELEMENT_ENTRIES.concat(candidates) : candidates);
649
1043
  const parentTokens = parent ? parent.tokens : this.tokensOnRoot;
650
1044
  const styled = this.styled(node, matched, parentTokens, parentInherited);
651
1045
  const cache = {
@@ -654,11 +1048,70 @@ export class StyleResolver {
654
1048
  context: {},
655
1049
  parentContext,
656
1050
  ...styled,
1051
+ ...this.record(matched),
657
1052
  };
658
1053
  node.styleCache = cache;
659
1054
  node.styleDirty = false;
1055
+ node.hasDirty = false;
1056
+ node.stateDirty = false;
660
1057
  return cache;
661
1058
  }
1059
+ /** The rules a node matched, for its cache to keep, while a sheet uses `:has()`. */
1060
+ record(matched) {
1061
+ return this.tracksHas ? { matched } : {};
1062
+ }
1063
+ /**
1064
+ * Set by the engine once a sheet uses `:has()`. Until then no cache keeps the rules it matched,
1065
+ * so an app that never asks pays nothing for it.
1066
+ */
1067
+ tracksHas = false;
1068
+ /**
1069
+ * The cache of a node whose subtree changed, and nothing else, when it matches the rules it
1070
+ * did: keeping the object is what leaves everything under it alone. Nothing when it has to be
1071
+ * resolved, as it does with no record of what it matched, made before any sheet asked.
1072
+ */
1073
+ keptBeneath(node, parentContext, epoch) {
1074
+ const cached = node.styleCache;
1075
+ const stands = (node.hasDirty === true || node.stateDirty === true) &&
1076
+ cached?.matched !== undefined &&
1077
+ !node.styleDirty &&
1078
+ cached.parentContext === parentContext &&
1079
+ cached.generation === this.generation;
1080
+ if (!stands)
1081
+ return null;
1082
+ const candidates = this.rulesFor(node);
1083
+ const now = this.matched(node, node.styled ? ELEMENT_ENTRIES.concat(candidates) : candidates);
1084
+ const before = cached.matched;
1085
+ if (now.length !== before.length || now.some((rule, i) => rule !== before[i]))
1086
+ return null;
1087
+ node.hasDirty = false;
1088
+ node.stateDirty = false;
1089
+ cached.epoch = epoch;
1090
+ return cached;
1091
+ }
1092
+ /** A cache that still stands for a node marked for its subtree or its state alone. */
1093
+ kept(node, parentContext, epoch) {
1094
+ return (this.keptBeneath(node, parentContext, epoch) ?? this.keptUnmatched(node, parentContext, epoch));
1095
+ }
1096
+ /**
1097
+ * The cache of a node whose pressed state changed and nothing else, where no rule can match
1098
+ * it at all: it has no style of its own to change, so it and all under it stay as they are.
1099
+ */
1100
+ keptUnmatched(node, parentContext, epoch) {
1101
+ const cached = node.styleCache;
1102
+ const stands = node.stateDirty === true &&
1103
+ cached !== null &&
1104
+ !node.styleDirty &&
1105
+ !node.hasDirty &&
1106
+ cached.parentContext === parentContext &&
1107
+ cached.generation === this.generation &&
1108
+ (node.anonymous === true || this.hasNoRules(node));
1109
+ if (!stands)
1110
+ return null;
1111
+ node.stateDirty = false;
1112
+ cached.epoch = epoch;
1113
+ return cached;
1114
+ }
662
1115
  /**
663
1116
  * Whether a node's last answer still stands under a parent that resolved to `parentContext`,
664
1117
  * without resolving it. What lets a commit step over a clean child in one comparison.
@@ -673,6 +1126,8 @@ export class StyleResolver {
673
1126
  reusable(cached, node, parentContext) {
674
1127
  return (cached !== null &&
675
1128
  !node.styleDirty &&
1129
+ !node.hasDirty &&
1130
+ !node.stateDirty &&
676
1131
  cached.parentContext === parentContext &&
677
1132
  cached.generation === this.generation);
678
1133
  }
@@ -685,7 +1140,9 @@ export class StyleResolver {
685
1140
  node.hostSheet === null &&
686
1141
  this.globalSheet === null &&
687
1142
  this.addedSheets.length === 0 &&
688
- !node.customProperties);
1143
+ !node.customProperties &&
1144
+ !node.boundStyle &&
1145
+ !node.styled);
689
1146
  }
690
1147
  /**
691
1148
  * A node with no rules anywhere: it renders with what it inherits and passes the same map on.
@@ -698,7 +1155,10 @@ export class StyleResolver {
698
1155
  */
699
1156
  unstyled(node, epoch, parent, parentContext, parentInherited) {
700
1157
  const tokens = parent ? parent.tokens : this.tokensOnRoot;
701
- if (parentInherited === EMPTY &&
1158
+ // What inline style sets is inherited too, with no rule to read it against.
1159
+ const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
1160
+ if (!inline &&
1161
+ parentInherited === EMPTY &&
702
1162
  parentContext === ROOT_CONTEXT &&
703
1163
  tokens === NO_TOKENS &&
704
1164
  !node.styleDirty) {
@@ -714,7 +1174,7 @@ export class StyleResolver {
714
1174
  context: {},
715
1175
  parentContext,
716
1176
  style: parentInherited,
717
- inherited: parentInherited,
1177
+ inherited: inline ? inheritFrom(parentInherited, inline) : parentInherited,
718
1178
  tokens,
719
1179
  };
720
1180
  node.styleCache = passthrough;
@@ -756,6 +1216,7 @@ export class StyleResolver {
756
1216
  const add = (from, bump) => {
757
1217
  if (!from)
758
1218
  return;
1219
+ this.placeLayers(from);
759
1220
  for (const rule of from.rules) {
760
1221
  entries.push({ rule, sheet: from, weight: rule.specificity + bump });
761
1222
  }
@@ -765,7 +1226,41 @@ export class StyleResolver {
765
1226
  add(sheet, COMPONENT_SPECIFICITY_BUMP);
766
1227
  for (const added of this.addedSheets)
767
1228
  add(added, 0);
768
- return entries.sort((a, b) => a.weight - b.weight);
1229
+ if (!entries.some((entry) => entry.rule.layer !== undefined)) {
1230
+ return entries.sort((a, b) => a.weight - b.weight);
1231
+ }
1232
+ this.layered = true;
1233
+ const places = this.layerPlaces;
1234
+ return entries.sort((a, b) => layerOrder(places.get(a.rule), places.get(b.rule)) || a.weight - b.weight);
1235
+ }
1236
+ /**
1237
+ * Gives each of a sheet's layers its place among every sheet's, the first time the sheet is
1238
+ * merged, and each of its layered rules that place. Sheets are merged in the order a document
1239
+ * holds them: the app's global sheet, then each component's as it first renders.
1240
+ */
1241
+ placeLayers(sheet) {
1242
+ if (!sheet.layers || this.layeredSheets.has(sheet))
1243
+ return;
1244
+ this.layeredSheets.add(sheet);
1245
+ // A block with no name is a layer of this sheet's alone.
1246
+ const unnamed = `\0${this.unnamedLayers++}`;
1247
+ const places = sheet.layers.map((path) => {
1248
+ const place = [];
1249
+ let names = this.layerNames;
1250
+ for (const part of path) {
1251
+ const name = part.startsWith('\0') ? unnamed + part : part;
1252
+ let nested = names.nested.get(name);
1253
+ if (!nested)
1254
+ names.nested.set(name, (nested = { place: names.nested.size, nested: new Map() }));
1255
+ place.push(nested.place);
1256
+ names = nested;
1257
+ }
1258
+ return place;
1259
+ });
1260
+ for (const rule of sheet.rules) {
1261
+ if (rule.layer !== undefined)
1262
+ this.layerPlaces.set(rule, places[rule.layer]);
1263
+ }
769
1264
  }
770
1265
  /**
771
1266
  * A node's style from the rules it matched, shared with every other node that matched the same
@@ -777,8 +1272,11 @@ export class StyleResolver {
777
1272
  * written to after, so sharing it is safe; each node still has a cache and a context of its own.
778
1273
  */
779
1274
  styled(node, matched, parentTokens, parentInherited) {
780
- if (node.customProperties)
781
- return this.style(node, matched, parentTokens, parentInherited);
1275
+ // Shared between nodes that match alike, unless the node sets something of its own.
1276
+ const inline = node.inlineInherits ? inlineInherited(node.props['style']) : null;
1277
+ if (node.customProperties || inline || node.boundStyle) {
1278
+ return this.style(node, matched, parentTokens, parentInherited, inline);
1279
+ }
782
1280
  if (this.sharedGeneration !== this.generation) {
783
1281
  this.shared.clear();
784
1282
  this.sharedGeneration = this.generation;
@@ -808,8 +1306,15 @@ export class StyleResolver {
808
1306
  return id;
809
1307
  }
810
1308
  /** A node's style from the rules it matched, worked out. */
811
- style(node, matched, parentTokens, parentInherited) {
812
- const result = this.cascade(matched);
1309
+ style(node, matched, parentTokens, parentInherited, inline = null) {
1310
+ // Inline style is the last normal declaration, under every important one.
1311
+ const bound = node.boundStyle;
1312
+ const result = this.cascade(inline || bound
1313
+ ? [
1314
+ ...matched,
1315
+ { declarations: inline ?? EMPTY, ...(bound && { deferred: bound }) },
1316
+ ]
1317
+ : matched, parentInherited);
813
1318
  // Tokens are in scope for this node's own declarations as well as its descendants', so they
814
1319
  // are merged before any `var()` here is resolved.
815
1320
  const tokens = tokensInScope(parentTokens, result.tokens, node.customProperties);
@@ -819,11 +1324,11 @@ export class StyleResolver {
819
1324
  }
820
1325
  if (own['borderStyle'] === 'none')
821
1326
  drawNoBorder(own);
822
- return {
823
- style: { ...parentInherited, ...own },
824
- inherited: inheritFrom(parentInherited, own),
825
- tokens,
826
- };
1327
+ // `pointer-events: inherit` won the cascade: what the parent hands down stands.
1328
+ if (own['pointerEvents'] === 'inherit')
1329
+ delete own['pointerEvents'];
1330
+ const style = { ...parentInherited, ...own };
1331
+ return { style, inherited: decorate(style, inheritFrom(parentInherited, own), own), tokens };
827
1332
  }
828
1333
  /** The rules that apply to a node, weakest first. */
829
1334
  matched(node, entries) {
@@ -835,13 +1340,16 @@ export class StyleResolver {
835
1340
  return out;
836
1341
  }
837
1342
  /** The declarations of the rules a node matched. The list is sorted, so later simply wins. */
838
- cascade(matched) {
1343
+ cascade(matched, parentInherited) {
839
1344
  const normal = {};
840
1345
  const important = {};
841
1346
  let hasImportant = false;
842
1347
  let tokens = null;
1348
+ let importantTokens = null;
843
1349
  let deferred = null;
844
- for (const rule of matched) {
1350
+ const forms = new OtherForms(() => this.directionOf(matched, parentInherited));
1351
+ for (const rule of this.layered ? byImportance(matched, this.layerPlaces) : matched) {
1352
+ deferred = forms.before(rule, normal, important, deferred);
845
1353
  Object.assign(normal, rule.declarations);
846
1354
  if (rule.important) {
847
1355
  Object.assign(important, rule.important);
@@ -849,8 +1357,15 @@ export class StyleResolver {
849
1357
  }
850
1358
  if (rule.tokens)
851
1359
  Object.assign((tokens ??= {}), rule.tokens);
852
- deferred = carryDeferred(deferred, rule);
1360
+ // An important custom property beats every plain one, whatever the rules' order.
1361
+ if (rule.importantTokens) {
1362
+ Object.assign((importantTokens ??= {}), pick(rule.tokens, rule.importantTokens));
1363
+ }
1364
+ deferred = forms.within(rule, normal, important, carryDeferred(deferred, rule));
853
1365
  }
1366
+ if (importantTokens)
1367
+ Object.assign(tokens, importantTokens);
1368
+ deferred = forms.after(normal, deferred);
854
1369
  return {
855
1370
  declarations: hasImportant ? { ...normal, ...important } : normal,
856
1371
  important: hasImportant ? important : null,
@@ -858,6 +1373,37 @@ export class StyleResolver {
858
1373
  deferred,
859
1374
  };
860
1375
  }
1376
+ /**
1377
+ * An inline style over the style the cascade settled, both already in `style`: a corner or an
1378
+ * edge the inline style sets takes the other form of it out, as a later rule would.
1379
+ */
1380
+ overOtherForms(inline, style) {
1381
+ let twin = null;
1382
+ const written = Object.keys(inline);
1383
+ for (const [at, key] of written.entries()) {
1384
+ if (!(key in TWIN.ltr))
1385
+ continue;
1386
+ twin ??= TWIN[(style['direction'] ?? this.conditions.direction) === 'rtl' ? 'rtl' : 'ltr'];
1387
+ const other = twin[key];
1388
+ // Where the inline style sets both, the one it set later stands.
1389
+ if (written.indexOf(other) < at)
1390
+ delete style[other];
1391
+ }
1392
+ }
1393
+ /**
1394
+ * The direction an element is laid out in: the last `direction` its own rules give it, the one
1395
+ * it inherits, or the app's. A logical corner is a different physical one in each.
1396
+ */
1397
+ directionOf(matched, parentInherited) {
1398
+ let plain;
1399
+ let important;
1400
+ for (const rule of matched) {
1401
+ plain = rule.declarations['direction'] ?? plain;
1402
+ important = rule.important?.['direction'] ?? important;
1403
+ }
1404
+ const direction = important ?? plain ?? parentInherited['direction'] ?? this.conditions.direction;
1405
+ return direction === 'rtl' ? 'rtl' : 'ltr';
1406
+ }
861
1407
  /**
862
1408
  * Whether a rule's media query holds, memoised per rule.
863
1409
  *
@@ -895,7 +1441,9 @@ export class StyleResolver {
895
1441
  }
896
1442
  /** What the device answers for a feature that is a keyword rather than a measurement. */
897
1443
  preference(feature) {
898
- const { width, height, colorScheme, reducedMotion } = this.conditions;
1444
+ const { width, height, colorScheme, reducedMotion, direction } = this.conditions;
1445
+ if (feature === 'direction')
1446
+ return direction ?? 'ltr';
899
1447
  if (feature === 'orientation')
900
1448
  return width > height ? 'landscape' : 'portrait';
901
1449
  if (feature === 'prefers-color-scheme')
@@ -969,6 +1517,14 @@ export class StyleResolver {
969
1517
  const values = lineValues(line, tokens);
970
1518
  if (!values)
971
1519
  return undefined;
1520
+ if (line.colour !== undefined) {
1521
+ // A colour that cannot be worked out makes the whole line invalid, as an unset token does.
1522
+ const colour = this.settledWithin(line.colour, declaration, own, parentInherited, tokens);
1523
+ if (colour === undefined)
1524
+ return undefined;
1525
+ for (const prop of line.colors)
1526
+ values[prop] = colour;
1527
+ }
972
1528
  // `currentcolor` is the colour in scope, which only the node knows.
973
1529
  if (values[line.colors[0]] === 'currentcolor') {
974
1530
  const colour = own['color'] ?? parentInherited['color'] ?? 'black';
@@ -993,7 +1549,7 @@ export class StyleResolver {
993
1549
  return;
994
1550
  if (declaration.alternatives?.some((alternative) => alternative in tokens))
995
1551
  return;
996
- this.onUndefinedToken(name, declaration.props);
1552
+ this.onUndefinedToken(name, declaration.props, declaration.on);
997
1553
  }
998
1554
  /** A display token set to a value native has no display for. */
999
1555
  reportDisplay(declaration, tokens) {
@@ -1022,13 +1578,15 @@ export class StyleResolver {
1022
1578
  const fill = (part) => this.filledIn(part, declaration, own, parentInherited, tokens);
1023
1579
  if (Array.isArray(value))
1024
1580
  return this.filledList(value, fill, tokens);
1025
- const marked = settledMarker(value, tokens);
1026
1581
  // `currentcolor` is the colour in scope, which only the node knows: Tailwind's ring default.
1027
1582
  // On `color` itself that is the inherited colour, as CSS reads it.
1028
- if (marked === 'currentcolor') {
1583
+ const current = () => {
1029
1584
  const ownColour = declaration.props.includes('color') ? undefined : own['color'];
1030
- return ownColour ?? parentInherited['color'] ?? 'black';
1031
- }
1585
+ return (ownColour ?? parentInherited['color'] ?? 'black');
1586
+ };
1587
+ const marked = settledMarker(value, tokens, current);
1588
+ if (marked === 'currentcolor')
1589
+ return current();
1032
1590
  if (marked !== NOT_A_MARKER)
1033
1591
  return marked ?? UNSETTLED;
1034
1592
  const pending = value.__defer;
@@ -1531,6 +2089,75 @@ function tokenForm(token, kind) {
1531
2089
  const value = formOf(token, kind);
1532
2090
  return value === undefined && kind === 'color' && isCurrentColour(token) ? CURRENT_COLOUR : value;
1533
2091
  }
2092
+ const BOUND_NUMBERS = new Set([
2093
+ 'opacity',
2094
+ 'flex',
2095
+ 'flexGrow',
2096
+ 'flexShrink',
2097
+ 'zIndex',
2098
+ 'aspectRatio',
2099
+ 'elevation',
2100
+ ]);
2101
+ const BOUND_LENGTH = /^(?:(?:min|max)?(?:width|height)|top|right|bottom|left|start|end|gap|fontSize|letterSpacing)$|(?:margin|padding|inset|Width|Radius|Gap|Basis)/i;
2102
+ /** The properties whose token is read in a form of its own. */
2103
+ const BOUND_KINDS = {
2104
+ display: 'display',
2105
+ transform: 'transform',
2106
+ boxShadow: 'shadow',
2107
+ filter: 'filter',
2108
+ };
2109
+ /** What a token is read as for a bound declaration of `prop`, as the compiler says for a rule. */
2110
+ function boundKind(prop) {
2111
+ // `fill` and `stroke` are a shape's paint, which is a colour.
2112
+ if (/color$/i.test(prop) || prop === 'fill' || prop === 'stroke')
2113
+ return 'color';
2114
+ if (prop === 'fontWeight')
2115
+ return 'weight';
2116
+ if (prop === 'fontFamily')
2117
+ return 'family';
2118
+ if (prop === 'lineHeight')
2119
+ return 'lineHeight';
2120
+ if (prop in BOUND_KINDS)
2121
+ return BOUND_KINDS[prop];
2122
+ if (BOUND_NUMBERS.has(prop))
2123
+ return 'number';
2124
+ return BOUND_LENGTH.test(prop) ? 'length' : 'keyword';
2125
+ }
2126
+ /**
2127
+ * A bound declaration whose value is one `var()`, `var(--brand, red)`, as the deferred declaration
2128
+ * the compiler writes for the same value in a rule. `token` is the value as a token, which is an
2129
+ * alias when it is a `var()`; null for any other.
2130
+ *
2131
+ * ponytail: the whole value has to be the `var()`. One inside a longer value, `calc()` included,
2132
+ * is what the compiler parses at build time and nothing here does; write the sum in a custom
2133
+ * property, `[style.--gap]`, and read that in the stylesheet.
2134
+ */
2135
+ export function boundDeclaration(prop, token, on) {
2136
+ if (!token?.alias)
2137
+ return null;
2138
+ const kind = boundKind(prop);
2139
+ const [reference, ...alternatives] = aliasesOf(token);
2140
+ for (; token?.alias; token = token.fallback)
2141
+ ;
2142
+ // `tokenForm`, so a fallback of `currentColor` is the colour of the element, as in a rule.
2143
+ const fallback = token && !isDerived(token) ? tokenForm(token, kind) : undefined;
2144
+ return {
2145
+ props: [prop],
2146
+ ...(on === undefined ? {} : { on }),
2147
+ kind,
2148
+ reference: reference,
2149
+ ...(alternatives.length ? { alternatives } : {}),
2150
+ ...(fallback !== undefined ? { fallback } : {}),
2151
+ ...(token && isDerived(token) ? { fallbackToken: token } : {}),
2152
+ };
2153
+ }
2154
+ /** The names a `var()` tries in turn: its own, then each `var()` nested as its fallback. */
2155
+ function aliasesOf(token) {
2156
+ const names = [];
2157
+ for (; token?.alias; token = token.fallback)
2158
+ names.push(token.alias);
2159
+ return names;
2160
+ }
1534
2161
  /** The fallback written, or one made of other tokens, worked out from the tokens where it is used. */
1535
2162
  function fallbackOf(declaration, tokens) {
1536
2163
  const token = declaration.fallbackToken;
@@ -1744,8 +2371,11 @@ function placed(position, tokens) {
1744
2371
  /**
1745
2372
  * A colour expression with its tokens looked up and any mix in it worked out. Undefined when a
1746
2373
  * token it needs is not defined anywhere and has nothing to fall back to.
2374
+ *
2375
+ * `current` is the node's colour, for a `currentColor` inside a mix or a relative colour. One that
2376
+ * is the whole expression is answered as `currentcolor`, for the caller to fill in.
1747
2377
  */
1748
- function resolveColour(expression, tokens) {
2378
+ function resolveColour(expression, tokens, current) {
1749
2379
  if ('color' in expression)
1750
2380
  return expression.color;
1751
2381
  if ('channels' in expression)
@@ -1756,15 +2386,27 @@ function resolveColour(expression, tokens) {
1756
2386
  return tokenColour(expression, tokens);
1757
2387
  if ('relative' in expression) {
1758
2388
  const { space, from, channels, alpha } = expression.relative;
1759
- const origin = resolveColour(from, tokens);
2389
+ const origin = innerColour(from, tokens, current);
1760
2390
  return origin === undefined ? undefined : relativeColour(space, origin, channels, alpha);
1761
2391
  }
1762
- const sides = mixSides(expression.mix, tokens);
2392
+ const sides = mixSides(expression.mix, tokens, current);
1763
2393
  return sides && mixColours(expression.mix.space, ...sides, expression.mix.hue);
1764
2394
  }
2395
+ /** A colour inside another: `currentColor` there, written or in a token, is the node's colour. */
2396
+ function innerColour(expression, tokens, current) {
2397
+ if (!current)
2398
+ return resolveColour(expression, tokens);
2399
+ if ('reference' in expression) {
2400
+ const names = [expression.reference, ...(expression.alternatives ?? [])];
2401
+ if (isCurrentColour(firstSet(names, tokens)))
2402
+ return current();
2403
+ }
2404
+ const colour = resolveColour(expression, tokens, current);
2405
+ return colour?.toLowerCase() === 'currentcolor' ? current() : colour;
2406
+ }
1765
2407
  /** A mix's two sides, a mix inside it unrounded, as CSS rounds only the colour at the end. */
1766
- function mixSides(mix, tokens) {
1767
- const colour = (side) => 'mix' in side ? unroundedMix(side.mix, tokens) : resolveColour(side, tokens);
2408
+ function mixSides(mix, tokens, current) {
2409
+ const colour = (side) => 'mix' in side ? unroundedMix(side.mix, tokens, current) : innerColour(side, tokens, current);
1768
2410
  const first = colour(mix.a);
1769
2411
  const second = colour(mix.b);
1770
2412
  if (first === undefined || second === undefined)
@@ -1774,8 +2416,8 @@ function mixSides(mix, tokens) {
1774
2416
  { colour: second, percentage: mix.bPercentage },
1775
2417
  ];
1776
2418
  }
1777
- function unroundedMix(mix, tokens) {
1778
- const sides = mixSides(mix, tokens);
2419
+ function unroundedMix(mix, tokens, current) {
2420
+ const sides = mixSides(mix, tokens, current);
1779
2421
  return sides && mixChannels(mix.space, ...sides, mix.hue);
1780
2422
  }
1781
2423
  /** A calc marker worked out, as its slot takes it; undefined when a token in it is not set. */
@@ -1951,10 +2593,10 @@ const NEEDS_FILLING = new WeakMap();
1951
2593
  /** What `settledMarker` says of a part that is not a token's marker at all. */
1952
2594
  const NOT_A_MARKER = Symbol('not a marker');
1953
2595
  /** A colour, length or calc marker settled from the tokens, or undefined when one cannot be. */
1954
- function settledMarker(value, tokens) {
2596
+ function settledMarker(value, tokens, current) {
1955
2597
  const colour = colourMarker(value);
1956
2598
  if (colour)
1957
- return resolveColour(colour, tokens);
2599
+ return resolveColour(colour, tokens, current);
1958
2600
  // Whether a shadow is inset, from a token that is the word or nothing: `ring-inset`.
1959
2601
  const inset = value.__inset;
1960
2602
  if (inset)