@react-x11/components 0.14.0 → 0.16.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 (97) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +209 -23
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +873 -175
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +62 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -0
  22. package/dist/html/css/shapes.js +214 -0
  23. package/dist/html/css/shapes.js.map +1 -0
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +75 -27
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +15 -0
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +38 -7
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +160 -50
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +29 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +159 -20
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +69 -19
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1648 -225
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +23 -8
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +907 -323
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +36 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +174 -37
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts +4 -1
  64. package/dist/html/paint.d.ts.map +1 -1
  65. package/dist/html/paint.js +769 -204
  66. package/dist/html/paint.js.map +1 -1
  67. package/dist/html/resources.js +14 -1
  68. package/dist/html/resources.js.map +1 -1
  69. package/dist/html/svg.d.ts +50 -17
  70. package/dist/html/svg.d.ts.map +1 -1
  71. package/dist/html/svg.js +501 -83
  72. package/dist/html/svg.js.map +1 -1
  73. package/dist/html/widgets.d.ts.map +1 -1
  74. package/dist/html/widgets.js +57 -4
  75. package/dist/html/widgets.js.map +1 -1
  76. package/package.json +3 -3
  77. package/src/code-editor/node.ts +24 -5
  78. package/src/html/css/cascade.ts +987 -176
  79. package/src/html/css/color.ts +345 -18
  80. package/src/html/css/content.ts +173 -15
  81. package/src/html/css/parse.ts +60 -8
  82. package/src/html/css/shapes.ts +273 -0
  83. package/src/html/css/style.ts +123 -30
  84. package/src/html/css/ua.ts +15 -0
  85. package/src/html/css/values.ts +23 -9
  86. package/src/html/index.ts +7 -0
  87. package/src/html/layout/block.ts +169 -56
  88. package/src/html/layout/boxes.ts +198 -24
  89. package/src/html/layout/css-grid.ts +89 -22
  90. package/src/html/layout/flex.ts +1863 -219
  91. package/src/html/layout/inline.ts +27 -7
  92. package/src/html/layout/table.ts +989 -333
  93. package/src/html/node.ts +204 -37
  94. package/src/html/paint.ts +999 -252
  95. package/src/html/resources.ts +13 -1
  96. package/src/html/svg.ts +600 -78
  97. package/src/html/widgets.ts +61 -4
package/src/html/node.ts CHANGED
@@ -56,10 +56,18 @@ import {
56
56
  HOVER_PSEUDO_NONE,
57
57
  HOVER_PSEUDO_OTHER,
58
58
  HOVER_UNTOUCHED,
59
+ NO_FOCUS,
60
+ } from './css/cascade.js';
61
+ import type {
62
+ FocusState,
63
+ HoverTouch,
64
+ KeptStyles,
65
+ MetricFace,
66
+ PointerState,
59
67
  } from './css/cascade.js';
60
- import type { HoverTouch, KeptStyles, MetricFace } from './css/cascade.js';
61
68
  import { mediaMatches, parseStylesheet } from './css/parse.js';
62
69
  import type { MediaCondition, Stylesheet } from './css/parse.js';
70
+ import type { ShapeStyles } from './css/shapes.js';
63
71
  import { uaStylesheet } from './css/ua.js';
64
72
  import type { ComputedStyle, RootLook } from './css/style.js';
65
73
  import {
@@ -74,6 +82,7 @@ import {
74
82
  GENERATED_FROM,
75
83
  INLINE_OFFSETS,
76
84
  LINE_BOX_RAISES,
85
+ SHAPE_STYLES,
77
86
  SHIFTED_LINES,
78
87
  TEXT_SHIFTS,
79
88
  columned,
@@ -127,7 +136,7 @@ import {
127
136
  stacksLayers,
128
137
  } from './paint.js';
129
138
  import type { PaintContext } from './paint.js';
130
- import { controlRectsOf, measureControl } from './controls.js';
139
+ import { controlRectsOf, measureControl, styledField } from './controls.js';
131
140
  import type { BareField, ControlRect } from './controls.js';
132
141
  import { ResourceStore } from './resources.js';
133
142
  import type { ResourceRequest, ResourceResult } from './resources.js';
@@ -288,6 +297,8 @@ export class HtmlViewNode extends Node {
288
297
  * the text carries surrogate pairs. null until checked. */
289
298
  private _pointsAreUnits: boolean | null = null;
290
299
  private _hovered: Element[] = [];
300
+ /** Which element's widget holds the focus (`setFocus`). */
301
+ private _focus: FocusState = NO_FOCUS;
291
302
  /** Where the pointer last moved to, in logical window pixels, while it
292
303
  * is over the element (`defaultMouseMove`). */
293
304
  private _pointerAt: { x: number; y: number } | null = null;
@@ -400,6 +411,12 @@ export class HtmlViewNode extends Node {
400
411
  controlBorder: look.controlBorder * s,
401
412
  controlRadius: look.controlRadius * s,
402
413
  controlFontSize: (look.controlFontSize ?? look.fontSize) * s,
414
+ ...(look.focusRingWidth !== undefined && {
415
+ focusRingWidth: look.focusRingWidth * s,
416
+ }),
417
+ ...(look.focusRingOffset !== undefined && {
418
+ focusRingOffset: look.focusRingOffset * s,
419
+ }),
403
420
  };
404
421
  this._deviceLookFor = look;
405
422
  this._deviceLookAt = s;
@@ -709,6 +726,7 @@ export class HtmlViewNode extends Node {
709
726
  hovered: new Set(this._hovered),
710
727
  active: EMPTY_SET,
711
728
  });
729
+ this._cascade.setFocus(this._focus);
712
730
  this._mediaBand = this._cascade.mediaBand(width);
713
731
  }
714
732
 
@@ -1029,6 +1047,10 @@ export class HtmlViewNode extends Node {
1029
1047
  scale: this._scale,
1030
1048
  imageSize: (el) => this._resources.imageSize(imageUrlOf(el) ?? ''),
1031
1049
  urlSize: (url) => this._resources.imageSize(url),
1050
+ faceAscent: (style) => {
1051
+ const fonts = this._fonts();
1052
+ return fonts ? faceExtentOf(fonts, style).ascent : undefined;
1053
+ },
1032
1054
  controlSize: (el, kind, style) =>
1033
1055
  measureControl(el, kind, style, this._fonts(), look),
1034
1056
  });
@@ -1537,37 +1559,120 @@ export class HtmlViewNode extends Node {
1537
1559
  * with the styles of every element the move did not reach kept.
1538
1560
  */
1539
1561
  private _restyleHover(was: readonly Element[], now: readonly Element[]) {
1562
+ const cascade = this._cascade;
1540
1563
  const reach = this._hoverReach(was, now);
1541
- if (reach !== null) {
1564
+ if (reach !== null && cascade) {
1542
1565
  if (!reach.size) return;
1543
- if (this._hoverInPlace(reach, was)) return;
1566
+ const before = { hovered: new Set(was), active: EMPTY_SET };
1567
+ if (this._hoverInPlace(reach, before, this._focus, cascade.hoverPseudo)) {
1568
+ return;
1569
+ }
1544
1570
  }
1545
1571
  this._invalidate(Stale.Boxes, reach);
1546
1572
  }
1547
1573
 
1574
+ /** The element whose widget holds the focus, or null (`setFocus`). */
1575
+ get focusedElement(): Element | null {
1576
+ return this._focus.element;
1577
+ }
1578
+
1579
+ /**
1580
+ * The focus moved: `element`'s widget took it, or, given null, no widget
1581
+ * of this document holds it now — it went to something else in the
1582
+ * window, or nowhere. `visible` is whether it shows, `:focus-visible`,
1583
+ * which it always does in a text field. Restyles what `:focus` and its
1584
+ * kin reach as a hover does (`_restyleHover`), and returns whether the
1585
+ * cascade's answer could have changed.
1586
+ *
1587
+ * No element of a document takes the focus: a control's widget, mounted
1588
+ * beside it, does, and says so here. Only a text field's does, so far —
1589
+ * the widgets core draws as components keep their own focus to
1590
+ * themselves.
1591
+ */
1592
+ setFocus(element: Element | null, visible = true): boolean {
1593
+ const was = this._focus;
1594
+ if (was.element === element && (!element || was.visible === visible)) {
1595
+ return false;
1596
+ }
1597
+ const within = new Set<Element>();
1598
+ for (
1599
+ let at: Element | null = element;
1600
+ at;
1601
+ at = isElement(at.parent) ? at.parent : null
1602
+ ) {
1603
+ within.add(at);
1604
+ }
1605
+ const now: FocusState = element ? { element, visible, within } : NO_FOCUS;
1606
+ this._focus = now;
1607
+ const cascade = this._cascade;
1608
+ cascade?.setFocus(now);
1609
+ if (!cascade || !cascade.focusSensitive) return false;
1610
+ const reach = cascade.focusLocal
1611
+ ? this._stateReach(
1612
+ [...was.within],
1613
+ [...now.within],
1614
+ // one element, whose ring came or went
1615
+ was.element === element ? element : null,
1616
+ (el) => cascade.focusTouches(el),
1617
+ (el, into) => cascade.focusAnchors(el, into),
1618
+ )
1619
+ : null;
1620
+ if (reach !== null) {
1621
+ if (!reach.size) return true;
1622
+ const pointer = { hovered: new Set(this._hovered), active: EMPTY_SET };
1623
+ if (this._hoverInPlace(reach, pointer, was, cascade.focusPseudo)) {
1624
+ return true;
1625
+ }
1626
+ }
1627
+ this._invalidate(Stale.Boxes, reach);
1628
+ return true;
1629
+ }
1630
+
1548
1631
  /**
1549
1632
  * The elements whose styles a move of the hovered chain can change, or
1550
1633
  * null where that cannot be said (`Cascade.hoverLocal`).
1634
+ */
1635
+ private _hoverReach(
1636
+ was: readonly Element[],
1637
+ now: readonly Element[],
1638
+ ): Set<Element> | null {
1639
+ const cascade = this._cascade;
1640
+ if (!cascade || !cascade.hoverLocal) return null;
1641
+ return this._stateReach(
1642
+ was,
1643
+ now,
1644
+ null,
1645
+ (el) => cascade.hoverTouches(el),
1646
+ (el, into) => cascade.hoverAnchors(el, into),
1647
+ );
1648
+ }
1649
+
1650
+ /**
1651
+ * The elements whose styles a move of the hovered chain can change, or
1652
+ * of the focus's chain.
1551
1653
  *
1552
1654
  * Only an element whose hover state flipped and that a compound testing
1553
1655
  * the pointer could match can change (`Cascade.hoverTouches`), with its
1554
1656
  * subtree, and its later siblings' where a sibling combinator is in play;
1555
1657
  * and the elements a `:has()` testing the pointer may flip, which are
1556
1658
  * around the ones that changed. A move between two paragraphs under
1557
- * `a:hover` reaches nothing.
1659
+ * `a:hover` reaches nothing. The focus's the same way, its chain the
1660
+ * focused element and every element around it, and `also` one whose
1661
+ * state flipped though it is in both — the ring coming or going.
1558
1662
  */
1559
- private _hoverReach(
1663
+ private _stateReach(
1560
1664
  was: readonly Element[],
1561
1665
  now: readonly Element[],
1562
- ): Set<Element> | null {
1563
- const cascade = this._cascade;
1564
- if (!cascade || !cascade.hoverLocal) return null;
1666
+ also: Element | null,
1667
+ touches: (el: Element) => HoverTouch,
1668
+ anchors: (el: Element, into: Map<Element, HoverTouch>) => void,
1669
+ ): Set<Element> {
1565
1670
  const before = new Set(was);
1566
1671
  const after = new Set(now);
1567
1672
  const roots = new Map<Element, HoverTouch>();
1568
- const flipped = (el: Element, other: Set<Element>): void => {
1569
- if (other.has(el)) return;
1570
- const touch = cascade.hoverTouches(el);
1673
+ const flipped = (el: Element, other: Set<Element> | null): void => {
1674
+ if (other?.has(el)) return;
1675
+ const touch = touches(el);
1571
1676
  if (touch > (roots.get(el) ?? HOVER_UNTOUCHED)) roots.set(el, touch);
1572
1677
  };
1573
1678
  for (const el of was) flipped(el, after);
@@ -1576,10 +1681,14 @@ export class HtmlViewNode extends Node {
1576
1681
  // side is where a `:has()` is looked for from
1577
1682
  const deepest = (chain: readonly Element[], other: Set<Element>) => {
1578
1683
  const el = chain[0];
1579
- if (el && !other.has(el)) cascade.hoverAnchors(el, roots);
1684
+ if (el && !other.has(el)) anchors(el, roots);
1580
1685
  };
1581
1686
  deepest(was, after);
1582
1687
  deepest(now, before);
1688
+ if (also) {
1689
+ flipped(also, null);
1690
+ anchors(also, roots);
1691
+ }
1583
1692
 
1584
1693
  // the roots' subtrees, which inherit from them and a descendant
1585
1694
  // combinator reaches, and their later siblings' too where a sibling
@@ -1624,8 +1733,10 @@ export class HtmlViewNode extends Node {
1624
1733
  * - The boxes take their new styles: an element's own, the text in it,
1625
1734
  * the anonymous boxes the fix-up made around what is in it, and its
1626
1735
  * `::before` and `::after`, styled again by their own rules. A box
1627
- * whose style is derived some other way — a marker, a first letter, a
1628
- * control — is not one to restyle here.
1736
+ * whose style is derived some other way — a marker, a first letter —
1737
+ * is not one to restyle here. A control's is, and its widget is told
1738
+ * (`_reportControls`), unless the page took to drawing the field or
1739
+ * stopped.
1629
1740
  * - Each layout of their text is made again from the runs it was made
1630
1741
  * from (`TextLayoutCache.inputsOf`) with the new ink (`runFor`), where
1631
1742
  * its geometry comes out the same.
@@ -1635,10 +1746,16 @@ export class HtmlViewNode extends Node {
1635
1746
  * costs what it changed, in a document of any length. True where it was
1636
1747
  * restyled here, or had nothing to restyle; false where the boxes have to
1637
1748
  * be built again.
1749
+ *
1750
+ * A focus change is answered the same way (`setFocus`): `before` and
1751
+ * `beforeFocus` are the states the change was from, and `pseudoRules`
1752
+ * which pseudo-elements' rules test the state that changed.
1638
1753
  */
1639
1754
  private _hoverInPlace(
1640
1755
  reach: ReadonlySet<Element>,
1641
- was: readonly Element[],
1756
+ before: PointerState,
1757
+ beforeFocus: FocusState,
1758
+ pseudoRules: 0 | 1 | 2,
1642
1759
  ): boolean {
1643
1760
  const cascade = this._cascade;
1644
1761
  const tree = this._tree;
@@ -1647,17 +1764,15 @@ export class HtmlViewNode extends Node {
1647
1764
  if (this._stale !== Stale.Nothing || this._laidOutWidth < 0) return false;
1648
1765
  if (reach.size > HOVER_RESTYLE_LIMIT) return false;
1649
1766
  // a first line's, a first letter's and a marker's styles are layout's
1650
- const pseudoRules = cascade.hoverPseudo;
1651
1767
  if (pseudoRules === HOVER_PSEUDO_OTHER) return false;
1652
1768
 
1653
- // whether the rules that test the pointer answer differently for an
1654
- // element than they did before the move
1655
- const before = { hovered: new Set(was), active: EMPTY_SET };
1769
+ // whether the rules that test the pointer or the focus answer
1770
+ // differently for an element than they did before the change
1656
1771
  const flips = new Map<Element, boolean>();
1657
1772
  const flipped = (el: Element): boolean => {
1658
1773
  let flip = flips.get(el);
1659
1774
  if (flip === undefined) {
1660
- flip = cascade.pointerChanged(el, before);
1775
+ flip = cascade.pointerChanged(el, before, beforeFocus);
1661
1776
  flips.set(el, flip);
1662
1777
  }
1663
1778
  return flip;
@@ -1675,6 +1790,8 @@ export class HtmlViewNode extends Node {
1675
1790
  let reaches = false;
1676
1791
  let reorders = false;
1677
1792
  const moving = new Set<Element>();
1793
+ /** Whether a control's box was restyled: its widget is told. */
1794
+ let widgets = false;
1678
1795
  const styleOf = (el: Element): ComputedStyle | null => {
1679
1796
  const kept = tree.styles.get(el);
1680
1797
  if (!kept) return null;
@@ -1711,7 +1828,39 @@ export class HtmlViewNode extends Node {
1711
1828
  styleOf(el);
1712
1829
  if (refused) return false;
1713
1830
  }
1714
- if (!changed.size && pseudoRules === HOVER_PSEUDO_NONE) return true;
1831
+ // The drawings the move reached. What the rules give the shapes in one
1832
+ // is no box's style (`BoxTree.shapeStyler`), so it is asked for again:
1833
+ // of a drawing whose own style changed — its colour, a custom property
1834
+ // — and of each where a rule that reaches into a drawing tests the
1835
+ // pointer, `a:hover svg path`. One with another answer is painted
1836
+ // again; one never painted has none yet, and asks when it is.
1837
+ const redrawn: [Box, ShapeStyles | null][] = [];
1838
+ if (tree.shapeStyler) {
1839
+ for (const el of reach) {
1840
+ if (tagOf(el) !== 'svg') continue;
1841
+ const style = fresh.get(el);
1842
+ if (!style) continue;
1843
+ if (!changed.has(el) && !cascade.shapesFollowPointer) continue;
1844
+ const box = this._firstBoxesOf(tree).get(el);
1845
+ const was = box && SHAPE_STYLES.get(box);
1846
+ if (!box || was === undefined) continue;
1847
+ const shapes = cascade.shapeStyles(el, style, tree.shapeCopies);
1848
+ if (shapes?.key !== was?.key) redrawn.push([box, shapes]);
1849
+ }
1850
+ }
1851
+ const redraw = (): Rect[] => {
1852
+ const inks: Rect[] = [];
1853
+ for (const [box, shapes] of redrawn) {
1854
+ SHAPE_STYLES.set(box, shapes);
1855
+ const ink = inkOf(box);
1856
+ if (ink) inks.push(ink);
1857
+ }
1858
+ return inks;
1859
+ };
1860
+ if (!changed.size && pseudoRules === HOVER_PSEUDO_NONE) {
1861
+ this._repaintInk(redraw());
1862
+ return true;
1863
+ }
1715
1864
  for (const el of changed.keys()) {
1716
1865
  const tag = tagOf(el);
1717
1866
  // their backgrounds are the canvas's
@@ -1786,11 +1935,20 @@ export class HtmlViewNode extends Node {
1786
1935
  if (!style) continue;
1787
1936
  const kept = tree.styles.get(el)!.style;
1788
1937
  // a style derived from the element's, or drawn from it somewhere
1789
- // else — a widget mounted beside the document takes its look from
1790
- // it. An image, a drawing or a rule is drawn here, from its box,
1791
- // like any.
1938
+ // else. A widget mounted beside the document takes its look from
1939
+ // it, and is told again once it is restyled — unless the page took
1940
+ // to drawing the field, or stopped, which is another size of box.
1941
+ // An image, a drawing or a rule is drawn here, from its box, like
1942
+ // any.
1792
1943
  if (box.style !== kept) return false;
1793
- if (box.marker || WIDGETS.has(box.replaced)) return false;
1944
+ if (box.marker) return false;
1945
+ if (WIDGETS.has(box.replaced)) {
1946
+ const kind = box.replaced!;
1947
+ if (styledField(kind, kept) !== styledField(kind, style)) {
1948
+ return false;
1949
+ }
1950
+ widgets = true;
1951
+ }
1794
1952
  if (moving.has(el) && !movable(box, kept, style)) return false;
1795
1953
  ink = !quiet.has(el);
1796
1954
  } else if (above) {
@@ -1830,7 +1988,10 @@ export class HtmlViewNode extends Node {
1830
1988
  }
1831
1989
  }
1832
1990
  }
1833
- if (!restyled.length) return true;
1991
+ if (!restyled.length) {
1992
+ this._repaintInk(redraw());
1993
+ return true;
1994
+ }
1834
1995
 
1835
1996
  // the text: each layout holding a run of a restyled box's, made again
1836
1997
  // with its new ink, where it comes out the same shape
@@ -1883,6 +2044,7 @@ export class HtmlViewNode extends Node {
1883
2044
  for (const [el, style] of changed) {
1884
2045
  tree.styles.set(el, { style, inFlex: tree.styles.get(el)!.inFlex });
1885
2046
  }
2047
+ for (const ink of redraw()) inks.push(ink);
1886
2048
  // A box that moved moves what is in it, as layout moved it the first
1887
2049
  // time (`applyRelativeOffsets`), and a later layout moves it the same
1888
2050
  // way; where it reached the document's end, before or after, the
@@ -1906,7 +2068,7 @@ export class HtmlViewNode extends Node {
1906
2068
  relayout = true;
1907
2069
  }
1908
2070
  }
1909
- if (moved.length) this._reportControls();
2071
+ if (moved.length || widgets) this._reportControls();
1910
2072
  if (relayout) {
1911
2073
  this._invalidate(Stale.Layout);
1912
2074
  return true;
@@ -1923,14 +2085,7 @@ export class HtmlViewNode extends Node {
1923
2085
  * of: made on the first move over a tree, which is one walk of it. */
1924
2086
  private _firstBoxes: { tree: BoxTree; of: Map<Element, Box> } | null = null;
1925
2087
 
1926
- /**
1927
- * The blocks a restyle of `changed` has to look in: for each element, the
1928
- * box its text is set in the lines of — the nearest box around its first
1929
- * that is not an inline one — which holds every box that takes its style
1930
- * from it, the pieces a block in it broke it into among them
1931
- * (`breakAround`). One that is inside another is left to it.
1932
- */
1933
- private _blocksOf(tree: BoxTree, changed: Iterable<Element>): Box[] {
2088
+ private _firstBoxesOf(tree: BoxTree): Map<Element, Box> {
1934
2089
  let index = this._firstBoxes;
1935
2090
  if (index?.tree !== tree) {
1936
2091
  const of = new Map<Element, Box>();
@@ -1944,9 +2099,21 @@ export class HtmlViewNode extends Node {
1944
2099
  }
1945
2100
  index = this._firstBoxes = { tree, of };
1946
2101
  }
2102
+ return index.of;
2103
+ }
2104
+
2105
+ /**
2106
+ * The blocks a restyle of `changed` has to look in: for each element, the
2107
+ * box its text is set in the lines of — the nearest box around its first
2108
+ * that is not an inline one — which holds every box that takes its style
2109
+ * from it, the pieces a block in it broke it into among them
2110
+ * (`breakAround`). One that is inside another is left to it.
2111
+ */
2112
+ private _blocksOf(tree: BoxTree, changed: Iterable<Element>): Box[] {
2113
+ const first = this._firstBoxesOf(tree);
1947
2114
  const blocks = new Set<Box>();
1948
2115
  for (const el of changed) {
1949
- let box: Box | null = index.of.get(el) ?? null;
2116
+ let box: Box | null = first.get(el) ?? null;
1950
2117
  while (
1951
2118
  box?.parent &&
1952
2119
  (box.kind === 'inline' || box.kind === 'text' || box.kind === 'break')