@react-x11/components 0.13.0 → 0.14.1

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 (134) hide show
  1. package/dist/html/controls.d.ts +29 -2
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +70 -2
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +42 -1
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +336 -16
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +18 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +27 -0
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/parse.d.ts +18 -4
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +66 -28
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/shapes.d.ts +44 -0
  18. package/dist/html/css/shapes.d.ts.map +1 -0
  19. package/dist/html/css/shapes.js +194 -0
  20. package/dist/html/css/shapes.js.map +1 -0
  21. package/dist/html/css/style.d.ts +128 -15
  22. package/dist/html/css/style.d.ts.map +1 -1
  23. package/dist/html/css/style.js +547 -144
  24. package/dist/html/css/style.js.map +1 -1
  25. package/dist/html/css/transform.d.ts +85 -0
  26. package/dist/html/css/transform.d.ts.map +1 -0
  27. package/dist/html/css/transform.js +425 -0
  28. package/dist/html/css/transform.js.map +1 -0
  29. package/dist/html/css/ua.d.ts.map +1 -1
  30. package/dist/html/css/ua.js +14 -2
  31. package/dist/html/css/ua.js.map +1 -1
  32. package/dist/html/css/values.d.ts +4 -0
  33. package/dist/html/css/values.d.ts.map +1 -1
  34. package/dist/html/css/values.js.map +1 -1
  35. package/dist/html/fonts.d.ts +55 -5
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +284 -23
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/layout/axes.d.ts +14 -0
  40. package/dist/html/layout/axes.d.ts.map +1 -0
  41. package/dist/html/layout/axes.js +28 -0
  42. package/dist/html/layout/axes.js.map +1 -0
  43. package/dist/html/layout/block.d.ts +104 -12
  44. package/dist/html/layout/block.d.ts.map +1 -1
  45. package/dist/html/layout/block.js +410 -39
  46. package/dist/html/layout/block.js.map +1 -1
  47. package/dist/html/layout/boxes.d.ts +72 -1
  48. package/dist/html/layout/boxes.d.ts.map +1 -1
  49. package/dist/html/layout/boxes.js +40 -2
  50. package/dist/html/layout/boxes.js.map +1 -1
  51. package/dist/html/layout/cache.d.ts +1 -1
  52. package/dist/html/layout/cache.d.ts.map +1 -1
  53. package/dist/html/layout/cache.js +6 -0
  54. package/dist/html/layout/cache.js.map +1 -1
  55. package/dist/html/layout/css-grid.d.ts.map +1 -1
  56. package/dist/html/layout/css-grid.js +144 -24
  57. package/dist/html/layout/css-grid.js.map +1 -1
  58. package/dist/html/layout/flex.d.ts.map +1 -1
  59. package/dist/html/layout/flex.js +585 -85
  60. package/dist/html/layout/flex.js.map +1 -1
  61. package/dist/html/layout/inline.d.ts +3 -0
  62. package/dist/html/layout/inline.d.ts.map +1 -1
  63. package/dist/html/layout/inline.js +144 -54
  64. package/dist/html/layout/inline.js.map +1 -1
  65. package/dist/html/layout/multicol.d.ts +42 -0
  66. package/dist/html/layout/multicol.d.ts.map +1 -0
  67. package/dist/html/layout/multicol.js +897 -0
  68. package/dist/html/layout/multicol.js.map +1 -0
  69. package/dist/html/layout/shaping.d.ts +7 -0
  70. package/dist/html/layout/shaping.d.ts.map +1 -1
  71. package/dist/html/layout/shaping.js +25 -20
  72. package/dist/html/layout/shaping.js.map +1 -1
  73. package/dist/html/layout/table.d.ts.map +1 -1
  74. package/dist/html/layout/table.js +97 -31
  75. package/dist/html/layout/table.js.map +1 -1
  76. package/dist/html/node.d.ts +4 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +319 -47
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +83 -5
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +1474 -217
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/svg.d.ts +39 -9
  85. package/dist/html/svg.d.ts.map +1 -1
  86. package/dist/html/svg.js +482 -37
  87. package/dist/html/svg.js.map +1 -1
  88. package/dist/html/widgets.js +87 -7
  89. package/dist/html/widgets.js.map +1 -1
  90. package/dist/html/woff2.d.ts +14 -0
  91. package/dist/html/woff2.d.ts.map +1 -0
  92. package/dist/html/woff2.js +608 -0
  93. package/dist/html/woff2.js.map +1 -0
  94. package/dist/maps/controller.d.ts +17 -1
  95. package/dist/maps/controller.d.ts.map +1 -1
  96. package/dist/maps/controller.js +27 -7
  97. package/dist/maps/controller.js.map +1 -1
  98. package/dist/richtext/node.d.ts +8 -1
  99. package/dist/richtext/node.d.ts.map +1 -1
  100. package/dist/richtext/node.js +5 -1
  101. package/dist/richtext/node.js.map +1 -1
  102. package/dist/richtext/runs.d.ts +25 -6
  103. package/dist/richtext/runs.d.ts.map +1 -1
  104. package/dist/richtext/runs.js +240 -24
  105. package/dist/richtext/runs.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/html/controls.ts +85 -2
  108. package/src/html/css/cascade.ts +366 -8
  109. package/src/html/css/color.ts +29 -0
  110. package/src/html/css/parse.ts +71 -31
  111. package/src/html/css/shapes.ts +239 -0
  112. package/src/html/css/style.ts +652 -147
  113. package/src/html/css/transform.ts +459 -0
  114. package/src/html/css/ua.ts +15 -2
  115. package/src/html/css/values.ts +4 -0
  116. package/src/html/fonts.ts +312 -29
  117. package/src/html/layout/axes.ts +62 -0
  118. package/src/html/layout/block.ts +466 -37
  119. package/src/html/layout/boxes.ts +95 -3
  120. package/src/html/layout/cache.ts +6 -0
  121. package/src/html/layout/css-grid.ts +169 -32
  122. package/src/html/layout/flex.ts +686 -79
  123. package/src/html/layout/inline.ts +149 -55
  124. package/src/html/layout/multicol.ts +977 -0
  125. package/src/html/layout/shaping.ts +27 -18
  126. package/src/html/layout/table.ts +105 -35
  127. package/src/html/node.ts +354 -48
  128. package/src/html/paint.ts +1804 -315
  129. package/src/html/svg.ts +573 -36
  130. package/src/html/widgets.ts +95 -9
  131. package/src/html/woff2.ts +612 -0
  132. package/src/maps/controller.ts +28 -7
  133. package/src/richtext/node.ts +13 -2
  134. package/src/richtext/runs.ts +313 -35
@@ -21,7 +21,6 @@ import { Element as DomElement, isTag } from 'domhandler';
21
21
  import type { Element } from 'domhandler';
22
22
 
23
23
  import { attr, tagOf } from '../dom.js';
24
- import { svgSizeHint } from '../svg.js';
25
24
  import {
26
25
  asciiLower,
27
26
  escapeEnd,
@@ -33,6 +32,7 @@ import type { Declaration, StyleRule, Stylesheet } from './parse.js';
33
32
  import {
34
33
  applyDeclaration,
35
34
  blockify,
35
+ settleButton,
36
36
  settleContentVisibility,
37
37
  settleOverflow,
38
38
  settleClamp,
@@ -53,6 +53,13 @@ import type { UnitContext } from './values.js';
53
53
  import { viewportUnit } from './values.js';
54
54
  import { customProperties, substituteIn } from './vars.js';
55
55
  import type { CustomProps } from './vars.js';
56
+ import {
57
+ ROOT_BOX_PROPS,
58
+ SHAPE_TAGS,
59
+ isShapeProp,
60
+ shapeValue,
61
+ } from './shapes.js';
62
+ import type { ShapeContext, ShapeStyle, ShapeStyles } from './shapes.js';
56
63
 
57
64
  /** Where a declaration came from. Higher wins before specificity is asked. */
58
65
  const enum Origin {
@@ -138,6 +145,14 @@ interface IndexedRule {
138
145
  compiled: boolean;
139
146
  /** Which rule this is, in a sharing key (`Cascade.sharedStyleFor`). */
140
147
  id: number;
148
+ /** For a rule asked of the elements in a drawing by their type, or of
149
+ * them all (`Cascade.shapeStyles`): the ids and classes it asks of its
150
+ * subject's ancestors (`ancestorKeys`). Null until a drawing asks, and
151
+ * in every other rule. */
152
+ needs: readonly string[] | null;
153
+ /** For a rule kept for drawings: whether it declares something a
154
+ * drawing's root does not have from its box's style. Null until asked. */
155
+ root: boolean | null;
141
156
  }
142
157
 
143
158
  /**
@@ -193,6 +208,11 @@ class RuleIndex {
193
208
  */
194
209
  reaches(el: Element): boolean {
195
210
  if (this.universal.length || this.byTag.has(tagOf(el))) return true;
211
+ return this.names(el);
212
+ }
213
+
214
+ /** Whether a bucket here is for `el`'s id, or for a class of its. */
215
+ names(el: Element): boolean {
196
216
  if (this.byId.size) {
197
217
  const id = attr(el, 'id');
198
218
  if (id && this.byId.has(id)) return true;
@@ -208,18 +228,19 @@ class RuleIndex {
208
228
  return false;
209
229
  }
210
230
 
211
- add(rule: StyleRule): void {
231
+ add(rule: StyleRule, key = rightmostKey(rule.selector)): void {
212
232
  this.size += 1;
213
233
  const indexed: IndexedRule = {
214
234
  rule,
215
235
  match: null,
216
236
  compiled: false,
217
237
  id: this._nextId++,
238
+ needs: null,
239
+ root: null,
218
240
  };
219
241
  if (rule.selector.includes(':hover') || rule.selector.includes(':active')) {
220
242
  this.hoverSensitive = true;
221
243
  }
222
- const key = rightmostKey(rule.selector);
223
244
  if (UNSHAREABLE.test(rule.selector)) {
224
245
  if (key.kind === 'id') this.ownStyleIds.add(key.name);
225
246
  else if (key.kind === 'class') this.ownStyleClasses.add(key.name);
@@ -575,11 +596,12 @@ const POINTER_PSEUDO = /:(?:hover|active)(?![\w-])/i;
575
596
  function compileSelector(
576
597
  selector: string,
577
598
  adapter: CssSelectAdapter,
599
+ pseudos: typeof PSEUDOS = PSEUDOS,
578
600
  ): (el: Element) => boolean {
579
601
  return compile(noEmptyWords(selector), {
580
602
  adapter,
581
603
  xmlMode: false,
582
- pseudos: PSEUDOS,
604
+ pseudos,
583
605
  cacheResults: !POINTER_PSEUDO.test(selector),
584
606
  } as unknown as Parameters<typeof compile>[1]) as unknown as (
585
607
  node: Element,
@@ -640,7 +662,14 @@ function rightmostKey(selector: string): {
640
662
  start = i + 1;
641
663
  }
642
664
  }
643
- const compound = selector.slice(start);
665
+ return compoundKey(selector.slice(start));
666
+ }
667
+
668
+ /** A compound selector's most selective key (`rightmostKey`). */
669
+ function compoundKey(compound: string): {
670
+ kind: 'id' | 'class' | 'tag' | 'any';
671
+ name: string;
672
+ } {
644
673
  let id: string | null = null;
645
674
  let cls: string | null = null;
646
675
  let tag: string | null = null;
@@ -674,6 +703,39 @@ function rightmostKey(selector: string): {
674
703
  return { kind: 'any', name: '' };
675
704
  }
676
705
 
706
+ /**
707
+ * The ids and classes a selector asks of its subject's ancestors: for each
708
+ * compound a descendant or a child combinator follows, its id, or else its
709
+ * first class, as `#name` or `.name`. Such a compound is an ancestor of
710
+ * the subject — one a sibling combinator follows is a sibling of one — so
711
+ * an element with no ancestor of some such name is not the rule's, which
712
+ * is known without matching it. A compound with neither asks nothing.
713
+ */
714
+ function ancestorKeys(selector: string): string[] {
715
+ const keys: string[] = [];
716
+ const compounds = compoundsOf(selector);
717
+ for (let i = 0; i < compounds.length - 1; i += 1) {
718
+ const { text, next } = compounds[i];
719
+ if (next !== ' ' && next !== '>') continue;
720
+ const key = compoundKey(text);
721
+ if (key.kind === 'id') keys.push(`#${key.name}`);
722
+ else if (key.kind === 'class') keys.push(`.${key.name}`);
723
+ }
724
+ return keys;
725
+ }
726
+
727
+ /** What the rules asked of every element of a drawing, or of every one of
728
+ * a type, ask of its ancestors (`Cascade.shapeStyles`). */
729
+ interface ShapeNeeds {
730
+ /** The classes and the ids some such rule names in an ancestor. */
731
+ classes: Set<string>;
732
+ ids: Set<string>;
733
+ /** Whether a rule asked of every element asks nothing of its ancestors,
734
+ * and the types with such a rule. */
735
+ universal: boolean;
736
+ tags: Set<string>;
737
+ }
738
+
677
739
  function balancedEnd(
678
740
  text: string,
679
741
  from: number,
@@ -762,6 +824,13 @@ export class Cascade {
762
824
  marker: new RuleIndex(),
763
825
  selection: new RuleIndex(),
764
826
  };
827
+ /** The author's rules that could style a shape in a drawing: the ones
828
+ * that declare a property a shape has (`shapes.ts`) and whose subject
829
+ * is no element of HTML's alone. They are in `_index` too, for the
830
+ * elements that have boxes; these are asked of the ones in an inline
831
+ * `<svg>`, which have none (`shapeStyles`). */
832
+ private _shapes = new RuleIndex();
833
+ private _shapeNeeds: ShapeNeeds | null = null;
765
834
  private _adapter: CssSelectAdapter;
766
835
  /** The compounds of the selectors that test the pointer, each without
767
836
  * its `:hover` or `:active` (`pointerCompounds`), and their matchers,
@@ -836,6 +905,8 @@ export class Cascade {
836
905
  /** Whether any declaration has a length in `lh` or `rlh`: only then is
837
906
  * the line height settled ahead of the declarations that read it. */
838
907
  private _lh = false;
908
+ /** The pseudo-classes css-select is handed, `:root` this cascade's. */
909
+ private _pseudos: typeof PSEUDOS;
839
910
 
840
911
  constructor(
841
912
  sheets: Stylesheet[],
@@ -847,7 +918,13 @@ export class Cascade {
847
918
  zeroWidth: FaceMetric | null = null,
848
919
  normalLine: FaceMetric | null = null,
849
920
  families: FontFamilies | null = null,
921
+ /** The document's element where it is not an `<html>`: an SVG image's
922
+ * `<svg>`, which is `:root` there (Selectors 4, 14.1). */
923
+ documentElement: Element | null = null,
850
924
  ) {
925
+ this._pseudos = documentElement
926
+ ? { ...PSEUDOS, root: (el: Element) => el === documentElement }
927
+ : PSEUDOS;
851
928
  this._xHeightOf = xHeight;
852
929
  this._zeroWidthOf = zeroWidth;
853
930
  this._normalLineOf = normalLine;
@@ -874,7 +951,17 @@ export class Cascade {
874
951
  if (!this._lh && usesLh(rule.declarations)) this._lh = true;
875
952
  const pseudo = splitPseudoElement(rule);
876
953
  if (pseudo) this._pseudo[pseudo.which].add(pseudo.rule);
877
- else this._index.add(rule);
954
+ else {
955
+ const key = rightmostKey(rule.selector);
956
+ this._index.add(rule, key);
957
+ if (
958
+ rule.order >= 0 &&
959
+ (key.kind !== 'tag' || SHAPE_TAGS.has(key.name)) &&
960
+ declaresShape(rule.declarations)
961
+ ) {
962
+ this._shapes.add(rule, key);
963
+ }
964
+ }
878
965
  this._noteHover((pseudo?.rule ?? rule).selector);
879
966
  }
880
967
  for (const bp of sheet.breakpoints) breakpoints.add(bp);
@@ -1051,7 +1138,7 @@ export class Cascade {
1051
1138
  * it, as a rule it refuses drops out of the cascade. */
1052
1139
  private _compile(selector: string): ((el: Element) => boolean) | null {
1053
1140
  try {
1054
- return compileSelector(selector, this._adapter);
1141
+ return compileSelector(selector, this._adapter, this._pseudos);
1055
1142
  } catch {
1056
1143
  return null;
1057
1144
  }
@@ -1237,6 +1324,7 @@ export class Cascade {
1237
1324
  parentStyle,
1238
1325
  inFlexContainer,
1239
1326
  candidates,
1327
+ true,
1240
1328
  ),
1241
1329
  key: this._nextShareKey++,
1242
1330
  };
@@ -1293,6 +1381,7 @@ export class Cascade {
1293
1381
  parentStyle,
1294
1382
  inFlexContainer,
1295
1383
  this._candidates(el),
1384
+ true,
1296
1385
  );
1297
1386
  }
1298
1387
 
@@ -1509,6 +1598,9 @@ export class Cascade {
1509
1598
  parentStyle: ComputedStyle,
1510
1599
  inFlexContainer: boolean,
1511
1600
  candidates: Candidate[],
1601
+ /** Whether the style is the element's own, and not that of one of its
1602
+ * pseudo-elements. */
1603
+ own = false,
1512
1604
  ): ComputedStyle {
1513
1605
  const style = inherit(parentStyle, this.initial);
1514
1606
  // custom properties first, in cascade order, so every `var()` in the
@@ -1521,10 +1613,14 @@ export class Cascade {
1521
1613
  // em-relative length in the same rule resolves against this element's.
1522
1614
  const root = isRootElement(el);
1523
1615
  const rootStyle = root ? this.initial : (this._root ?? this.initial);
1616
+ // whose declaration is being applied: the UA sheet's families are the
1617
+ // host's own faces, and its lists end where they end
1618
+ let authored = true;
1524
1619
  const ctxParent: UnitContext = {
1525
1620
  em: parentStyle.fontSize,
1526
1621
  rem: rootStyle.fontSize,
1527
1622
  initial: this.initial,
1623
+ fallbackFamily: () => (authored ? this.initial.fontFamily : null),
1528
1624
  vw: this.viewportWidth,
1529
1625
  vh: this.viewportHeight,
1530
1626
  scale: this.scale,
@@ -1543,6 +1639,7 @@ export class Cascade {
1543
1639
  let sized = false;
1544
1640
  let keyword = false;
1545
1641
  for (const c of candidates) {
1642
+ authored = c.origin !== Origin.UserAgent;
1546
1643
  for (const d of pick(c)) {
1547
1644
  if (
1548
1645
  d.prop === 'font-family' ||
@@ -1606,6 +1703,7 @@ export class Cascade {
1606
1703
  // them. A `font` sets the size as well, which the first pass settled.
1607
1704
  if (this._lh) {
1608
1705
  for (const c of candidates) {
1706
+ authored = c.origin !== Origin.UserAgent;
1609
1707
  for (const d of pick(c)) {
1610
1708
  if (d.prop === 'line-height' || d.prop === 'font') {
1611
1709
  this._apply(style, parentStyle, d, ctx);
@@ -1621,6 +1719,7 @@ export class Cascade {
1621
1719
  }
1622
1720
  const settled = style.fontSize;
1623
1721
  for (const c of candidates) {
1722
+ authored = c.origin !== Origin.UserAgent;
1624
1723
  for (const d of pick(c)) {
1625
1724
  if (d.prop === 'font-size' || d.custom) continue;
1626
1725
  this._apply(style, parentStyle, d, ctx);
@@ -1648,6 +1747,10 @@ export class Cascade {
1648
1747
  settleOverflow(style);
1649
1748
  settleContentVisibility(style);
1650
1749
  blockify(style, inFlexContainer);
1750
+ // a button is laid out as one, whatever `display` it was given
1751
+ if (own && el.name.length === 6 && tagOf(el) === 'button') {
1752
+ settleButton(style);
1753
+ }
1651
1754
  decorate(style);
1652
1755
  // which faces of the document's own families this family, weight and
1653
1756
  // slant ask for — known only now, with all three computed
@@ -1726,6 +1829,185 @@ export class Cascade {
1726
1829
  return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
1727
1830
  }
1728
1831
 
1832
+ /** Whether any rule could style a shape in a drawing: a document with
1833
+ * none asks nothing of its drawings (`shapeStyles`). */
1834
+ get stylesShapes(): boolean {
1835
+ return this._shapes.size > 0;
1836
+ }
1837
+
1838
+ /** Whether a pointer move can change what a rule gives a shape in a
1839
+ * drawing. */
1840
+ get shapesFollowPointer(): boolean {
1841
+ return this._shapes.hoverSensitive;
1842
+ }
1843
+
1844
+ /**
1845
+ * What the rules give the elements of an inline drawing, `root` and what
1846
+ * is in it, or null where none reaches one: the narrow cascade of
1847
+ * `shapes.ts`. `style` is the root's computed style — its `color`, its
1848
+ * custom properties and its font size are what a value in the drawing is
1849
+ * read against, as though every element in it had inherited them, which
1850
+ * all but one a rule gives its own has.
1851
+ *
1852
+ * An element's rules and its `style` attribute are put in cascade order,
1853
+ * and each property is the last valid declaration's. The presentation
1854
+ * attributes are below them all (SVG 2, 6.2), and `SvgView` reads those
1855
+ * itself. The root's box has its paint, its opacity and whether it is
1856
+ * shown from its computed style, so those are left out for it.
1857
+ *
1858
+ * Asked as a drawing is first painted (`BoxTree.shapeStyler`), of the
1859
+ * rules in `_shapes` alone: a page of hundreds of icons pays for the ones
1860
+ * it shows, a look in the buckets for each of their elements where no
1861
+ * rule reaches them, and a document with no such rule nothing.
1862
+ */
1863
+ shapeStyles(root: Element, style: ComputedStyle): ShapeStyles | null {
1864
+ const index = this._shapes;
1865
+ if (!index.size) return null;
1866
+ // The rules kept by a class or an id are asked of the elements with
1867
+ // that name. The rest — `.dark .logo path`, `.menu [aria-hidden]` — are
1868
+ // asked of every `<path>`, or of every element, and nearly all of them
1869
+ // are for some other part of the page: each says so by the class or the
1870
+ // id it asks of an ancestor, which is looked for once for the drawing,
1871
+ // around it and in it, where matching the rule would look for it from
1872
+ // each of its elements. On a page with none of them around a drawing,
1873
+ // nothing is matched at all.
1874
+ const needs = (this._shapeNeeds ??= this._needsOfShapes());
1875
+ const found = new Set<string>();
1876
+ const asks = needs.classes.size > 0 || needs.ids.size > 0;
1877
+ const note = (el: Element): void => {
1878
+ if (needs.ids.size) {
1879
+ const id = attr(el, 'id');
1880
+ if (id && needs.ids.has(id)) found.add(`#${id}`);
1881
+ }
1882
+ if (needs.classes.size) {
1883
+ const className = attr(el, 'class');
1884
+ if (className) {
1885
+ for (const name of className.split(/\s+/)) {
1886
+ if (needs.classes.has(name)) found.add(`.${name}`);
1887
+ }
1888
+ }
1889
+ }
1890
+ };
1891
+ if (asks) {
1892
+ let at = root.parent;
1893
+ while (at && at.type === 'tag') {
1894
+ note(at as Element);
1895
+ at = at.parent;
1896
+ }
1897
+ }
1898
+ const live = (indexed: IndexedRule): boolean => {
1899
+ const keys = indexed.needs;
1900
+ if (keys) for (const key of keys) if (!found.has(key)) return false;
1901
+ return true;
1902
+ };
1903
+ // The root's box has its paint from its computed style, so most of the
1904
+ // rules that name a drawing by its class — an icon set's — have
1905
+ // nothing else to say of it.
1906
+ const liveForRoot = (indexed: IndexedRule): boolean =>
1907
+ (indexed.root ??= declaresRootShape(indexed.rule.declarations)) &&
1908
+ live(indexed);
1909
+
1910
+ let of: Map<Element, ShapeStyle> | null = null;
1911
+ let key = '';
1912
+ let ctx: ShapeContext | null = null;
1913
+ // in document order, the root first: an element's place in it is what
1914
+ // the key names it by, and its ancestors in the drawing are noted
1915
+ // before it is asked of
1916
+ const stack: Element[] = [root];
1917
+ let place = -1;
1918
+ while (stack.length) {
1919
+ const el = stack.pop()!;
1920
+ place += 1;
1921
+ // what is in a `<foreignObject>` is not the drawing's to draw
1922
+ if (el.name !== 'foreignobject' && el.name !== 'foreignObject') {
1923
+ const children = el.children;
1924
+ for (let i = children.length - 1; i >= 0; i -= 1) {
1925
+ const child = children[i];
1926
+ if (child.type === 'tag') stack.push(child as Element);
1927
+ }
1928
+ }
1929
+ if (asks) note(el);
1930
+ const tag = tagOf(el);
1931
+ const asked = found.size
1932
+ ? index.universal.length > 0 || index.byTag.has(tag)
1933
+ : needs.universal || needs.tags.has(tag);
1934
+ if (!asked && !index.names(el)) continue;
1935
+ const candidates: Candidate[] = [];
1936
+ this._matchInto(
1937
+ index,
1938
+ el,
1939
+ candidates,
1940
+ undefined,
1941
+ el === root ? liveForRoot : live,
1942
+ );
1943
+ if (!candidates.length) continue;
1944
+ const inline = attr(el, 'style');
1945
+ if (inline) pushInlineShapes(candidates, inline);
1946
+ candidates.sort(byCascade);
1947
+ ctx ??= {
1948
+ color: style.color,
1949
+ scheme: style.colorScheme,
1950
+ units: {
1951
+ em: style.fontSize,
1952
+ rem: (this._root ?? this.initial).fontSize,
1953
+ vw: this.viewportWidth,
1954
+ vh: this.viewportHeight,
1955
+ scale: this.scale,
1956
+ initial: this.initial,
1957
+ },
1958
+ };
1959
+ let own: Record<string, string> | null = null;
1960
+ for (const c of candidates) {
1961
+ for (const d of pick(c)) {
1962
+ if (!isShapeProp(d.prop)) continue;
1963
+ if (el === root && ROOT_BOX_PROPS.has(d.prop)) continue;
1964
+ // a `var()` with nothing to stand for it is invalid at
1965
+ // computed-value time, and the property as though `unset`
1966
+ const value = shapeValue(
1967
+ d.prop,
1968
+ d.vars ? (substituteIn(d.value, style.custom) ?? 'unset') : d.value,
1969
+ ctx,
1970
+ );
1971
+ if (value !== null) (own ??= {})[d.prop] = value;
1972
+ }
1973
+ }
1974
+ if (!own) continue;
1975
+ (of ??= new Map()).set(el, own);
1976
+ key += `${place}{`;
1977
+ for (const prop in own) key += `${prop}:${own[prop]};`;
1978
+ key += '}';
1979
+ }
1980
+ return of ? { of, key } : null;
1981
+ }
1982
+
1983
+ /** `ShapeNeeds`, and each such rule's own (`IndexedRule.needs`). */
1984
+ private _needsOfShapes(): ShapeNeeds {
1985
+ const index = this._shapes;
1986
+ const needs: ShapeNeeds = {
1987
+ classes: new Set(),
1988
+ ids: new Set(),
1989
+ universal: false,
1990
+ tags: new Set(),
1991
+ };
1992
+ const read = (bucket: IndexedRule[]): boolean => {
1993
+ let free = false;
1994
+ for (const indexed of bucket) {
1995
+ const keys = ancestorKeys(indexed.rule.selector);
1996
+ indexed.needs = keys;
1997
+ if (!keys.length) free = true;
1998
+ for (const key of keys) {
1999
+ (key[0] === '#' ? needs.ids : needs.classes).add(key.slice(1));
2000
+ }
2001
+ }
2002
+ return free;
2003
+ };
2004
+ needs.universal = read(index.universal);
2005
+ for (const [tag, bucket] of index.byTag) {
2006
+ if (read(bucket)) needs.tags.add(tag);
2007
+ }
2008
+ return needs;
2009
+ }
2010
+
1729
2011
  /**
1730
2012
  * Whether the pointer's move from `was` to where it is changed which
1731
2013
  * rules match `el`, or its `::before`, `::after` or another of its
@@ -1768,6 +2050,8 @@ export class Cascade {
1768
2050
  el: Element,
1769
2051
  out: Candidate[] | null,
1770
2052
  matched?: number[],
2053
+ /** The rules to try, where some are known not to match without it. */
2054
+ live?: (indexed: IndexedRule) => boolean,
1771
2055
  ): void {
1772
2056
  // A media query's width is CSS pixels; the viewport is kept in device.
1773
2057
  const width = this.viewportWidth / this.scale;
@@ -1776,6 +2060,7 @@ export class Cascade {
1776
2060
  const consider = (bucket: IndexedRule[] | undefined): void => {
1777
2061
  if (!bucket) return;
1778
2062
  for (const indexed of bucket) {
2063
+ if (live !== undefined && !live(indexed)) continue;
1779
2064
  const rule = indexed.rule;
1780
2065
  if (
1781
2066
  !mediaMatches(
@@ -1791,7 +2076,11 @@ export class Cascade {
1791
2076
  if (!indexed.compiled) {
1792
2077
  indexed.compiled = true;
1793
2078
  try {
1794
- indexed.match = compileSelector(rule.selector, this._adapter);
2079
+ indexed.match = compileSelector(
2080
+ rule.selector,
2081
+ this._adapter,
2082
+ this._pseudos,
2083
+ );
1795
2084
  } catch {
1796
2085
  // A selector this matcher does not know (`::-moz-…`, a CSS4 form
1797
2086
  // it has not learnt) drops out of the cascade rather than out of
@@ -2071,6 +2360,51 @@ function setsContent(c: Candidate): boolean {
2071
2360
  /** Whether a rule's declarations set a custom property, by the array. */
2072
2361
  const CUSTOM_IN = new WeakMap<Declaration[], boolean>();
2073
2362
 
2363
+ /** Whether declarations set a property a shape in a drawing has. */
2364
+ function declaresShape(declarations: readonly Declaration[]): boolean {
2365
+ for (const d of declarations) if (isShapeProp(d.prop)) return true;
2366
+ return false;
2367
+ }
2368
+
2369
+ /** Whether declarations set one a drawing's root does not have from its
2370
+ * box's style (`ROOT_BOX_PROPS`). */
2371
+ function declaresRootShape(declarations: readonly Declaration[]): boolean {
2372
+ for (const d of declarations) {
2373
+ if (isShapeProp(d.prop) && !ROOT_BOX_PROPS.has(d.prop)) return true;
2374
+ }
2375
+ return false;
2376
+ }
2377
+
2378
+ /** A `style` attribute's declarations of a shape's properties, as
2379
+ * candidates: over every rule, and its `!important` ones over theirs. */
2380
+ function pushInlineShapes(out: Candidate[], inline: string): void {
2381
+ const declarations = parseDeclarations(inline).filter((d) =>
2382
+ isShapeProp(d.prop),
2383
+ );
2384
+ const normal = declarations.filter((d) => !d.important);
2385
+ const important = declarations.filter((d) => d.important);
2386
+ if (normal.length) {
2387
+ out.push({
2388
+ origin: Origin.Inline,
2389
+ layer: null,
2390
+ specificity: 0,
2391
+ order: 0,
2392
+ declarations: normal,
2393
+ only: -1,
2394
+ });
2395
+ }
2396
+ if (important.length) {
2397
+ out.push({
2398
+ origin: Origin.InlineImportant,
2399
+ layer: null,
2400
+ specificity: 0,
2401
+ order: 0,
2402
+ declarations: important,
2403
+ only: -1,
2404
+ });
2405
+ }
2406
+ }
2407
+
2074
2408
  function pick(c: Candidate): Declaration[] {
2075
2409
  return c.only < 0 ? c.declarations : [c.declarations[c.only]];
2076
2410
  }
@@ -2275,6 +2609,12 @@ function presentationHints(el: Element): Declaration[] {
2275
2609
  const height = attr(el, 'height');
2276
2610
  const h = height ? svgSizeHint(height) : null;
2277
2611
  if (h) push('height', h);
2612
+ // and its paint is its `fill` and `stroke` properties' (13.2), which a
2613
+ // rule of the document's then sets over the attribute
2614
+ const fill = attr(el, 'fill');
2615
+ if (fill) push('fill', fill);
2616
+ const stroke = attr(el, 'stroke');
2617
+ if (stroke) push('stroke', stroke);
2278
2618
  }
2279
2619
 
2280
2620
  if (tag === 'table') {
@@ -2450,6 +2790,24 @@ function lengthAttr(value: string): string {
2450
2790
  return Number.isFinite(n) ? `${n}px` : v;
2451
2791
  }
2452
2792
 
2793
+ /** A number and a unit, as an SVG's `width` and `height` are written. */
2794
+ const SVG_LENGTH =
2795
+ /^\s*([+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?)\s*([a-z]*)\s*$/i;
2796
+
2797
+ /**
2798
+ * The `width`/`height` of an inline `<svg>` as CSS: they are presentation
2799
+ * attributes for the properties of the same names (SVG 2, 5.1.1), so
2800
+ * `height="50%"` is a percentage of the containing block like a style's.
2801
+ * A bare number is pixels; null for what CSS would not parse.
2802
+ */
2803
+ function svgSizeHint(value: string): string | null {
2804
+ const v = value.trim();
2805
+ if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?%$/i.test(v)) return v;
2806
+ const m = SVG_LENGTH.exec(v);
2807
+ if (!m) return null;
2808
+ return m[2] ? v : `${parseFloat(m[1])}px`;
2809
+ }
2810
+
2453
2811
  function closestTable(el: Element): Element | null {
2454
2812
  let node = el.parent;
2455
2813
  while (node) {
@@ -773,6 +773,24 @@ export function lightDark(
773
773
  value: string,
774
774
  scheme: 'light' | 'dark',
775
775
  ): string | null {
776
+ // kept by the value: the UA sheet's link colour is one, so every link of
777
+ // every document asks, and a theme's are a few asked by every element
778
+ const kept = PICKED[scheme];
779
+ let picked = kept.get(value);
780
+ if (picked === undefined) {
781
+ picked = pickScheme(value, scheme);
782
+ if (kept.size >= 512) kept.clear();
783
+ kept.set(value, picked);
784
+ }
785
+ return picked;
786
+ }
787
+
788
+ const PICKED = {
789
+ light: new Map<string, string | null>(),
790
+ dark: new Map<string, string | null>(),
791
+ };
792
+
793
+ function pickScheme(value: string, scheme: 'light' | 'dark'): string | null {
776
794
  let out = value;
777
795
  // a branch can hold another, which the next round replaces
778
796
  for (let round = 0; round < 32; round += 1) {
@@ -827,6 +845,17 @@ export function usedColorScheme(
827
845
  return first ?? preferred;
828
846
  }
829
847
 
848
+ /**
849
+ * A browser's own colours in each scheme — the system colours `Canvas`,
850
+ * `CanvasText` and `LinkText` (CSS Color 4, 6.2), as Chrome has them: what
851
+ * a document is drawn in where its root's scheme is not the palette's,
852
+ * which has the colours of its own scheme and of no other.
853
+ */
854
+ export const SCHEME_COLORS = {
855
+ light: { canvas: '#ffffff', text: '#000000', link: '#0000ee' },
856
+ dark: { canvas: '#121212', text: '#ffffff', link: '#9e9eff' },
857
+ } as const;
858
+
830
859
  const SCHEME_IDENT = /^-?(?:[a-z_]|--)[\w-]*$/;
831
860
  /** What a scheme's name cannot be: the keywords the grammar has, and the
832
861
  * CSS-wide ones, which reach here only in a list. */