@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.
- 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 +42 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +336 -16
- 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/shapes.d.ts +44 -0
- package/dist/html/css/shapes.d.ts.map +1 -0
- package/dist/html/css/shapes.js +194 -0
- package/dist/html/css/shapes.js.map +1 -0
- 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 +547 -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 +104 -12
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +410 -39
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +72 -1
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +40 -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 +144 -24
- 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 +585 -85
- 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 +97 -31
- package/dist/html/layout/table.js.map +1 -1
- package/dist/html/node.d.ts +4 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +319 -47
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +83 -5
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +1474 -217
- package/dist/html/paint.js.map +1 -1
- package/dist/html/svg.d.ts +39 -9
- package/dist/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +482 -37
- 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 +366 -8
- package/src/html/css/color.ts +29 -0
- package/src/html/css/parse.ts +71 -31
- package/src/html/css/shapes.ts +239 -0
- package/src/html/css/style.ts +652 -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 +466 -37
- package/src/html/layout/boxes.ts +95 -3
- package/src/html/layout/cache.ts +6 -0
- package/src/html/layout/css-grid.ts +169 -32
- package/src/html/layout/flex.ts +686 -79
- 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 +105 -35
- package/src/html/node.ts +354 -48
- package/src/html/paint.ts +1804 -315
- package/src/html/svg.ts +573 -36
- 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,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,
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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)
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
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
|
|
2852
|
-
const
|
|
2853
|
-
|
|
2854
|
-
|
|
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 <
|
|
2859
|
-
x >=
|
|
3140
|
+
x < left ||
|
|
3141
|
+
x >= left + across ||
|
|
2860
3142
|
y < reach ||
|
|
2861
|
-
y >= reach +
|
|
3143
|
+
y >= reach + down
|
|
2862
3144
|
) {
|
|
2863
3145
|
return;
|
|
2864
3146
|
}
|
|
2865
|
-
if (clipsOverflow(child))
|
|
2866
|
-
|
|
2867
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
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
|