@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
package/src/html/node.ts CHANGED
@@ -59,7 +59,8 @@ import {
59
59
  } from './css/cascade.js';
60
60
  import type { HoverTouch, KeptStyles, MetricFace } from './css/cascade.js';
61
61
  import { mediaMatches, parseStylesheet } from './css/parse.js';
62
- import type { Stylesheet } from './css/parse.js';
62
+ import type { MediaCondition, Stylesheet } from './css/parse.js';
63
+ import type { ShapeStyles } from './css/shapes.js';
63
64
  import { uaStylesheet } from './css/ua.js';
64
65
  import type { ComputedStyle, RootLook } from './css/style.js';
65
66
  import {
@@ -67,12 +68,17 @@ import {
67
68
  buildBoxes,
68
69
  CONTENT_IMAGES,
69
70
  CLEARED_FROM,
71
+ COLUMN_LINES,
72
+ COLUMN_PIECES,
73
+ COLUMN_ROWS,
70
74
  CUT_BLOCKS,
71
75
  GENERATED_FROM,
72
76
  INLINE_OFFSETS,
73
77
  LINE_BOX_RAISES,
78
+ SHAPE_STYLES,
74
79
  SHIFTED_LINES,
75
80
  TEXT_SHIFTS,
81
+ columned,
76
82
  } from './layout/boxes.js';
77
83
  import type {
78
84
  Box,
@@ -82,8 +88,10 @@ import type {
82
88
  ReplacedKind,
83
89
  TextLayoutLike,
84
90
  } from './layout/boxes.js';
85
- import { layoutDocument, retranslate, transformed } from './layout/block.js';
91
+ import { layoutDocument, placedMatrix, retranslate } from './layout/block.js';
92
+ import { invert, mapPoint, mapRect, transformed } from './css/transform.js';
86
93
  import { TextLayoutCache } from './layout/cache.js';
94
+ import { weightAxes } from './layout/axes.js';
87
95
  import { shapingSafe } from './layout/shaping.js';
88
96
  import { SurfaceCache, newSurface } from './surfaces.js';
89
97
  import type { SurfaceLike } from './surfaces.js';
@@ -109,10 +117,16 @@ import {
109
117
  hoistNegative,
110
118
  holds,
111
119
  holdsAbsolute,
120
+ inClip,
121
+ inClipPath,
122
+ onLine,
123
+ ownBounds,
112
124
  paintDocument,
125
+ pathClips,
113
126
  queryChildIndex,
114
127
  selectionRows,
115
128
  stackLayers,
129
+ stacksLayers,
116
130
  } from './paint.js';
117
131
  import type { PaintContext } from './paint.js';
118
132
  import { controlRectsOf, measureControl } from './controls.js';
@@ -716,15 +730,23 @@ export class HtmlViewNode extends Node {
716
730
  place: (sheet: Stylesheet, element: Element) => void,
717
731
  depth = 0,
718
732
  chain: Set<string> = new Set(),
733
+ /** The media queries `sheet` is itself under, where it was imported
734
+ * under some. */
735
+ under: MediaCondition[][] | null = null,
719
736
  ): void {
720
- for (const url of sheet.imports) {
737
+ for (let i = 0; i < sheet.imports.length; i += 1) {
738
+ const url = sheet.imports[i];
739
+ // what it imports is under its import's media queries, and under
740
+ // the ones the sheet importing it is under
741
+ const conditions = sheet.importConditions[i];
742
+ const media = conditions ? [...(under ?? []), conditions] : under;
721
743
  this._resources.request({ url, kind: 'stylesheet', element });
722
744
  const fetched = this._resources.stylesheet(url, [encoding]);
723
745
  const base = this._resources.sheetBase(url);
724
746
  seen.push({ url, text: fetched?.text ?? null, base });
725
747
  const key = base ?? url;
726
748
  if (!fetched || depth >= MAX_IMPORT_DEPTH || chain.has(key)) continue;
727
- const imported = parseStylesheet(fetched.text, 0, layers, base);
749
+ const imported = parseStylesheet(fetched.text, 0, layers, base, media);
728
750
  chain.add(key);
729
751
  this._placeImports(
730
752
  imported,
@@ -735,6 +757,7 @@ export class HtmlViewNode extends Node {
735
757
  place,
736
758
  depth + 1,
737
759
  chain,
760
+ media,
738
761
  );
739
762
  chain.delete(key);
740
763
  place(imported, element);
@@ -849,11 +872,22 @@ export class HtmlViewNode extends Node {
849
872
  return height && height > 0 ? height : 600;
850
873
  }
851
874
 
875
+ /** The engine this document lays text out with, and the one it is over:
876
+ * one object while the engine is, since caches are kept by it. */
877
+ private _engine: { over: FontsLike; fonts: FontsLike } | null = null;
878
+
852
879
  private _fonts(): FontsLike | null {
853
880
  const fonts = (this.app as { fonts?: FontsLike } | null)?.fonts;
881
+ if (!fonts) return null;
854
882
  // a face the engine cannot shape from costs its characters, not the
855
883
  // document (`shaping.ts`)
856
- return fonts ? shapingSafe(fonts) : null;
884
+ const safe = shapingSafe(fonts);
885
+ if (this._engine?.over !== safe) {
886
+ // and a variable face of the document's is set at the weight its
887
+ // rule has for a style's (`axes.ts`)
888
+ this._engine = { over: safe, fonts: weightAxes(safe, this._webFonts) };
889
+ }
890
+ return this._engine.fonts;
857
891
  }
858
892
 
859
893
  /** The text layouts the last pass made, for this one to reuse. */
@@ -1072,7 +1106,7 @@ export class HtmlViewNode extends Node {
1072
1106
  // The boxes are device pixels; each rect becomes the style of a widget
1073
1107
  // mounted beside this element, and a style is logical.
1074
1108
  const s = this._scale;
1075
- const rects = controlRectsOf(tree).map((r) =>
1109
+ const rects = controlRectsOf(tree, s).map((r) =>
1076
1110
  s === 1
1077
1111
  ? r
1078
1112
  : {
@@ -1082,6 +1116,14 @@ export class HtmlViewNode extends Node {
1082
1116
  width: r.width / s,
1083
1117
  height: r.height / s,
1084
1118
  fontSize: r.fontSize / s,
1119
+ ...(r.clip && {
1120
+ clip: {
1121
+ x: r.clip.x / s,
1122
+ y: r.clip.y / s,
1123
+ width: r.clip.width / s,
1124
+ height: r.clip.height / s,
1125
+ },
1126
+ }),
1085
1127
  ...(r.bare && {
1086
1128
  bare: {
1087
1129
  ...r.bare,
@@ -1396,6 +1438,9 @@ export class HtmlViewNode extends Node {
1396
1438
  rect = { x: caret.x, y: caret.y, width: 0, height: caret.height };
1397
1439
  }
1398
1440
  }
1441
+ // where it is drawn: the rectangle around it, turned and scaled as the
1442
+ // boxes it is in are, and itself (`getBoundingClientRect`, CSSOM View)
1443
+ rect = throughTransforms(rect, box);
1399
1444
  const s = this._scale;
1400
1445
  return {
1401
1446
  x: rect.x / s,
@@ -1668,7 +1713,39 @@ export class HtmlViewNode extends Node {
1668
1713
  styleOf(el);
1669
1714
  if (refused) return false;
1670
1715
  }
1671
- if (!changed.size && pseudoRules === HOVER_PSEUDO_NONE) return true;
1716
+ // The drawings the move reached. What the rules give the shapes in one
1717
+ // is no box's style (`BoxTree.shapeStyler`), so it is asked for again:
1718
+ // of a drawing whose own style changed — its colour, a custom property
1719
+ // — and of each where a rule that reaches into a drawing tests the
1720
+ // pointer, `a:hover svg path`. One with another answer is painted
1721
+ // again; one never painted has none yet, and asks when it is.
1722
+ const redrawn: [Box, ShapeStyles | null][] = [];
1723
+ if (tree.shapeStyler) {
1724
+ for (const el of reach) {
1725
+ if (tagOf(el) !== 'svg') continue;
1726
+ const style = fresh.get(el);
1727
+ if (!style) continue;
1728
+ if (!changed.has(el) && !cascade.shapesFollowPointer) continue;
1729
+ const box = this._firstBoxesOf(tree).get(el);
1730
+ const was = box && SHAPE_STYLES.get(box);
1731
+ if (!box || was === undefined) continue;
1732
+ const shapes = cascade.shapeStyles(el, style);
1733
+ if (shapes?.key !== was?.key) redrawn.push([box, shapes]);
1734
+ }
1735
+ }
1736
+ const redraw = (): Rect[] => {
1737
+ const inks: Rect[] = [];
1738
+ for (const [box, shapes] of redrawn) {
1739
+ SHAPE_STYLES.set(box, shapes);
1740
+ const ink = inkOf(box);
1741
+ if (ink) inks.push(ink);
1742
+ }
1743
+ return inks;
1744
+ };
1745
+ if (!changed.size && pseudoRules === HOVER_PSEUDO_NONE) {
1746
+ this._repaintInk(redraw());
1747
+ return true;
1748
+ }
1672
1749
  for (const el of changed.keys()) {
1673
1750
  const tag = tagOf(el);
1674
1751
  // their backgrounds are the canvas's
@@ -1787,7 +1864,10 @@ export class HtmlViewNode extends Node {
1787
1864
  }
1788
1865
  }
1789
1866
  }
1790
- if (!restyled.length) return true;
1867
+ if (!restyled.length) {
1868
+ this._repaintInk(redraw());
1869
+ return true;
1870
+ }
1791
1871
 
1792
1872
  // the text: each layout holding a run of a restyled box's, made again
1793
1873
  // with its new ink, where it comes out the same shape
@@ -1840,6 +1920,7 @@ export class HtmlViewNode extends Node {
1840
1920
  for (const [el, style] of changed) {
1841
1921
  tree.styles.set(el, { style, inFlex: tree.styles.get(el)!.inFlex });
1842
1922
  }
1923
+ for (const ink of redraw()) inks.push(ink);
1843
1924
  // A box that moved moves what is in it, as layout moved it the first
1844
1925
  // time (`applyRelativeOffsets`), and a later layout moves it the same
1845
1926
  // way; where it reached the document's end, before or after, the
@@ -1880,14 +1961,7 @@ export class HtmlViewNode extends Node {
1880
1961
  * of: made on the first move over a tree, which is one walk of it. */
1881
1962
  private _firstBoxes: { tree: BoxTree; of: Map<Element, Box> } | null = null;
1882
1963
 
1883
- /**
1884
- * The blocks a restyle of `changed` has to look in: for each element, the
1885
- * box its text is set in the lines of — the nearest box around its first
1886
- * that is not an inline one — which holds every box that takes its style
1887
- * from it, the pieces a block in it broke it into among them
1888
- * (`breakAround`). One that is inside another is left to it.
1889
- */
1890
- private _blocksOf(tree: BoxTree, changed: Iterable<Element>): Box[] {
1964
+ private _firstBoxesOf(tree: BoxTree): Map<Element, Box> {
1891
1965
  let index = this._firstBoxes;
1892
1966
  if (index?.tree !== tree) {
1893
1967
  const of = new Map<Element, Box>();
@@ -1901,9 +1975,21 @@ export class HtmlViewNode extends Node {
1901
1975
  }
1902
1976
  index = this._firstBoxes = { tree, of };
1903
1977
  }
1978
+ return index.of;
1979
+ }
1980
+
1981
+ /**
1982
+ * The blocks a restyle of `changed` has to look in: for each element, the
1983
+ * box its text is set in the lines of — the nearest box around its first
1984
+ * that is not an inline one — which holds every box that takes its style
1985
+ * from it, the pieces a block in it broke it into among them
1986
+ * (`breakAround`). One that is inside another is left to it.
1987
+ */
1988
+ private _blocksOf(tree: BoxTree, changed: Iterable<Element>): Box[] {
1989
+ const first = this._firstBoxesOf(tree);
1904
1990
  const blocks = new Set<Box>();
1905
1991
  for (const el of changed) {
1906
- let box: Box | null = index.of.get(el) ?? null;
1992
+ let box: Box | null = first.get(el) ?? null;
1907
1993
  while (
1908
1994
  box?.parent &&
1909
1995
  (box.kind === 'inline' || box.kind === 'text' || box.kind === 'break')
@@ -2342,6 +2428,7 @@ function sameRects(a: ControlRect[], b: ControlRect[]): boolean {
2342
2428
  p.fontFamily !== q.fontFamily ||
2343
2429
  p.fontSize !== q.fontSize ||
2344
2430
  p.opacity !== q.opacity ||
2431
+ !sameClip(p.clip, q.clip) ||
2345
2432
  !sameBare(p.bare, q.bare)
2346
2433
  ) {
2347
2434
  return false;
@@ -2350,6 +2437,13 @@ function sameRects(a: ControlRect[], b: ControlRect[]): boolean {
2350
2437
  return true;
2351
2438
  }
2352
2439
 
2440
+ function sameClip(a?: ControlRect['clip'], b?: ControlRect['clip']): boolean {
2441
+ if (!a || !b) return a === b;
2442
+ return (
2443
+ a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height
2444
+ );
2445
+ }
2446
+
2353
2447
  function sameBare(a?: BareField, b?: BareField): boolean {
2354
2448
  if (!a || !b) return a === b;
2355
2449
  return (
@@ -2399,7 +2493,16 @@ function nearestText(box: Box, x: number, y: number): number | null {
2399
2493
  const distance = dy * 4 + dx;
2400
2494
  if (distance >= bestDistance) continue;
2401
2495
  bestDistance = distance;
2402
- const local = text.layout.indexAt(x - text.drawX, y - text.drawY);
2496
+ // in the row that is this line's, where the layout's other lines are
2497
+ // in another column and a point past this one is not over them
2498
+ let row = y - text.drawY;
2499
+ if (columned.any && COLUMN_ROWS.has(text)) {
2500
+ row = Math.max(
2501
+ natural.y,
2502
+ Math.min(row, natural.y + natural.height - 0.01),
2503
+ );
2504
+ }
2505
+ const local = text.layout.indexAt(x - text.drawX, row);
2403
2506
  const offsets = layoutOffsetsOf(text.layout);
2404
2507
  const units = offsets.length
2405
2508
  ? offsets[Math.max(0, Math.min(local, offsets.length - 1))]
@@ -2413,14 +2516,37 @@ function nearestText(box: Box, x: number, y: number): number | null {
2413
2516
  axisDistance(y, node.boundsY, node.boundsHeight) * 4 +
2414
2517
  axisDistance(x, node.boundsX, node.boundsWidth);
2415
2518
  if (boundsDistance >= bestDistance) return;
2519
+ // in a box painted through a matrix, the point taken back through it
2520
+ // (`deepestAt`): the text is where it was laid out
2521
+ const matrix = placedMatrix(node);
2522
+ if (!matrix) {
2523
+ visitIn(node);
2524
+ return;
2525
+ }
2526
+ const back = invert(matrix);
2527
+ if (!back) return;
2528
+ const px = x;
2529
+ const py = y;
2530
+ [x, y] = mapPoint(back, px, py);
2531
+ try {
2532
+ visitIn(node);
2533
+ } finally {
2534
+ x = px;
2535
+ y = py;
2536
+ }
2537
+ };
2416
2538
 
2539
+ const visitIn = (node: Box): void => {
2417
2540
  const lines = node.lines;
2418
2541
  if (lines?.length) {
2419
2542
  // The line nearest in y, by binary search; then outward both ways
2420
2543
  // while vertical distance alone could still beat the best.
2421
2544
  let lo = 0;
2422
2545
  let hi = lines.length;
2423
- while (lo < hi) {
2546
+ // the lines columns took apart are in no order down the page: each
2547
+ // is tried
2548
+ const apart = columned.any && COLUMN_LINES.has(lines);
2549
+ while (!apart && lo < hi) {
2424
2550
  const mid = (lo + hi) >> 1;
2425
2551
  if (lines[mid].y > y) hi = mid;
2426
2552
  else lo = mid + 1;
@@ -2431,8 +2557,10 @@ function nearestText(box: Box, x: number, y: number): number | null {
2431
2557
  tryLine(lines[i]);
2432
2558
  }
2433
2559
  for (let i = lo; i < lines.length; i += 1) {
2434
- if (axisDistance(y, lines[i].y, lines[i].height) * 4 >= bestDistance)
2560
+ if (axisDistance(y, lines[i].y, lines[i].height) * 4 >= bestDistance) {
2561
+ if (apart) continue;
2435
2562
  break;
2563
+ }
2436
2564
  tryLine(lines[i]);
2437
2565
  }
2438
2566
  }
@@ -2519,7 +2647,16 @@ function caretAt(
2519
2647
  const caret = text.layout.caretPosition(
2520
2648
  codePointAtOffset(offsets, layoutUnits),
2521
2649
  );
2522
- found = { x: text.drawX + caret.x, y: line.y, height: line.height };
2650
+ const at = throughTransforms(
2651
+ {
2652
+ x: text.drawX + caret.x,
2653
+ y: line.y,
2654
+ width: 0,
2655
+ height: line.height,
2656
+ },
2657
+ node,
2658
+ );
2659
+ found = { x: at.x, y: at.y, height: at.height };
2523
2660
  return;
2524
2661
  }
2525
2662
  }
@@ -2534,6 +2671,21 @@ function caretAt(
2534
2671
  return found;
2535
2672
  }
2536
2673
 
2674
+ /**
2675
+ * A rectangle in a box's own coordinates — the ones it and what it holds
2676
+ * were laid out in — as the rectangle around where it is drawn: through
2677
+ * the matrix of each box from `from` up that is painted through one
2678
+ * (`placedMatrix`). The rectangle itself, in a document that transforms
2679
+ * nothing but by moving it.
2680
+ */
2681
+ function throughTransforms(rect: Rect, from: Box | null): Rect {
2682
+ for (let at = from; at; at = at.parent) {
2683
+ const matrix = placedMatrix(at);
2684
+ if (matrix) rect = mapRect(matrix, rect.x, rect.y, rect.width, rect.height);
2685
+ }
2686
+ return rect;
2687
+ }
2688
+
2537
2689
  /** Every band a document range covers, in window coordinates. The subtree
2538
2690
  * text ranges prune the walk to the boxes the range actually crosses, so a
2539
2691
  * drag's small range costs its own paragraphs; only Ctrl+A pays for all. */
@@ -2689,6 +2841,19 @@ function lineBands(
2689
2841
  height: rows.height,
2690
2842
  });
2691
2843
  }
2844
+ // The spaces a piece of a line ends on, where the line goes on
2845
+ // after them: the engine stripped them from the piece it laid out
2846
+ // as a line, and they are the text's all the same, in the elements
2847
+ // that hold its end — a link whose text ends in a space, before
2848
+ // the next link, is that space wider than its letters
2849
+ if (text.trail && b >= text.textEnd && !rtlLine(natural)) {
2850
+ out.push({
2851
+ x: dx + natural.x + natural.width + text.drawX - moved,
2852
+ y: dy + rows.y,
2853
+ width: text.trail,
2854
+ height: rows.height,
2855
+ });
2856
+ }
2692
2857
  // the spaces `pre-wrap` keeps that the line ends on, which have no
2693
2858
  // run: the engine hung them past the line (`LineText.hung`)
2694
2859
  for (const space of text.hung ?? []) {
@@ -2706,6 +2871,14 @@ function lineBands(
2706
2871
  }
2707
2872
  }
2708
2873
 
2874
+ /** Whether a laid-out line's last run reads right to left, its end at its
2875
+ * left. */
2876
+ function rtlLine(natural: {
2877
+ runs: { run?: { direction?: string } }[];
2878
+ }): boolean {
2879
+ return natural.runs[natural.runs.length - 1]?.run?.direction === 'rtl';
2880
+ }
2881
+
2709
2882
  /** Whether a line's text is where the line ends: its last, with nothing
2710
2883
  * placed after it — an inline-block or an image past its content. */
2711
2884
  function endsLine(
@@ -2732,12 +2905,27 @@ function collectBands(
2732
2905
  ): void {
2733
2906
  if (box.subtreeTextEnd <= box.subtreeTextStart) return;
2734
2907
  if (box.subtreeTextEnd <= from || box.subtreeTextStart >= to) return;
2908
+ const first = out.length;
2735
2909
  if (box.lines) lineBands(box.lines, from, to, dx, dy, out, false, ink);
2736
2910
  // Atomics are ordinary children, reached below.
2737
2911
  for (const child of box.children) {
2738
2912
  if (child.kind === 'text' || child.kind === 'break') continue;
2739
2913
  collectBands(child, from, to, dx, dy, out, ink);
2740
2914
  }
2915
+ // where a box painted through a matrix draws them
2916
+ const matrix = out.length > first ? placedMatrix(box) : null;
2917
+ if (!matrix) return;
2918
+ for (let i = first; i < out.length; i += 1) {
2919
+ const band = out[i];
2920
+ const to = mapRect(
2921
+ matrix,
2922
+ band.x - dx,
2923
+ band.y - dy,
2924
+ band.width,
2925
+ band.height,
2926
+ );
2927
+ out[i] = { ...band, ...to, x: to.x + dx, y: to.y + dy };
2928
+ }
2741
2929
  }
2742
2930
 
2743
2931
  /** The layers of a hit test, in the order CSS paints them within a
@@ -2777,7 +2965,22 @@ function deepestAt(
2777
2965
  // are painted in `z-index` order and then the document's (`byZIndex`),
2778
2966
  // the negative ones under the flow: the Zen Garden's `›` has
2779
2967
  // `z-index: 3` over the bar the "View All Designs" link fills after it.
2780
- const take = (el: Element, key: readonly number[], text: boolean) => {
2968
+ // Nor is a box the pointer passes through under it: one that is not
2969
+ // visible, which is no target for the pointer in any browser (CSS 2.1
2970
+ // 11.2 has it invisible, and a hit test finds what is seen), or one with
2971
+ // `pointer-events: none` (CSS UI 4, 5.2). Both are inherited, and what is
2972
+ // in such a box may say otherwise, so the walk goes on through it. A
2973
+ // site's closed menu is laid out over its page, hidden: GitHub's covered
2974
+ // its repository's tabs, and the pointer over a tab was over the menu.
2975
+ const take = (
2976
+ el: Element,
2977
+ style: ComputedStyle,
2978
+ key: readonly number[],
2979
+ text: boolean,
2980
+ ) => {
2981
+ if (style.visibility !== 'visible' || style.pointerEvents === 'none') {
2982
+ return;
2983
+ }
2781
2984
  if (compareKeys(key, foundKey) < 0) return;
2782
2985
  found = el;
2783
2986
  foundKey = key;
@@ -2821,12 +3024,72 @@ function deepestAt(
2821
3024
  atViewport = false;
2822
3025
  }
2823
3026
  };
3027
+ // A box painted through a matrix (`placedMatrix`) is under the point
3028
+ // where the matrix puts it: inside the rectangle around what it draws
3029
+ // there, the point is taken back through the matrix, into the
3030
+ // coordinates the box and all it holds were laid out in, and the walk
3031
+ // goes on with that. An icon turned a quarter is pressed where it is
3032
+ // drawn; a box flattened to nothing is nowhere.
2824
3033
  const enterAt = (
2825
3034
  child: Box,
2826
3035
  context: readonly number[],
2827
3036
  clipped: readonly Box[],
3037
+ ): void => {
3038
+ const matrix = placedMatrix(child);
3039
+ if (!matrix) {
3040
+ enterIn(child, context, clipped, null);
3041
+ return;
3042
+ }
3043
+ if (
3044
+ x < child.boundsX ||
3045
+ x >= child.boundsX + child.boundsWidth ||
3046
+ y < child.boundsY ||
3047
+ y >= child.boundsY + child.boundsHeight
3048
+ ) {
3049
+ return;
3050
+ }
3051
+ const back = invert(matrix);
3052
+ if (!back) return;
3053
+ const px = x;
3054
+ const py = y;
3055
+ [x, y] = mapPoint(back, px, py);
3056
+ try {
3057
+ enterIn(child, context, clipped, ownBounds(child));
3058
+ } finally {
3059
+ x = px;
3060
+ y = py;
3061
+ }
3062
+ };
3063
+ const enterIn = (
3064
+ child: Box,
3065
+ context: readonly number[],
3066
+ clipped: readonly Box[],
3067
+ /** What the box draws in its own coordinates, where those are not the
3068
+ * ones its bounds are in: a box painted through a matrix. */
3069
+ bounds: Rect | null,
2828
3070
  ): void => {
2829
3071
  const style = child.style;
3072
+ // What a `clip-path` cuts away of a box it cuts away of all the box
3073
+ // holds, whatever that is positioned from (CSS Masking 1, 5.1), and
3074
+ // nothing not drawn is under a pointer: a label a page hides for a
3075
+ // screen reader alone under `clip-path: inset(50%)` took the hover and
3076
+ // the press of what was drawn where it lay.
3077
+ if (pathClips(child) && !inClipPath(child, x, y)) return;
3078
+ // `clip` is not that rule as paint has it. It shows the part of an
3079
+ // absolute box it names (CSS 2.1 11.1.2), and past that part nothing is
3080
+ // drawn of the box, nor of what is painted with it. A stacking context
3081
+ // paints all it holds, inside its clip. A box that is none leaves its
3082
+ // positioned boxes to the context around it, which cuts each to the
3083
+ // clips its containing block is under (`clipsFor`): this one for all
3084
+ // but a fixed box, which is under none and is drawn past it. So past
3085
+ // its clip the first is left, and the second is walked as a box that
3086
+ // clips its overflow is past its edge — whether or not the point is in
3087
+ // its own rectangle, which a clip may show only a part of. Both were
3088
+ // walked as if they showed whole: a label hidden for a screen reader
3089
+ // alone under `clip: rect(0, 0, 0, 0)` took the hover and the press of
3090
+ // the link drawn where it lay.
3091
+ let cut = !inClip(child, x, y);
3092
+ if (cut && stacksLayers(child)) return;
2830
3093
  if (
2831
3094
  style.position !== 'static' ||
2832
3095
  (child.parent?.kind === 'flex' && typeof style.zIndex === 'number')
@@ -2834,6 +3097,14 @@ function deepestAt(
2834
3097
  // a flex item with a `z-index` is layered unpositioned (`layered`)
2835
3098
  const z = style.zIndex === 'auto' ? 0 : style.zIndex;
2836
3099
  context = [...context, z < 0 ? HIT_NEGATIVE : HIT_POSITIONED, z];
3100
+ } else if (
3101
+ transformed(style) &&
3102
+ child.kind !== 'inline' &&
3103
+ child.parent &&
3104
+ !onLine(child.parent, child)
3105
+ ) {
3106
+ // a transformed box is painted with the positioned ones (`layered`)
3107
+ context = [...context, HIT_POSITIONED, 0];
2837
3108
  } else if (style.float !== 'none') context = [...context, HIT_FLOAT];
2838
3109
  if (clipped.length !== 0 && child.outOfFlow) {
2839
3110
  const containing = containingBlockOf(child);
@@ -2841,34 +3112,46 @@ function deepestAt(
2841
3112
  ? clipped.filter((clip) => holds(clip, containing))
2842
3113
  : [];
2843
3114
  }
2844
- const inside =
2845
- x >= child.x &&
2846
- x < child.x + child.width &&
2847
- y >= child.y &&
2848
- y < child.y + child.height;
3115
+ // in one of its pieces, where columns broke it: its rect takes in the
3116
+ // columns between them, which are other boxes'
3117
+ const pieces = columned.any ? COLUMN_PIECES.get(child) : undefined;
3118
+ const inside = pieces
3119
+ ? pieces.some(
3120
+ (piece) =>
3121
+ x >= piece.x &&
3122
+ x < piece.x + piece.width &&
3123
+ y >= piece.y &&
3124
+ y < piece.y + piece.height,
3125
+ )
3126
+ : x >= child.x &&
3127
+ x < child.x + child.width &&
3128
+ y >= child.y &&
3129
+ y < child.y + child.height;
2849
3130
  const own = inside && hasRect(child);
2850
3131
  if (!own) {
2851
- const reach = child.boundsY;
2852
- const known =
2853
- Number.isFinite(reach) &&
2854
- (child.boundsWidth > 0 || child.boundsHeight > 0);
3132
+ const left = bounds ? bounds.x : child.boundsX;
3133
+ const reach = bounds ? bounds.y : child.boundsY;
3134
+ const across = bounds ? bounds.width : child.boundsWidth;
3135
+ const down = bounds ? bounds.height : child.boundsHeight;
3136
+ const known = Number.isFinite(reach) && (across > 0 || down > 0);
2855
3137
  if (!known) {
2856
3138
  if (!inside) return;
2857
3139
  } else if (
2858
- x < child.boundsX ||
2859
- x >= child.boundsX + child.boundsWidth ||
3140
+ x < left ||
3141
+ x >= left + across ||
2860
3142
  y < reach ||
2861
- y >= reach + child.boundsHeight
3143
+ y >= reach + down
2862
3144
  ) {
2863
3145
  return;
2864
3146
  }
2865
- if (clipsOverflow(child)) {
2866
- if (!holdsAbsolute(child)) return;
2867
- clipped = [...clipped, child];
2868
- }
3147
+ if (clipsOverflow(child)) cut = true;
3148
+ }
3149
+ if (cut) {
3150
+ if (!holdsAbsolute(child)) return;
3151
+ clipped = [...clipped, child];
2869
3152
  }
2870
3153
  if (own && child.el && clipped.length === 0) {
2871
- take(child.el, [...context, HIT_BLOCK], false);
3154
+ take(child.el, style, [...context, HIT_BLOCK], false);
2872
3155
  }
2873
3156
  visit(child, context, clipped);
2874
3157
  };
@@ -2915,7 +3198,8 @@ function deepestAt(
2915
3198
  tree.textBoxes,
2916
3199
  text.spans.documentAt(run.start),
2917
3200
  );
2918
- if (owner) take(owner, [...context, HIT_INLINE], true);
3201
+ const style = owner && tree.styles.get(owner)?.style;
3202
+ if (style) take(owner, style, [...context, HIT_INLINE], true);
2919
3203
  }
2920
3204
  }
2921
3205
  }
@@ -2986,6 +3270,9 @@ const PAINT_ONLY = new Set([
2986
3270
  'color',
2987
3271
  'textFillColor',
2988
3272
  'cursor',
3273
+ 'pointerEvents',
3274
+ 'fill',
3275
+ 'stroke',
2989
3276
  'backgroundColor',
2990
3277
  'borderTopColor',
2991
3278
  'borderRightColor',
@@ -3002,6 +3289,8 @@ const PAINT_ONLY = new Set([
3002
3289
  'underlineThickness',
3003
3290
  'underlineOffset',
3004
3291
  'lineThrough',
3292
+ 'lineThroughStyle',
3293
+ 'lineThroughThickness',
3005
3294
  ]);
3006
3295
 
3007
3296
  /** The fields of a run its ink is: what `runFor` takes from `PAINT_ONLY`. */
@@ -3012,6 +3301,8 @@ const INK_FIELDS = [
3012
3301
  'underlineOffset',
3013
3302
  'underlineThickness',
3014
3303
  'strike',
3304
+ 'strikeStyle',
3305
+ 'strikeThickness',
3015
3306
  ] as const;
3016
3307
 
3017
3308
  /** The fields of a run its shape is, which ink never changes. */
@@ -3065,10 +3356,20 @@ const INK_REACH = new Set([
3065
3356
  'outlineOffset',
3066
3357
  ]);
3067
3358
 
3359
+ /** What transforms a box: it moves, turns or scales where it is, and
3360
+ * nothing around it does (CSS Transforms 1, 3). */
3361
+ const TRANSFORM_FIELDS = new Set([
3362
+ 'translate',
3363
+ 'rotate',
3364
+ 'scale',
3365
+ 'transform',
3366
+ 'transformOrigin',
3367
+ ]);
3368
+
3068
3369
  /**
3069
3370
  * How two styles of one element differ, for a restyle in place: null where
3070
3371
  * they do not, a `HoverChange` where every difference is ink, how far ink
3071
- * reaches, a `z-index` that stays a stacking context's, or a translation —
3372
+ * reaches, a `z-index` that stays a stacking context's, or a transform —
3072
3373
  * none of which moves anything but the box and what it holds (CSS
3073
3374
  * Transforms 1: a transform does not affect layout) — and false where
3074
3375
  * anything else differs.
@@ -3107,7 +3408,7 @@ function hoverChange(
3107
3408
  change.order = true;
3108
3409
  continue;
3109
3410
  }
3110
- if (key === 'transformTranslate' || key === 'translate') {
3411
+ if (TRANSFORM_FIELDS.has(key)) {
3111
3412
  change.move = true;
3112
3413
  continue;
3113
3414
  }
@@ -3150,12 +3451,17 @@ function inkOf(box: Box): Rect | null {
3150
3451
  }
3151
3452
  if (!at || at.boundsY === Infinity) return null;
3152
3453
  if (!(at.boundsWidth > 0 && at.boundsHeight > 0)) return null;
3153
- return {
3154
- x: at.boundsX,
3155
- y: at.boundsY,
3156
- width: at.boundsWidth,
3157
- height: at.boundsHeight,
3158
- };
3454
+ // its bounds are in the coordinates of the box it is in, which a box
3455
+ // around it painted through a matrix draws somewhere else
3456
+ return throughTransforms(
3457
+ {
3458
+ x: at.boundsX,
3459
+ y: at.boundsY,
3460
+ width: at.boundsWidth,
3461
+ height: at.boundsHeight,
3462
+ },
3463
+ at.parent,
3464
+ );
3159
3465
  }
3160
3466
 
3161
3467
  /** Structural equality for a computed value: a number, a string, or the