@react-x11/components 0.13.0 → 0.14.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 (128) 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.map +1 -1
  6. package/dist/html/css/cascade.js +26 -4
  7. package/dist/html/css/cascade.js.map +1 -1
  8. package/dist/html/css/color.d.ts +18 -0
  9. package/dist/html/css/color.d.ts.map +1 -1
  10. package/dist/html/css/color.js +27 -0
  11. package/dist/html/css/color.js.map +1 -1
  12. package/dist/html/css/parse.d.ts +18 -4
  13. package/dist/html/css/parse.d.ts.map +1 -1
  14. package/dist/html/css/parse.js +66 -28
  15. package/dist/html/css/parse.js.map +1 -1
  16. package/dist/html/css/style.d.ts +128 -15
  17. package/dist/html/css/style.d.ts.map +1 -1
  18. package/dist/html/css/style.js +561 -144
  19. package/dist/html/css/style.js.map +1 -1
  20. package/dist/html/css/transform.d.ts +85 -0
  21. package/dist/html/css/transform.d.ts.map +1 -0
  22. package/dist/html/css/transform.js +425 -0
  23. package/dist/html/css/transform.js.map +1 -0
  24. package/dist/html/css/ua.d.ts.map +1 -1
  25. package/dist/html/css/ua.js +14 -2
  26. package/dist/html/css/ua.js.map +1 -1
  27. package/dist/html/css/values.d.ts +4 -0
  28. package/dist/html/css/values.d.ts.map +1 -1
  29. package/dist/html/css/values.js.map +1 -1
  30. package/dist/html/fonts.d.ts +55 -5
  31. package/dist/html/fonts.d.ts.map +1 -1
  32. package/dist/html/fonts.js +284 -23
  33. package/dist/html/fonts.js.map +1 -1
  34. package/dist/html/layout/axes.d.ts +14 -0
  35. package/dist/html/layout/axes.d.ts.map +1 -0
  36. package/dist/html/layout/axes.js +28 -0
  37. package/dist/html/layout/axes.js.map +1 -0
  38. package/dist/html/layout/block.d.ts +93 -12
  39. package/dist/html/layout/block.d.ts.map +1 -1
  40. package/dist/html/layout/block.js +389 -39
  41. package/dist/html/layout/block.js.map +1 -1
  42. package/dist/html/layout/boxes.d.ts +59 -1
  43. package/dist/html/layout/boxes.d.ts.map +1 -1
  44. package/dist/html/layout/boxes.js +22 -2
  45. package/dist/html/layout/boxes.js.map +1 -1
  46. package/dist/html/layout/cache.d.ts +1 -1
  47. package/dist/html/layout/cache.d.ts.map +1 -1
  48. package/dist/html/layout/cache.js +6 -0
  49. package/dist/html/layout/cache.js.map +1 -1
  50. package/dist/html/layout/css-grid.d.ts.map +1 -1
  51. package/dist/html/layout/css-grid.js +90 -18
  52. package/dist/html/layout/css-grid.js.map +1 -1
  53. package/dist/html/layout/flex.d.ts.map +1 -1
  54. package/dist/html/layout/flex.js +570 -83
  55. package/dist/html/layout/flex.js.map +1 -1
  56. package/dist/html/layout/inline.d.ts +3 -0
  57. package/dist/html/layout/inline.d.ts.map +1 -1
  58. package/dist/html/layout/inline.js +144 -54
  59. package/dist/html/layout/inline.js.map +1 -1
  60. package/dist/html/layout/multicol.d.ts +42 -0
  61. package/dist/html/layout/multicol.d.ts.map +1 -0
  62. package/dist/html/layout/multicol.js +897 -0
  63. package/dist/html/layout/multicol.js.map +1 -0
  64. package/dist/html/layout/shaping.d.ts +7 -0
  65. package/dist/html/layout/shaping.d.ts.map +1 -1
  66. package/dist/html/layout/shaping.js +25 -20
  67. package/dist/html/layout/shaping.js.map +1 -1
  68. package/dist/html/layout/table.d.ts.map +1 -1
  69. package/dist/html/layout/table.js +62 -18
  70. package/dist/html/layout/table.js.map +1 -1
  71. package/dist/html/node.d.ts +3 -0
  72. package/dist/html/node.d.ts.map +1 -1
  73. package/dist/html/node.js +263 -36
  74. package/dist/html/node.js.map +1 -1
  75. package/dist/html/paint.d.ts +79 -4
  76. package/dist/html/paint.d.ts.map +1 -1
  77. package/dist/html/paint.js +1213 -141
  78. package/dist/html/paint.js.map +1 -1
  79. package/dist/html/svg.d.ts +15 -2
  80. package/dist/html/svg.d.ts.map +1 -1
  81. package/dist/html/svg.js +249 -17
  82. package/dist/html/svg.js.map +1 -1
  83. package/dist/html/widgets.js +87 -7
  84. package/dist/html/widgets.js.map +1 -1
  85. package/dist/html/woff2.d.ts +14 -0
  86. package/dist/html/woff2.d.ts.map +1 -0
  87. package/dist/html/woff2.js +608 -0
  88. package/dist/html/woff2.js.map +1 -0
  89. package/dist/maps/controller.d.ts +17 -1
  90. package/dist/maps/controller.d.ts.map +1 -1
  91. package/dist/maps/controller.js +27 -7
  92. package/dist/maps/controller.js.map +1 -1
  93. package/dist/richtext/node.d.ts +8 -1
  94. package/dist/richtext/node.d.ts.map +1 -1
  95. package/dist/richtext/node.js +5 -1
  96. package/dist/richtext/node.js.map +1 -1
  97. package/dist/richtext/runs.d.ts +25 -6
  98. package/dist/richtext/runs.d.ts.map +1 -1
  99. package/dist/richtext/runs.js +240 -24
  100. package/dist/richtext/runs.js.map +1 -1
  101. package/package.json +1 -1
  102. package/src/html/controls.ts +85 -2
  103. package/src/html/css/cascade.ts +23 -0
  104. package/src/html/css/color.ts +29 -0
  105. package/src/html/css/parse.ts +71 -31
  106. package/src/html/css/style.ts +664 -147
  107. package/src/html/css/transform.ts +459 -0
  108. package/src/html/css/ua.ts +15 -2
  109. package/src/html/css/values.ts +4 -0
  110. package/src/html/fonts.ts +312 -29
  111. package/src/html/layout/axes.ts +62 -0
  112. package/src/html/layout/block.ts +444 -37
  113. package/src/html/layout/boxes.ts +65 -3
  114. package/src/html/layout/cache.ts +6 -0
  115. package/src/html/layout/css-grid.ts +95 -22
  116. package/src/html/layout/flex.ts +671 -77
  117. package/src/html/layout/inline.ts +149 -55
  118. package/src/html/layout/multicol.ts +977 -0
  119. package/src/html/layout/shaping.ts +27 -18
  120. package/src/html/layout/table.ts +62 -19
  121. package/src/html/node.ts +300 -37
  122. package/src/html/paint.ts +1406 -206
  123. package/src/html/svg.ts +277 -19
  124. package/src/html/widgets.ts +95 -9
  125. package/src/html/woff2.ts +612 -0
  126. package/src/maps/controller.ts +28 -7
  127. package/src/richtext/node.ts +13 -2
  128. package/src/richtext/runs.ts +313 -35
package/src/html/node.ts CHANGED
@@ -59,7 +59,7 @@ 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
63
  import { uaStylesheet } from './css/ua.js';
64
64
  import type { ComputedStyle, RootLook } from './css/style.js';
65
65
  import {
@@ -67,12 +67,16 @@ import {
67
67
  buildBoxes,
68
68
  CONTENT_IMAGES,
69
69
  CLEARED_FROM,
70
+ COLUMN_LINES,
71
+ COLUMN_PIECES,
72
+ COLUMN_ROWS,
70
73
  CUT_BLOCKS,
71
74
  GENERATED_FROM,
72
75
  INLINE_OFFSETS,
73
76
  LINE_BOX_RAISES,
74
77
  SHIFTED_LINES,
75
78
  TEXT_SHIFTS,
79
+ columned,
76
80
  } from './layout/boxes.js';
77
81
  import type {
78
82
  Box,
@@ -82,8 +86,10 @@ import type {
82
86
  ReplacedKind,
83
87
  TextLayoutLike,
84
88
  } from './layout/boxes.js';
85
- import { layoutDocument, retranslate, transformed } from './layout/block.js';
89
+ import { layoutDocument, placedMatrix, retranslate } from './layout/block.js';
90
+ import { invert, mapPoint, mapRect, transformed } from './css/transform.js';
86
91
  import { TextLayoutCache } from './layout/cache.js';
92
+ import { weightAxes } from './layout/axes.js';
87
93
  import { shapingSafe } from './layout/shaping.js';
88
94
  import { SurfaceCache, newSurface } from './surfaces.js';
89
95
  import type { SurfaceLike } from './surfaces.js';
@@ -109,10 +115,16 @@ import {
109
115
  hoistNegative,
110
116
  holds,
111
117
  holdsAbsolute,
118
+ inClip,
119
+ inClipPath,
120
+ onLine,
121
+ ownBounds,
112
122
  paintDocument,
123
+ pathClips,
113
124
  queryChildIndex,
114
125
  selectionRows,
115
126
  stackLayers,
127
+ stacksLayers,
116
128
  } from './paint.js';
117
129
  import type { PaintContext } from './paint.js';
118
130
  import { controlRectsOf, measureControl } from './controls.js';
@@ -716,15 +728,23 @@ export class HtmlViewNode extends Node {
716
728
  place: (sheet: Stylesheet, element: Element) => void,
717
729
  depth = 0,
718
730
  chain: Set<string> = new Set(),
731
+ /** The media queries `sheet` is itself under, where it was imported
732
+ * under some. */
733
+ under: MediaCondition[][] | null = null,
719
734
  ): void {
720
- for (const url of sheet.imports) {
735
+ for (let i = 0; i < sheet.imports.length; i += 1) {
736
+ const url = sheet.imports[i];
737
+ // what it imports is under its import's media queries, and under
738
+ // the ones the sheet importing it is under
739
+ const conditions = sheet.importConditions[i];
740
+ const media = conditions ? [...(under ?? []), conditions] : under;
721
741
  this._resources.request({ url, kind: 'stylesheet', element });
722
742
  const fetched = this._resources.stylesheet(url, [encoding]);
723
743
  const base = this._resources.sheetBase(url);
724
744
  seen.push({ url, text: fetched?.text ?? null, base });
725
745
  const key = base ?? url;
726
746
  if (!fetched || depth >= MAX_IMPORT_DEPTH || chain.has(key)) continue;
727
- const imported = parseStylesheet(fetched.text, 0, layers, base);
747
+ const imported = parseStylesheet(fetched.text, 0, layers, base, media);
728
748
  chain.add(key);
729
749
  this._placeImports(
730
750
  imported,
@@ -735,6 +755,7 @@ export class HtmlViewNode extends Node {
735
755
  place,
736
756
  depth + 1,
737
757
  chain,
758
+ media,
738
759
  );
739
760
  chain.delete(key);
740
761
  place(imported, element);
@@ -849,11 +870,22 @@ export class HtmlViewNode extends Node {
849
870
  return height && height > 0 ? height : 600;
850
871
  }
851
872
 
873
+ /** The engine this document lays text out with, and the one it is over:
874
+ * one object while the engine is, since caches are kept by it. */
875
+ private _engine: { over: FontsLike; fonts: FontsLike } | null = null;
876
+
852
877
  private _fonts(): FontsLike | null {
853
878
  const fonts = (this.app as { fonts?: FontsLike } | null)?.fonts;
879
+ if (!fonts) return null;
854
880
  // a face the engine cannot shape from costs its characters, not the
855
881
  // document (`shaping.ts`)
856
- return fonts ? shapingSafe(fonts) : null;
882
+ const safe = shapingSafe(fonts);
883
+ if (this._engine?.over !== safe) {
884
+ // and a variable face of the document's is set at the weight its
885
+ // rule has for a style's (`axes.ts`)
886
+ this._engine = { over: safe, fonts: weightAxes(safe, this._webFonts) };
887
+ }
888
+ return this._engine.fonts;
857
889
  }
858
890
 
859
891
  /** The text layouts the last pass made, for this one to reuse. */
@@ -1072,7 +1104,7 @@ export class HtmlViewNode extends Node {
1072
1104
  // The boxes are device pixels; each rect becomes the style of a widget
1073
1105
  // mounted beside this element, and a style is logical.
1074
1106
  const s = this._scale;
1075
- const rects = controlRectsOf(tree).map((r) =>
1107
+ const rects = controlRectsOf(tree, s).map((r) =>
1076
1108
  s === 1
1077
1109
  ? r
1078
1110
  : {
@@ -1082,6 +1114,14 @@ export class HtmlViewNode extends Node {
1082
1114
  width: r.width / s,
1083
1115
  height: r.height / s,
1084
1116
  fontSize: r.fontSize / s,
1117
+ ...(r.clip && {
1118
+ clip: {
1119
+ x: r.clip.x / s,
1120
+ y: r.clip.y / s,
1121
+ width: r.clip.width / s,
1122
+ height: r.clip.height / s,
1123
+ },
1124
+ }),
1085
1125
  ...(r.bare && {
1086
1126
  bare: {
1087
1127
  ...r.bare,
@@ -1396,6 +1436,9 @@ export class HtmlViewNode extends Node {
1396
1436
  rect = { x: caret.x, y: caret.y, width: 0, height: caret.height };
1397
1437
  }
1398
1438
  }
1439
+ // where it is drawn: the rectangle around it, turned and scaled as the
1440
+ // boxes it is in are, and itself (`getBoundingClientRect`, CSSOM View)
1441
+ rect = throughTransforms(rect, box);
1399
1442
  const s = this._scale;
1400
1443
  return {
1401
1444
  x: rect.x / s,
@@ -2342,6 +2385,7 @@ function sameRects(a: ControlRect[], b: ControlRect[]): boolean {
2342
2385
  p.fontFamily !== q.fontFamily ||
2343
2386
  p.fontSize !== q.fontSize ||
2344
2387
  p.opacity !== q.opacity ||
2388
+ !sameClip(p.clip, q.clip) ||
2345
2389
  !sameBare(p.bare, q.bare)
2346
2390
  ) {
2347
2391
  return false;
@@ -2350,6 +2394,13 @@ function sameRects(a: ControlRect[], b: ControlRect[]): boolean {
2350
2394
  return true;
2351
2395
  }
2352
2396
 
2397
+ function sameClip(a?: ControlRect['clip'], b?: ControlRect['clip']): boolean {
2398
+ if (!a || !b) return a === b;
2399
+ return (
2400
+ a.x === b.x && a.y === b.y && a.width === b.width && a.height === b.height
2401
+ );
2402
+ }
2403
+
2353
2404
  function sameBare(a?: BareField, b?: BareField): boolean {
2354
2405
  if (!a || !b) return a === b;
2355
2406
  return (
@@ -2399,7 +2450,16 @@ function nearestText(box: Box, x: number, y: number): number | null {
2399
2450
  const distance = dy * 4 + dx;
2400
2451
  if (distance >= bestDistance) continue;
2401
2452
  bestDistance = distance;
2402
- const local = text.layout.indexAt(x - text.drawX, y - text.drawY);
2453
+ // in the row that is this line's, where the layout's other lines are
2454
+ // in another column and a point past this one is not over them
2455
+ let row = y - text.drawY;
2456
+ if (columned.any && COLUMN_ROWS.has(text)) {
2457
+ row = Math.max(
2458
+ natural.y,
2459
+ Math.min(row, natural.y + natural.height - 0.01),
2460
+ );
2461
+ }
2462
+ const local = text.layout.indexAt(x - text.drawX, row);
2403
2463
  const offsets = layoutOffsetsOf(text.layout);
2404
2464
  const units = offsets.length
2405
2465
  ? offsets[Math.max(0, Math.min(local, offsets.length - 1))]
@@ -2413,14 +2473,37 @@ function nearestText(box: Box, x: number, y: number): number | null {
2413
2473
  axisDistance(y, node.boundsY, node.boundsHeight) * 4 +
2414
2474
  axisDistance(x, node.boundsX, node.boundsWidth);
2415
2475
  if (boundsDistance >= bestDistance) return;
2476
+ // in a box painted through a matrix, the point taken back through it
2477
+ // (`deepestAt`): the text is where it was laid out
2478
+ const matrix = placedMatrix(node);
2479
+ if (!matrix) {
2480
+ visitIn(node);
2481
+ return;
2482
+ }
2483
+ const back = invert(matrix);
2484
+ if (!back) return;
2485
+ const px = x;
2486
+ const py = y;
2487
+ [x, y] = mapPoint(back, px, py);
2488
+ try {
2489
+ visitIn(node);
2490
+ } finally {
2491
+ x = px;
2492
+ y = py;
2493
+ }
2494
+ };
2416
2495
 
2496
+ const visitIn = (node: Box): void => {
2417
2497
  const lines = node.lines;
2418
2498
  if (lines?.length) {
2419
2499
  // The line nearest in y, by binary search; then outward both ways
2420
2500
  // while vertical distance alone could still beat the best.
2421
2501
  let lo = 0;
2422
2502
  let hi = lines.length;
2423
- while (lo < hi) {
2503
+ // the lines columns took apart are in no order down the page: each
2504
+ // is tried
2505
+ const apart = columned.any && COLUMN_LINES.has(lines);
2506
+ while (!apart && lo < hi) {
2424
2507
  const mid = (lo + hi) >> 1;
2425
2508
  if (lines[mid].y > y) hi = mid;
2426
2509
  else lo = mid + 1;
@@ -2431,8 +2514,10 @@ function nearestText(box: Box, x: number, y: number): number | null {
2431
2514
  tryLine(lines[i]);
2432
2515
  }
2433
2516
  for (let i = lo; i < lines.length; i += 1) {
2434
- if (axisDistance(y, lines[i].y, lines[i].height) * 4 >= bestDistance)
2517
+ if (axisDistance(y, lines[i].y, lines[i].height) * 4 >= bestDistance) {
2518
+ if (apart) continue;
2435
2519
  break;
2520
+ }
2436
2521
  tryLine(lines[i]);
2437
2522
  }
2438
2523
  }
@@ -2519,7 +2604,16 @@ function caretAt(
2519
2604
  const caret = text.layout.caretPosition(
2520
2605
  codePointAtOffset(offsets, layoutUnits),
2521
2606
  );
2522
- found = { x: text.drawX + caret.x, y: line.y, height: line.height };
2607
+ const at = throughTransforms(
2608
+ {
2609
+ x: text.drawX + caret.x,
2610
+ y: line.y,
2611
+ width: 0,
2612
+ height: line.height,
2613
+ },
2614
+ node,
2615
+ );
2616
+ found = { x: at.x, y: at.y, height: at.height };
2523
2617
  return;
2524
2618
  }
2525
2619
  }
@@ -2534,6 +2628,21 @@ function caretAt(
2534
2628
  return found;
2535
2629
  }
2536
2630
 
2631
+ /**
2632
+ * A rectangle in a box's own coordinates — the ones it and what it holds
2633
+ * were laid out in — as the rectangle around where it is drawn: through
2634
+ * the matrix of each box from `from` up that is painted through one
2635
+ * (`placedMatrix`). The rectangle itself, in a document that transforms
2636
+ * nothing but by moving it.
2637
+ */
2638
+ function throughTransforms(rect: Rect, from: Box | null): Rect {
2639
+ for (let at = from; at; at = at.parent) {
2640
+ const matrix = placedMatrix(at);
2641
+ if (matrix) rect = mapRect(matrix, rect.x, rect.y, rect.width, rect.height);
2642
+ }
2643
+ return rect;
2644
+ }
2645
+
2537
2646
  /** Every band a document range covers, in window coordinates. The subtree
2538
2647
  * text ranges prune the walk to the boxes the range actually crosses, so a
2539
2648
  * drag's small range costs its own paragraphs; only Ctrl+A pays for all. */
@@ -2689,6 +2798,19 @@ function lineBands(
2689
2798
  height: rows.height,
2690
2799
  });
2691
2800
  }
2801
+ // The spaces a piece of a line ends on, where the line goes on
2802
+ // after them: the engine stripped them from the piece it laid out
2803
+ // as a line, and they are the text's all the same, in the elements
2804
+ // that hold its end — a link whose text ends in a space, before
2805
+ // the next link, is that space wider than its letters
2806
+ if (text.trail && b >= text.textEnd && !rtlLine(natural)) {
2807
+ out.push({
2808
+ x: dx + natural.x + natural.width + text.drawX - moved,
2809
+ y: dy + rows.y,
2810
+ width: text.trail,
2811
+ height: rows.height,
2812
+ });
2813
+ }
2692
2814
  // the spaces `pre-wrap` keeps that the line ends on, which have no
2693
2815
  // run: the engine hung them past the line (`LineText.hung`)
2694
2816
  for (const space of text.hung ?? []) {
@@ -2706,6 +2828,14 @@ function lineBands(
2706
2828
  }
2707
2829
  }
2708
2830
 
2831
+ /** Whether a laid-out line's last run reads right to left, its end at its
2832
+ * left. */
2833
+ function rtlLine(natural: {
2834
+ runs: { run?: { direction?: string } }[];
2835
+ }): boolean {
2836
+ return natural.runs[natural.runs.length - 1]?.run?.direction === 'rtl';
2837
+ }
2838
+
2709
2839
  /** Whether a line's text is where the line ends: its last, with nothing
2710
2840
  * placed after it — an inline-block or an image past its content. */
2711
2841
  function endsLine(
@@ -2732,12 +2862,27 @@ function collectBands(
2732
2862
  ): void {
2733
2863
  if (box.subtreeTextEnd <= box.subtreeTextStart) return;
2734
2864
  if (box.subtreeTextEnd <= from || box.subtreeTextStart >= to) return;
2865
+ const first = out.length;
2735
2866
  if (box.lines) lineBands(box.lines, from, to, dx, dy, out, false, ink);
2736
2867
  // Atomics are ordinary children, reached below.
2737
2868
  for (const child of box.children) {
2738
2869
  if (child.kind === 'text' || child.kind === 'break') continue;
2739
2870
  collectBands(child, from, to, dx, dy, out, ink);
2740
2871
  }
2872
+ // where a box painted through a matrix draws them
2873
+ const matrix = out.length > first ? placedMatrix(box) : null;
2874
+ if (!matrix) return;
2875
+ for (let i = first; i < out.length; i += 1) {
2876
+ const band = out[i];
2877
+ const to = mapRect(
2878
+ matrix,
2879
+ band.x - dx,
2880
+ band.y - dy,
2881
+ band.width,
2882
+ band.height,
2883
+ );
2884
+ out[i] = { ...band, ...to, x: to.x + dx, y: to.y + dy };
2885
+ }
2741
2886
  }
2742
2887
 
2743
2888
  /** The layers of a hit test, in the order CSS paints them within a
@@ -2777,7 +2922,22 @@ function deepestAt(
2777
2922
  // are painted in `z-index` order and then the document's (`byZIndex`),
2778
2923
  // the negative ones under the flow: the Zen Garden's `›` has
2779
2924
  // `z-index: 3` over the bar the "View All Designs" link fills after it.
2780
- const take = (el: Element, key: readonly number[], text: boolean) => {
2925
+ // Nor is a box the pointer passes through under it: one that is not
2926
+ // visible, which is no target for the pointer in any browser (CSS 2.1
2927
+ // 11.2 has it invisible, and a hit test finds what is seen), or one with
2928
+ // `pointer-events: none` (CSS UI 4, 5.2). Both are inherited, and what is
2929
+ // in such a box may say otherwise, so the walk goes on through it. A
2930
+ // site's closed menu is laid out over its page, hidden: GitHub's covered
2931
+ // its repository's tabs, and the pointer over a tab was over the menu.
2932
+ const take = (
2933
+ el: Element,
2934
+ style: ComputedStyle,
2935
+ key: readonly number[],
2936
+ text: boolean,
2937
+ ) => {
2938
+ if (style.visibility !== 'visible' || style.pointerEvents === 'none') {
2939
+ return;
2940
+ }
2781
2941
  if (compareKeys(key, foundKey) < 0) return;
2782
2942
  found = el;
2783
2943
  foundKey = key;
@@ -2821,12 +2981,72 @@ function deepestAt(
2821
2981
  atViewport = false;
2822
2982
  }
2823
2983
  };
2984
+ // A box painted through a matrix (`placedMatrix`) is under the point
2985
+ // where the matrix puts it: inside the rectangle around what it draws
2986
+ // there, the point is taken back through the matrix, into the
2987
+ // coordinates the box and all it holds were laid out in, and the walk
2988
+ // goes on with that. An icon turned a quarter is pressed where it is
2989
+ // drawn; a box flattened to nothing is nowhere.
2824
2990
  const enterAt = (
2825
2991
  child: Box,
2826
2992
  context: readonly number[],
2827
2993
  clipped: readonly Box[],
2994
+ ): void => {
2995
+ const matrix = placedMatrix(child);
2996
+ if (!matrix) {
2997
+ enterIn(child, context, clipped, null);
2998
+ return;
2999
+ }
3000
+ if (
3001
+ x < child.boundsX ||
3002
+ x >= child.boundsX + child.boundsWidth ||
3003
+ y < child.boundsY ||
3004
+ y >= child.boundsY + child.boundsHeight
3005
+ ) {
3006
+ return;
3007
+ }
3008
+ const back = invert(matrix);
3009
+ if (!back) return;
3010
+ const px = x;
3011
+ const py = y;
3012
+ [x, y] = mapPoint(back, px, py);
3013
+ try {
3014
+ enterIn(child, context, clipped, ownBounds(child));
3015
+ } finally {
3016
+ x = px;
3017
+ y = py;
3018
+ }
3019
+ };
3020
+ const enterIn = (
3021
+ child: Box,
3022
+ context: readonly number[],
3023
+ clipped: readonly Box[],
3024
+ /** What the box draws in its own coordinates, where those are not the
3025
+ * ones its bounds are in: a box painted through a matrix. */
3026
+ bounds: Rect | null,
2828
3027
  ): void => {
2829
3028
  const style = child.style;
3029
+ // What a `clip-path` cuts away of a box it cuts away of all the box
3030
+ // holds, whatever that is positioned from (CSS Masking 1, 5.1), and
3031
+ // nothing not drawn is under a pointer: a label a page hides for a
3032
+ // screen reader alone under `clip-path: inset(50%)` took the hover and
3033
+ // the press of what was drawn where it lay.
3034
+ if (pathClips(child) && !inClipPath(child, x, y)) return;
3035
+ // `clip` is not that rule as paint has it. It shows the part of an
3036
+ // absolute box it names (CSS 2.1 11.1.2), and past that part nothing is
3037
+ // drawn of the box, nor of what is painted with it. A stacking context
3038
+ // paints all it holds, inside its clip. A box that is none leaves its
3039
+ // positioned boxes to the context around it, which cuts each to the
3040
+ // clips its containing block is under (`clipsFor`): this one for all
3041
+ // but a fixed box, which is under none and is drawn past it. So past
3042
+ // its clip the first is left, and the second is walked as a box that
3043
+ // clips its overflow is past its edge — whether or not the point is in
3044
+ // its own rectangle, which a clip may show only a part of. Both were
3045
+ // walked as if they showed whole: a label hidden for a screen reader
3046
+ // alone under `clip: rect(0, 0, 0, 0)` took the hover and the press of
3047
+ // the link drawn where it lay.
3048
+ let cut = !inClip(child, x, y);
3049
+ if (cut && stacksLayers(child)) return;
2830
3050
  if (
2831
3051
  style.position !== 'static' ||
2832
3052
  (child.parent?.kind === 'flex' && typeof style.zIndex === 'number')
@@ -2834,6 +3054,14 @@ function deepestAt(
2834
3054
  // a flex item with a `z-index` is layered unpositioned (`layered`)
2835
3055
  const z = style.zIndex === 'auto' ? 0 : style.zIndex;
2836
3056
  context = [...context, z < 0 ? HIT_NEGATIVE : HIT_POSITIONED, z];
3057
+ } else if (
3058
+ transformed(style) &&
3059
+ child.kind !== 'inline' &&
3060
+ child.parent &&
3061
+ !onLine(child.parent, child)
3062
+ ) {
3063
+ // a transformed box is painted with the positioned ones (`layered`)
3064
+ context = [...context, HIT_POSITIONED, 0];
2837
3065
  } else if (style.float !== 'none') context = [...context, HIT_FLOAT];
2838
3066
  if (clipped.length !== 0 && child.outOfFlow) {
2839
3067
  const containing = containingBlockOf(child);
@@ -2841,34 +3069,46 @@ function deepestAt(
2841
3069
  ? clipped.filter((clip) => holds(clip, containing))
2842
3070
  : [];
2843
3071
  }
2844
- const inside =
2845
- x >= child.x &&
2846
- x < child.x + child.width &&
2847
- y >= child.y &&
2848
- y < child.y + child.height;
3072
+ // in one of its pieces, where columns broke it: its rect takes in the
3073
+ // columns between them, which are other boxes'
3074
+ const pieces = columned.any ? COLUMN_PIECES.get(child) : undefined;
3075
+ const inside = pieces
3076
+ ? pieces.some(
3077
+ (piece) =>
3078
+ x >= piece.x &&
3079
+ x < piece.x + piece.width &&
3080
+ y >= piece.y &&
3081
+ y < piece.y + piece.height,
3082
+ )
3083
+ : x >= child.x &&
3084
+ x < child.x + child.width &&
3085
+ y >= child.y &&
3086
+ y < child.y + child.height;
2849
3087
  const own = inside && hasRect(child);
2850
3088
  if (!own) {
2851
- const reach = child.boundsY;
2852
- const known =
2853
- Number.isFinite(reach) &&
2854
- (child.boundsWidth > 0 || child.boundsHeight > 0);
3089
+ const left = bounds ? bounds.x : child.boundsX;
3090
+ const reach = bounds ? bounds.y : child.boundsY;
3091
+ const across = bounds ? bounds.width : child.boundsWidth;
3092
+ const down = bounds ? bounds.height : child.boundsHeight;
3093
+ const known = Number.isFinite(reach) && (across > 0 || down > 0);
2855
3094
  if (!known) {
2856
3095
  if (!inside) return;
2857
3096
  } else if (
2858
- x < child.boundsX ||
2859
- x >= child.boundsX + child.boundsWidth ||
3097
+ x < left ||
3098
+ x >= left + across ||
2860
3099
  y < reach ||
2861
- y >= reach + child.boundsHeight
3100
+ y >= reach + down
2862
3101
  ) {
2863
3102
  return;
2864
3103
  }
2865
- if (clipsOverflow(child)) {
2866
- if (!holdsAbsolute(child)) return;
2867
- clipped = [...clipped, child];
2868
- }
3104
+ if (clipsOverflow(child)) cut = true;
3105
+ }
3106
+ if (cut) {
3107
+ if (!holdsAbsolute(child)) return;
3108
+ clipped = [...clipped, child];
2869
3109
  }
2870
3110
  if (own && child.el && clipped.length === 0) {
2871
- take(child.el, [...context, HIT_BLOCK], false);
3111
+ take(child.el, style, [...context, HIT_BLOCK], false);
2872
3112
  }
2873
3113
  visit(child, context, clipped);
2874
3114
  };
@@ -2915,7 +3155,8 @@ function deepestAt(
2915
3155
  tree.textBoxes,
2916
3156
  text.spans.documentAt(run.start),
2917
3157
  );
2918
- if (owner) take(owner, [...context, HIT_INLINE], true);
3158
+ const style = owner && tree.styles.get(owner)?.style;
3159
+ if (style) take(owner, style, [...context, HIT_INLINE], true);
2919
3160
  }
2920
3161
  }
2921
3162
  }
@@ -2986,6 +3227,9 @@ const PAINT_ONLY = new Set([
2986
3227
  'color',
2987
3228
  'textFillColor',
2988
3229
  'cursor',
3230
+ 'pointerEvents',
3231
+ 'fill',
3232
+ 'stroke',
2989
3233
  'backgroundColor',
2990
3234
  'borderTopColor',
2991
3235
  'borderRightColor',
@@ -3002,6 +3246,8 @@ const PAINT_ONLY = new Set([
3002
3246
  'underlineThickness',
3003
3247
  'underlineOffset',
3004
3248
  'lineThrough',
3249
+ 'lineThroughStyle',
3250
+ 'lineThroughThickness',
3005
3251
  ]);
3006
3252
 
3007
3253
  /** The fields of a run its ink is: what `runFor` takes from `PAINT_ONLY`. */
@@ -3012,6 +3258,8 @@ const INK_FIELDS = [
3012
3258
  'underlineOffset',
3013
3259
  'underlineThickness',
3014
3260
  'strike',
3261
+ 'strikeStyle',
3262
+ 'strikeThickness',
3015
3263
  ] as const;
3016
3264
 
3017
3265
  /** The fields of a run its shape is, which ink never changes. */
@@ -3065,10 +3313,20 @@ const INK_REACH = new Set([
3065
3313
  'outlineOffset',
3066
3314
  ]);
3067
3315
 
3316
+ /** What transforms a box: it moves, turns or scales where it is, and
3317
+ * nothing around it does (CSS Transforms 1, 3). */
3318
+ const TRANSFORM_FIELDS = new Set([
3319
+ 'translate',
3320
+ 'rotate',
3321
+ 'scale',
3322
+ 'transform',
3323
+ 'transformOrigin',
3324
+ ]);
3325
+
3068
3326
  /**
3069
3327
  * How two styles of one element differ, for a restyle in place: null where
3070
3328
  * 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 —
3329
+ * reaches, a `z-index` that stays a stacking context's, or a transform —
3072
3330
  * none of which moves anything but the box and what it holds (CSS
3073
3331
  * Transforms 1: a transform does not affect layout) — and false where
3074
3332
  * anything else differs.
@@ -3107,7 +3365,7 @@ function hoverChange(
3107
3365
  change.order = true;
3108
3366
  continue;
3109
3367
  }
3110
- if (key === 'transformTranslate' || key === 'translate') {
3368
+ if (TRANSFORM_FIELDS.has(key)) {
3111
3369
  change.move = true;
3112
3370
  continue;
3113
3371
  }
@@ -3150,12 +3408,17 @@ function inkOf(box: Box): Rect | null {
3150
3408
  }
3151
3409
  if (!at || at.boundsY === Infinity) return null;
3152
3410
  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
- };
3411
+ // its bounds are in the coordinates of the box it is in, which a box
3412
+ // around it painted through a matrix draws somewhere else
3413
+ return throughTransforms(
3414
+ {
3415
+ x: at.boundsX,
3416
+ y: at.boundsY,
3417
+ width: at.boundsWidth,
3418
+ height: at.boundsHeight,
3419
+ },
3420
+ at.parent,
3421
+ );
3159
3422
  }
3160
3423
 
3161
3424
  /** Structural equality for a computed value: a number, a string, or the