@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.
- package/dist/html/controls.d.ts +29 -2
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +70 -2
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +26 -4
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +18 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +27 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +18 -4
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +66 -28
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +128 -15
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +561 -144
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/transform.d.ts +85 -0
- package/dist/html/css/transform.d.ts.map +1 -0
- package/dist/html/css/transform.js +425 -0
- package/dist/html/css/transform.js.map +1 -0
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +14 -2
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +4 -0
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/fonts.d.ts +55 -5
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +284 -23
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/layout/axes.d.ts +14 -0
- package/dist/html/layout/axes.d.ts.map +1 -0
- package/dist/html/layout/axes.js +28 -0
- package/dist/html/layout/axes.js.map +1 -0
- package/dist/html/layout/block.d.ts +93 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +389 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +59 -1
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +22 -2
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +1 -1
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +6 -0
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/css-grid.d.ts.map +1 -1
- package/dist/html/layout/css-grid.js +90 -18
- package/dist/html/layout/css-grid.js.map +1 -1
- package/dist/html/layout/flex.d.ts.map +1 -1
- package/dist/html/layout/flex.js +570 -83
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +144 -54
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/layout/multicol.d.ts +42 -0
- package/dist/html/layout/multicol.d.ts.map +1 -0
- package/dist/html/layout/multicol.js +897 -0
- package/dist/html/layout/multicol.js.map +1 -0
- package/dist/html/layout/shaping.d.ts +7 -0
- package/dist/html/layout/shaping.d.ts.map +1 -1
- package/dist/html/layout/shaping.js +25 -20
- package/dist/html/layout/shaping.js.map +1 -1
- package/dist/html/layout/table.d.ts.map +1 -1
- package/dist/html/layout/table.js +62 -18
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +3 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +263 -36
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +79 -4
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1213 -141
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +15 -2
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +249 -17
- package/dist/html/svg.js.map +1 -1
- package/dist/html/widgets.js +87 -7
- package/dist/html/widgets.js.map +1 -1
- package/dist/html/woff2.d.ts +14 -0
- package/dist/html/woff2.d.ts.map +1 -0
- package/dist/html/woff2.js +608 -0
- package/dist/html/woff2.js.map +1 -0
- package/dist/maps/controller.d.ts +17 -1
- package/dist/maps/controller.d.ts.map +1 -1
- package/dist/maps/controller.js +27 -7
- package/dist/maps/controller.js.map +1 -1
- package/dist/richtext/node.d.ts +8 -1
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js +5 -1
- package/dist/richtext/node.js.map +1 -1
- package/dist/richtext/runs.d.ts +25 -6
- package/dist/richtext/runs.d.ts.map +1 -1
- package/dist/richtext/runs.js +240 -24
- package/dist/richtext/runs.js.map +1 -1
- package/package.json +1 -1
- package/src/html/controls.ts +85 -2
- package/src/html/css/cascade.ts +23 -0
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +71 -31
- package/src/html/css/style.ts +664 -147
- package/src/html/css/transform.ts +459 -0
- package/src/html/css/ua.ts +15 -2
- package/src/html/css/values.ts +4 -0
- package/src/html/fonts.ts +312 -29
- package/src/html/layout/axes.ts +62 -0
- package/src/html/layout/block.ts +444 -37
- package/src/html/layout/boxes.ts +65 -3
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +95 -22
- package/src/html/layout/flex.ts +671 -77
- package/src/html/layout/inline.ts +149 -55
- package/src/html/layout/multicol.ts +977 -0
- package/src/html/layout/shaping.ts +27 -18
- package/src/html/layout/table.ts +62 -19
- package/src/html/node.ts +300 -37
- package/src/html/paint.ts +1406 -206
- package/src/html/svg.ts +277 -19
- package/src/html/widgets.ts +95 -9
- package/src/html/woff2.ts +612 -0
- package/src/maps/controller.ts +28 -7
- package/src/richtext/node.ts +13 -2
- 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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
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
|
|
2852
|
-
const
|
|
2853
|
-
|
|
2854
|
-
|
|
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 <
|
|
2859
|
-
x >=
|
|
3097
|
+
x < left ||
|
|
3098
|
+
x >= left + across ||
|
|
2860
3099
|
y < reach ||
|
|
2861
|
-
y >= reach +
|
|
3100
|
+
y >= reach + down
|
|
2862
3101
|
) {
|
|
2863
3102
|
return;
|
|
2864
3103
|
}
|
|
2865
|
-
if (clipsOverflow(child))
|
|
2866
|
-
|
|
2867
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
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
|