@react-x11/components 0.11.0 → 0.12.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/flow/node.d.ts +14 -6
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +82 -64
- package/dist/flow/node.js.map +1 -1
- package/dist/html/controls.d.ts +26 -7
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +28 -3
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +19 -6
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +19 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +86 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +11 -0
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +114 -4
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +38 -1
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +326 -8
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +14 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +10 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +27 -2
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/css/vars.d.ts +5 -0
- package/dist/html/css/vars.d.ts.map +1 -1
- package/dist/html/css/vars.js +2 -2
- package/dist/html/css/vars.js.map +1 -1
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +35 -2
- package/dist/html/dom.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +38 -1
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +11 -2
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +181 -18
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +34 -6
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +35 -14
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +2 -2
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +8 -3
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/floats.d.ts +4 -0
- package/dist/html/layout/floats.d.ts.map +1 -1
- package/dist/html/layout/floats.js +26 -0
- package/dist/html/layout/floats.js.map +1 -1
- package/dist/html/layout/inline.d.ts +26 -2
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +1222 -115
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +33 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +512 -51
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +53 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +546 -180
- package/dist/html/paint.js.map +1 -1
- package/dist/html/surfaces.d.ts +7 -2
- package/dist/html/surfaces.d.ts.map +1 -1
- package/dist/html/surfaces.js +16 -0
- package/dist/html/surfaces.js.map +1 -1
- package/dist/internal/window.d.ts +22 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +35 -8
- package/dist/internal/window.js.map +1 -1
- package/dist/richtext/node.d.ts +7 -0
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js.map +1 -1
- package/package.json +5 -3
- package/src/flow/node.ts +80 -64
- package/src/html/controls.ts +50 -9
- package/src/html/css/cascade.ts +19 -6
- package/src/html/css/color.ts +87 -0
- package/src/html/css/parse.ts +107 -4
- package/src/html/css/style.ts +343 -8
- package/src/html/css/ua.ts +14 -0
- package/src/html/css/values.ts +31 -3
- package/src/html/css/vars.ts +2 -2
- package/src/html/dom.ts +38 -2
- package/src/html/index.ts +39 -1
- package/src/html/layout/block.ts +228 -16
- package/src/html/layout/boxes.ts +55 -21
- package/src/html/layout/cache.ts +8 -3
- package/src/html/layout/floats.ts +24 -0
- package/src/html/layout/inline.ts +1435 -108
- package/src/html/node.ts +535 -54
- package/src/html/paint.ts +650 -241
- package/src/html/surfaces.ts +21 -1
- package/src/internal/window.ts +36 -8
- package/src/richtext/node.ts +7 -0
package/dist/html/node.js
CHANGED
|
@@ -43,17 +43,17 @@ import { attr, HtmlSource, imageUrlOf, isElement, tagOf } from './dom.js';
|
|
|
43
43
|
import { Cascade } from './css/cascade.js';
|
|
44
44
|
import { mediaMatches, parseStylesheet } from './css/parse.js';
|
|
45
45
|
import { uaStylesheet } from './css/ua.js';
|
|
46
|
-
import { BOX_RAISES, buildBoxes, CONTENT_IMAGES, LINE_BOX_RAISES, SHIFTED_LINES, } from './layout/boxes.js';
|
|
47
|
-
import { layoutDocument } from './layout/block.js';
|
|
46
|
+
import { BOX_RAISES, buildBoxes, CONTENT_IMAGES, CUT_BLOCKS, GENERATED_FROM, INLINE_OFFSETS, LINE_BOX_RAISES, SHIFTED_LINES, TEXT_SHIFTS, } from './layout/boxes.js';
|
|
47
|
+
import { layoutDocument, retranslate, transformed } from './layout/block.js';
|
|
48
48
|
import { TextLayoutCache } from './layout/cache.js';
|
|
49
49
|
import { shapingSafe } from './layout/shaping.js';
|
|
50
|
-
import { SurfaceCache } from './surfaces.js';
|
|
50
|
+
import { SurfaceCache, newSurface } from './surfaces.js';
|
|
51
51
|
import { faceExtentOf, inlineDecoration, runFor } from './layout/inline.js';
|
|
52
52
|
// Through the inline module rather than a second cache: the offsets table for
|
|
53
53
|
// a layout is built once, on the first selection that needs it.
|
|
54
54
|
import { documentOffsetOf, layoutOffsetOf, layoutOffsets as layoutOffsetsOf, } from './layout/inline.js';
|
|
55
55
|
import { lineBands as bandsFor } from '../richtext/runs.js';
|
|
56
|
-
import { clipsOverflow, containingBlockOf, hasRect, holds, holdsAbsolute, paintDocument, queryChildIndex, } from './paint.js';
|
|
56
|
+
import { clipsOverflow, computePaintBounds, containingBlockOf, FIXED_BOXES, fixedToViewport, forgetDecoratedAncestors, hasRect, hoistNegative, holds, holdsAbsolute, paintDocument, queryChildIndex, stackLayers, } from './paint.js';
|
|
57
57
|
import { controlRectsOf, measureControl } from './controls.js';
|
|
58
58
|
import { ResourceStore } from './resources.js';
|
|
59
59
|
import { WebFonts } from './fonts.js';
|
|
@@ -118,6 +118,8 @@ export class HtmlViewNode extends Node {
|
|
|
118
118
|
_sheetsRead = null;
|
|
119
119
|
/** Blurred shadows, drawn once each. */
|
|
120
120
|
_shadowCache = null;
|
|
121
|
+
/** Whether the backend was found to have no offscreen surface. */
|
|
122
|
+
_noSurface = false;
|
|
121
123
|
_cascade = null;
|
|
122
124
|
_tree = null;
|
|
123
125
|
/** The faces `_warmFaces` has asked the font matcher for, and of which
|
|
@@ -315,14 +317,16 @@ export class HtmlViewNode extends Node {
|
|
|
315
317
|
*/
|
|
316
318
|
_requestBackgrounds(tree) {
|
|
317
319
|
for (const box of tree.backgrounds) {
|
|
318
|
-
|
|
320
|
+
// a pseudo-element's are its element's
|
|
321
|
+
const element = box.el ?? GENERATED_FROM.get(box);
|
|
322
|
+
if (!element)
|
|
319
323
|
continue;
|
|
320
324
|
// each layer's, where there is more than one
|
|
321
325
|
for (const url of box.style.backgroundImages ?? [
|
|
322
326
|
box.style.backgroundImage,
|
|
323
327
|
]) {
|
|
324
328
|
if (typeof url === 'string') {
|
|
325
|
-
this._resources.request({ url, kind: 'image', element
|
|
329
|
+
this._resources.request({ url, kind: 'image', element });
|
|
326
330
|
}
|
|
327
331
|
}
|
|
328
332
|
const border = box.style.borderImage.source;
|
|
@@ -330,9 +334,15 @@ export class HtmlViewNode extends Node {
|
|
|
330
334
|
this._resources.request({
|
|
331
335
|
url: border,
|
|
332
336
|
kind: 'image',
|
|
333
|
-
element
|
|
337
|
+
element,
|
|
334
338
|
});
|
|
335
339
|
}
|
|
340
|
+
// and each mask layer's, which is an image as a background's is
|
|
341
|
+
for (const url of box.style.mask.images) {
|
|
342
|
+
if (typeof url === 'string') {
|
|
343
|
+
this._resources.request({ url, kind: 'image', element });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
336
346
|
}
|
|
337
347
|
// and every image generated content names, which is only known there
|
|
338
348
|
// too
|
|
@@ -548,6 +558,58 @@ export class HtmlViewNode extends Node {
|
|
|
548
558
|
}
|
|
549
559
|
return true;
|
|
550
560
|
}
|
|
561
|
+
/**
|
|
562
|
+
* The viewport the document is seen through, in window coordinates: the
|
|
563
|
+
* content box of the pane that scrolls the element, where one does —
|
|
564
|
+
* what `position: fixed` and a fixed background are placed against, as
|
|
565
|
+
* a browser places them against its page area. Null where nothing
|
|
566
|
+
* scrolls it, and the element is its own.
|
|
567
|
+
*/
|
|
568
|
+
_viewport() {
|
|
569
|
+
for (let node = this.parent; node; node = node.parent) {
|
|
570
|
+
// `Scrollable`'s, and not every node's: asked the way core asks it
|
|
571
|
+
const scroller = node;
|
|
572
|
+
if (!scroller.isScroller?.())
|
|
573
|
+
continue;
|
|
574
|
+
const box = node.contentBox();
|
|
575
|
+
return box.width > 0 && box.height > 0 ? box : null;
|
|
576
|
+
}
|
|
577
|
+
return null;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* What the document draws fixed to the viewport of the pane that scrolls
|
|
581
|
+
* it, for that pane's scroll blit to repaint rather than copy with the
|
|
582
|
+
* text (react-x11's `viewportFixedRects`): the whole viewport where a
|
|
583
|
+
* fixed background shows through it, which makes a scroll a repaint, and
|
|
584
|
+
* where each fixed box is drawn. Null for a document with neither, and
|
|
585
|
+
* where nothing scrolls the element.
|
|
586
|
+
*/
|
|
587
|
+
viewportFixedRects() {
|
|
588
|
+
const tree = this._tree;
|
|
589
|
+
const viewport = tree && this._viewport();
|
|
590
|
+
if (!tree || !viewport)
|
|
591
|
+
return null;
|
|
592
|
+
if (hasFixedBackground(tree))
|
|
593
|
+
return [viewport];
|
|
594
|
+
const boxes = FIXED_BOXES.get(tree);
|
|
595
|
+
if (!boxes)
|
|
596
|
+
return null;
|
|
597
|
+
// laid out against the viewport at the document's top, and drawn at the
|
|
598
|
+
// viewport (`atViewport`)
|
|
599
|
+
const root = tree.root;
|
|
600
|
+
const out = [];
|
|
601
|
+
for (const box of boxes) {
|
|
602
|
+
if (!(box.boundsWidth > 0 && box.boundsHeight > 0))
|
|
603
|
+
continue;
|
|
604
|
+
out.push({
|
|
605
|
+
x: viewport.x + box.boundsX - root.x,
|
|
606
|
+
y: viewport.y + box.boundsY - root.y,
|
|
607
|
+
width: box.boundsWidth,
|
|
608
|
+
height: box.boundsHeight,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
return out.length ? out : null;
|
|
612
|
+
}
|
|
551
613
|
_viewportHeight() {
|
|
552
614
|
// The viewport a `vh` resolves against is the one the document is seen
|
|
553
615
|
// through, not the document — a document taller than it does not make
|
|
@@ -1002,29 +1064,52 @@ export class HtmlViewNode extends Node {
|
|
|
1002
1064
|
}
|
|
1003
1065
|
else {
|
|
1004
1066
|
const bands = [];
|
|
1005
|
-
|
|
1006
|
-
//
|
|
1007
|
-
//
|
|
1008
|
-
//
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
}
|
|
1016
|
-
else {
|
|
1017
|
-
collectBands(tree.root, box.subtreeTextStart, box.subtreeTextEnd, 0, 0, bands);
|
|
1018
|
-
}
|
|
1067
|
+
const fonts = this._layouts?.fonts ?? null;
|
|
1068
|
+
// an inline box broken around the blocks in it is its pieces, a box
|
|
1069
|
+
// of the element's each (CSS 2.1 9.2.1.1), and its fragments are
|
|
1070
|
+
// theirs; and on the lines between them it has a fragment around each
|
|
1071
|
+
// block, across the box the block is in and as tall as its border
|
|
1072
|
+
// box, as a browser's has — Blink's block-in-inline, Gecko's split
|
|
1073
|
+
// inline's anonymous block. A list item made inline around a block
|
|
1074
|
+
// link measured as the empty edge after it
|
|
1075
|
+
for (const piece of piecesOf(tree.root, box)) {
|
|
1076
|
+
inlineBands(tree.root, piece, fonts, bands);
|
|
1019
1077
|
}
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1078
|
+
for (const block of (box.cut ? CUT_BLOCKS.get(box) : null) ?? []) {
|
|
1079
|
+
const around = block.parent;
|
|
1080
|
+
if (!around)
|
|
1081
|
+
continue;
|
|
1082
|
+
bands.push({
|
|
1083
|
+
x: around.contentX,
|
|
1084
|
+
y: block.y,
|
|
1085
|
+
width: around.contentWidth,
|
|
1086
|
+
height: block.height,
|
|
1087
|
+
});
|
|
1025
1088
|
}
|
|
1026
1089
|
for (const band of bands)
|
|
1027
1090
|
rect = rect ? unionRect(rect, band) : band;
|
|
1091
|
+
if (!rect) {
|
|
1092
|
+
// on no line, where its block has none: text set at no size takes
|
|
1093
|
+
// no room and gets no box, and CSS puts it on lines of no height
|
|
1094
|
+
// at the block's top (9.4.2), as a browser measures it — nothing,
|
|
1095
|
+
// where the block's content starts. Where the text after it
|
|
1096
|
+
// starts was the next text that took room, elsewhere in the
|
|
1097
|
+
// document: the Zen Garden's 170 hides a heading at `font-size:
|
|
1098
|
+
// 0`, and an `<abbr>` in it measured as the end of the paragraph
|
|
1099
|
+
// above it
|
|
1100
|
+
let block = box.parent;
|
|
1101
|
+
while (block && block.kind === 'inline')
|
|
1102
|
+
block = block.parent;
|
|
1103
|
+
if (block && !block.lines?.length) {
|
|
1104
|
+
const rtl = block.style.direction === 'rtl';
|
|
1105
|
+
rect = {
|
|
1106
|
+
x: rtl ? block.contentX + block.contentWidth : block.contentX,
|
|
1107
|
+
y: block.contentY,
|
|
1108
|
+
width: 0,
|
|
1109
|
+
height: 0,
|
|
1110
|
+
};
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1028
1113
|
if (!rect) {
|
|
1029
1114
|
// an empty subtree's range is `[0, 0)`, wherever it stands: where
|
|
1030
1115
|
// its text would be is where the text after it starts
|
|
@@ -1048,7 +1133,20 @@ export class HtmlViewNode extends Node {
|
|
|
1048
1133
|
if (!tree)
|
|
1049
1134
|
return null;
|
|
1050
1135
|
const local = this._toDocument(x, y);
|
|
1051
|
-
return deepestAt(tree, local.x, local.y);
|
|
1136
|
+
return deepestAt(tree, local.x, local.y, undefined, this._fixedShift());
|
|
1137
|
+
}
|
|
1138
|
+
/** How far a box fixed to the viewport is drawn from where it was laid
|
|
1139
|
+
* out, in the document's pixels: the scroll of the pane the element is
|
|
1140
|
+
* in (`atViewport`). Null where nothing scrolls it. */
|
|
1141
|
+
_fixedShift() {
|
|
1142
|
+
const tree = this._tree;
|
|
1143
|
+
const viewport = tree && FIXED_BOXES.has(tree) && this._viewport();
|
|
1144
|
+
if (!tree || !viewport)
|
|
1145
|
+
return null;
|
|
1146
|
+
return {
|
|
1147
|
+
x: viewport.x - this.abs.x - tree.root.x,
|
|
1148
|
+
y: viewport.y - this.abs.y - tree.root.y,
|
|
1149
|
+
};
|
|
1052
1150
|
}
|
|
1053
1151
|
/**
|
|
1054
1152
|
* The cursor for a point of this element's, in device pixels: what core
|
|
@@ -1065,7 +1163,7 @@ export class HtmlViewNode extends Node {
|
|
|
1065
1163
|
if (!tree)
|
|
1066
1164
|
return null;
|
|
1067
1165
|
const hit = { text: false };
|
|
1068
|
-
const el = deepestAt(tree, x - this.abs.x, y - this.abs.y, hit);
|
|
1166
|
+
const el = deepestAt(tree, x - this.abs.x, y - this.abs.y, hit, this._fixedShift());
|
|
1069
1167
|
const cursor = el ? tree.styles.get(el)?.style.cursor : null;
|
|
1070
1168
|
// a keyword; a `url()` this cannot load falls back, as its list would
|
|
1071
1169
|
if (cursor && cursor !== 'auto' && /^[a-z-]+$/.test(cursor))
|
|
@@ -1107,10 +1205,8 @@ export class HtmlViewNode extends Node {
|
|
|
1107
1205
|
}
|
|
1108
1206
|
/** The hovered chain moved: restyle where it did, or the document. */
|
|
1109
1207
|
_restyleHover(was, now) {
|
|
1110
|
-
|
|
1111
|
-
if (
|
|
1112
|
-
this.invalidate(false, this, 'props');
|
|
1113
|
-
else if (done === false)
|
|
1208
|
+
// a restyle in place names what it repainted (`_hoverInPlace`)
|
|
1209
|
+
if (this._hoverInPlace(was, now) === false)
|
|
1114
1210
|
this._invalidate(Stale.Boxes);
|
|
1115
1211
|
}
|
|
1116
1212
|
/**
|
|
@@ -1205,6 +1301,11 @@ export class HtmlViewNode extends Node {
|
|
|
1205
1301
|
const fresh = new Map();
|
|
1206
1302
|
const changed = new Map();
|
|
1207
1303
|
let refused = false;
|
|
1304
|
+
// what changed beyond ink (`hoverChange`): how far some box's ink
|
|
1305
|
+
// reaches, the order its layer paints in, where one is
|
|
1306
|
+
let reaches = false;
|
|
1307
|
+
let reorders = false;
|
|
1308
|
+
const moving = new Set();
|
|
1208
1309
|
const styleOf = (el) => {
|
|
1209
1310
|
const kept = tree.styles.get(el);
|
|
1210
1311
|
if (!kept)
|
|
@@ -1224,11 +1325,18 @@ export class HtmlViewNode extends Node {
|
|
|
1224
1325
|
}
|
|
1225
1326
|
const style = cascade.styleFor(el, parentStyle, kept.inFlex);
|
|
1226
1327
|
fresh.set(el, style);
|
|
1227
|
-
const diff =
|
|
1328
|
+
const diff = hoverChange(kept.style, style);
|
|
1228
1329
|
if (diff === false)
|
|
1229
1330
|
refused = true;
|
|
1230
|
-
else if (diff)
|
|
1331
|
+
else if (diff) {
|
|
1231
1332
|
changed.set(el, style);
|
|
1333
|
+
if (diff.reach)
|
|
1334
|
+
reaches = true;
|
|
1335
|
+
if (diff.order)
|
|
1336
|
+
reorders = true;
|
|
1337
|
+
if (diff.move)
|
|
1338
|
+
moving.add(el);
|
|
1339
|
+
}
|
|
1232
1340
|
return style;
|
|
1233
1341
|
};
|
|
1234
1342
|
for (const el of reach) {
|
|
@@ -1262,10 +1370,14 @@ export class HtmlViewNode extends Node {
|
|
|
1262
1370
|
continue;
|
|
1263
1371
|
const kept = tree.styles.get(owner);
|
|
1264
1372
|
// an anonymous box, a pseudo-element, a marker, a control: a style
|
|
1265
|
-
// derived from the element's, or drawn from it somewhere else
|
|
1373
|
+
// derived from the element's, or drawn from it somewhere else — a
|
|
1374
|
+
// widget mounted beside the document takes its look from it. An
|
|
1375
|
+
// image, a drawing or a rule is drawn here, from its box, like any.
|
|
1266
1376
|
if (!box.el || box.style !== kept.style)
|
|
1267
1377
|
return false;
|
|
1268
|
-
if (box.marker || box.replaced
|
|
1378
|
+
if (box.marker || WIDGETS.has(box.replaced))
|
|
1379
|
+
return false;
|
|
1380
|
+
if (moving.has(owner) && !movable(box, kept.style, style))
|
|
1269
1381
|
return false;
|
|
1270
1382
|
restyled.push([box, style]);
|
|
1271
1383
|
if (box.kind === 'inline') {
|
|
@@ -1302,11 +1414,29 @@ export class HtmlViewNode extends Node {
|
|
|
1302
1414
|
}
|
|
1303
1415
|
}
|
|
1304
1416
|
}
|
|
1417
|
+
// what each box drew before, to be repainted with what it draws after
|
|
1418
|
+
const inks = [];
|
|
1419
|
+
for (const [box] of restyled) {
|
|
1420
|
+
const ink = inkOf(box);
|
|
1421
|
+
if (ink)
|
|
1422
|
+
inks.push(ink);
|
|
1423
|
+
}
|
|
1305
1424
|
// and only now, all of it
|
|
1306
|
-
|
|
1425
|
+
const moved = [];
|
|
1426
|
+
for (const [box, style] of restyled) {
|
|
1427
|
+
const was = box.style;
|
|
1307
1428
|
box.style = style;
|
|
1308
|
-
|
|
1429
|
+
if (moving.has(box.el))
|
|
1430
|
+
moved.push([box, was]);
|
|
1431
|
+
}
|
|
1432
|
+
for (const [box, decoration] of redecorated) {
|
|
1433
|
+
// what is inside it keeps which boxes around it paint, and this is
|
|
1434
|
+
// one more or one fewer of them
|
|
1435
|
+
if ((decoration === null) !== (box.decoration === null)) {
|
|
1436
|
+
forgetDecoratedAncestors(box);
|
|
1437
|
+
}
|
|
1309
1438
|
box.decoration = decoration;
|
|
1439
|
+
}
|
|
1310
1440
|
for (const text of texts) {
|
|
1311
1441
|
const next = relaid.get(text.layout);
|
|
1312
1442
|
if (next)
|
|
@@ -1315,8 +1445,72 @@ export class HtmlViewNode extends Node {
|
|
|
1315
1445
|
for (const [el, style] of changed) {
|
|
1316
1446
|
tree.styles.set(el, { style, inFlex: tree.styles.get(el).inFlex });
|
|
1317
1447
|
}
|
|
1448
|
+
// A box that moved moves what is in it, as layout moved it the first
|
|
1449
|
+
// time (`applyRelativeOffsets`), and a later layout moves it the same
|
|
1450
|
+
// way; where it reached the document's end, before or after, the
|
|
1451
|
+
// document's height may have changed with it, which only a layout says.
|
|
1452
|
+
let relayout = false;
|
|
1453
|
+
for (const [box, was] of moved) {
|
|
1454
|
+
const before = box.boundsY + box.boundsHeight;
|
|
1455
|
+
retranslate(box, was);
|
|
1456
|
+
tree.relative = true;
|
|
1457
|
+
if (before >= this._documentHeight)
|
|
1458
|
+
relayout = true;
|
|
1459
|
+
}
|
|
1460
|
+
// how far the boxes' ink reaches, and the order their layers paint in,
|
|
1461
|
+
// as layout leaves them (`layoutDocument`)
|
|
1462
|
+
if (reaches || reorders || moved.length) {
|
|
1463
|
+
computePaintBounds(tree.root, tree.movedInline);
|
|
1464
|
+
if (tree.negative)
|
|
1465
|
+
hoistNegative(tree.root);
|
|
1466
|
+
stackLayers(tree.root);
|
|
1467
|
+
}
|
|
1468
|
+
for (const [box] of moved) {
|
|
1469
|
+
if (box.boundsY + box.boundsHeight >= this._documentHeight) {
|
|
1470
|
+
relayout = true;
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
if (moved.length)
|
|
1474
|
+
this._reportControls();
|
|
1475
|
+
if (relayout) {
|
|
1476
|
+
this._invalidate(Stale.Layout);
|
|
1477
|
+
return 'painted';
|
|
1478
|
+
}
|
|
1479
|
+
for (const [box] of restyled) {
|
|
1480
|
+
const ink = inkOf(box);
|
|
1481
|
+
if (ink)
|
|
1482
|
+
inks.push(ink);
|
|
1483
|
+
}
|
|
1484
|
+
this._repaintInk(inks);
|
|
1318
1485
|
return 'painted';
|
|
1319
1486
|
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Repaint the document where `inks` are, in its own coordinates: the ink
|
|
1489
|
+
* of the boxes a hover restyled, before and after. Past a handful of rects
|
|
1490
|
+
* the damage is one around them all, which is what core would make of
|
|
1491
|
+
* them anyway.
|
|
1492
|
+
*/
|
|
1493
|
+
_repaintInk(inks) {
|
|
1494
|
+
if (!inks.length)
|
|
1495
|
+
return;
|
|
1496
|
+
const x = this.abs.x;
|
|
1497
|
+
const y = this.abs.y;
|
|
1498
|
+
const place = (r) => ({
|
|
1499
|
+
x: Math.floor(x + r.x),
|
|
1500
|
+
y: Math.floor(y + r.y),
|
|
1501
|
+
width: Math.ceil(r.width) + 1,
|
|
1502
|
+
height: Math.ceil(r.height) + 1,
|
|
1503
|
+
});
|
|
1504
|
+
if (inks.length > 6) {
|
|
1505
|
+
let all = inks[0];
|
|
1506
|
+
for (const r of inks)
|
|
1507
|
+
all = unionRect(all, r);
|
|
1508
|
+
this.invalidate(false, place(all), 'props');
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
for (const r of inks)
|
|
1512
|
+
this.invalidate(false, place(r), 'props');
|
|
1513
|
+
}
|
|
1320
1514
|
/** The document, for an application that wants to read or change it. */
|
|
1321
1515
|
get document() {
|
|
1322
1516
|
return this._source.document;
|
|
@@ -1388,6 +1582,16 @@ export class HtmlViewNode extends Node {
|
|
|
1388
1582
|
this._fail(error, this._laidOutWidth);
|
|
1389
1583
|
}
|
|
1390
1584
|
}
|
|
1585
|
+
/** A surface for the painter to draw a masked element on, asked of the
|
|
1586
|
+
* backend until it is found to have none. */
|
|
1587
|
+
_surface(width, height) {
|
|
1588
|
+
if (this._noSurface)
|
|
1589
|
+
return null;
|
|
1590
|
+
const surface = newSurface(this.app, width, height);
|
|
1591
|
+
if (!surface)
|
|
1592
|
+
this._noSurface = true;
|
|
1593
|
+
return surface;
|
|
1594
|
+
}
|
|
1391
1595
|
_paint(ctx, tree, range, damage) {
|
|
1392
1596
|
paintDocument(ctx, tree, {
|
|
1393
1597
|
originX: this.abs.x,
|
|
@@ -1395,6 +1599,7 @@ export class HtmlViewNode extends Node {
|
|
|
1395
1599
|
// the root's background covers the whole element, not only the
|
|
1396
1600
|
// document: an element grown past its content is canvas too
|
|
1397
1601
|
canvas: this.abs,
|
|
1602
|
+
viewport: this._viewport() ?? undefined,
|
|
1398
1603
|
scale: this._scale,
|
|
1399
1604
|
damage,
|
|
1400
1605
|
selection: range
|
|
@@ -1410,11 +1615,34 @@ export class HtmlViewNode extends Node {
|
|
|
1410
1615
|
const size = image ? this._resources.imageSize(url) : null;
|
|
1411
1616
|
return image && size ? { image, ...size } : null;
|
|
1412
1617
|
},
|
|
1618
|
+
surface: (width, height) => this._surface(width, height),
|
|
1413
1619
|
cached: (key, width, height, draw) => (this._shadowCache ??= new SurfaceCache(this.app)).get(key, width, height, draw),
|
|
1414
1620
|
});
|
|
1415
1621
|
}
|
|
1416
1622
|
}
|
|
1417
1623
|
const EMPTY_SET = new Set();
|
|
1624
|
+
/** Whether a tree has a background fixed to the viewport: an image a box
|
|
1625
|
+
* names (`tree.backgrounds`), or the canvas's image or gradient — the
|
|
1626
|
+
* root's, or the body's it paints, which the root box stands in for where
|
|
1627
|
+
* the markup has neither element. */
|
|
1628
|
+
function hasFixedBackground(tree) {
|
|
1629
|
+
for (const box of tree.backgrounds) {
|
|
1630
|
+
if (box.style.backgroundAttachment === 'fixed')
|
|
1631
|
+
return true;
|
|
1632
|
+
}
|
|
1633
|
+
const root = tree.root;
|
|
1634
|
+
const body = root.children.find((child) => child.el?.name === 'body');
|
|
1635
|
+
for (const box of body ? [root, body] : [root]) {
|
|
1636
|
+
const style = box.style;
|
|
1637
|
+
if (style.backgroundAttachment === 'fixed' &&
|
|
1638
|
+
(style.backgroundImage ||
|
|
1639
|
+
style.backgroundImages ||
|
|
1640
|
+
style.backgroundGradient)) {
|
|
1641
|
+
return true;
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
return false;
|
|
1645
|
+
}
|
|
1418
1646
|
/** The first box an element made, depth first. */
|
|
1419
1647
|
function boxFor(root, element) {
|
|
1420
1648
|
const stack = [root];
|
|
@@ -1428,6 +1656,83 @@ function boxFor(root, element) {
|
|
|
1428
1656
|
}
|
|
1429
1657
|
return null;
|
|
1430
1658
|
}
|
|
1659
|
+
/**
|
|
1660
|
+
* An inline element's boxes: the one it has, or where a block in it broke
|
|
1661
|
+
* it (`breakAround`), each piece, in document order.
|
|
1662
|
+
*/
|
|
1663
|
+
function piecesOf(root, first) {
|
|
1664
|
+
// the first piece is cut at its end where there are more
|
|
1665
|
+
if (first.kind !== 'inline' || !(first.cut & 2))
|
|
1666
|
+
return [first];
|
|
1667
|
+
const pieces = [];
|
|
1668
|
+
const stack = [root];
|
|
1669
|
+
while (stack.length) {
|
|
1670
|
+
const box = stack.pop();
|
|
1671
|
+
if (box.el === first.el && box.kind === 'inline')
|
|
1672
|
+
pieces.push(box);
|
|
1673
|
+
for (let i = box.children.length - 1; i >= 0; i -= 1) {
|
|
1674
|
+
stack.push(box.children[i]);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
return pieces;
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* The bands of one inline box's fragments, or a text's: on the lines of
|
|
1681
|
+
* the block it is laid out in, and no text of a float or a positioned box
|
|
1682
|
+
* inside it — on lines of their own — is one of them (CSSOM View 6.1): a
|
|
1683
|
+
* list item made inline around an absolute link measured as the link.
|
|
1684
|
+
* Each across its text, the inline-blocks and inline boxes' edges it holds
|
|
1685
|
+
* and its own padding and border where it starts or ends there, and as
|
|
1686
|
+
* tall as its border box.
|
|
1687
|
+
*/
|
|
1688
|
+
function inlineBands(root, box, fonts, out) {
|
|
1689
|
+
// its own, apart from any other piece's: `fragmentHeights` makes each of
|
|
1690
|
+
// the bands it is handed as tall as this box on that band's line
|
|
1691
|
+
const bands = [];
|
|
1692
|
+
let block = box.parent;
|
|
1693
|
+
while (block && !block.lines)
|
|
1694
|
+
block = block.parent;
|
|
1695
|
+
if (box.subtreeTextEnd > box.subtreeTextStart) {
|
|
1696
|
+
if (box.kind === 'inline' && block?.lines) {
|
|
1697
|
+
lineBands(block.lines, box.subtreeTextStart, box.subtreeTextEnd, 0, 0, bands, true);
|
|
1698
|
+
}
|
|
1699
|
+
else {
|
|
1700
|
+
collectBands(root, box.subtreeTextStart, box.subtreeTextEnd, 0, 0, bands);
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
if (box.kind === 'inline') {
|
|
1704
|
+
fragmentReach(box, bands);
|
|
1705
|
+
if (fonts)
|
|
1706
|
+
fragmentHeights(box, bands, fonts);
|
|
1707
|
+
// where `position: relative` on it or an inline box around it moved
|
|
1708
|
+
// it, all of it (CSS 2.1 9.4.3), as `getBoundingClientRect` has it
|
|
1709
|
+
// (CSSOM View 6.1): the bands are where the lines put it, and a link
|
|
1710
|
+
// moved 120px down measured on its line, as wide as from where it was
|
|
1711
|
+
// laid out to where it was drawn
|
|
1712
|
+
const { x, y } = relativeOffsetOf(box);
|
|
1713
|
+
if (x || y) {
|
|
1714
|
+
for (const band of bands) {
|
|
1715
|
+
band.x += x;
|
|
1716
|
+
band.y += y;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
out.push(...bands);
|
|
1721
|
+
}
|
|
1722
|
+
/** How far an inline box and the inline boxes around it moved it by their
|
|
1723
|
+
* relative offsets (`INLINE_OFFSETS`). */
|
|
1724
|
+
function relativeOffsetOf(box) {
|
|
1725
|
+
let x = 0;
|
|
1726
|
+
let y = 0;
|
|
1727
|
+
for (let at = box; at?.kind === 'inline'; at = at.parent) {
|
|
1728
|
+
const offset = INLINE_OFFSETS.get(at);
|
|
1729
|
+
if (offset) {
|
|
1730
|
+
x += offset.x;
|
|
1731
|
+
y += offset.y;
|
|
1732
|
+
}
|
|
1733
|
+
}
|
|
1734
|
+
return { x, y };
|
|
1735
|
+
}
|
|
1431
1736
|
/** Where the document's text goes on after a box with none: the start of
|
|
1432
1737
|
* the next text in document order, or the end of the last before it. */
|
|
1433
1738
|
function textAfter(root, target) {
|
|
@@ -1488,6 +1793,7 @@ function sameRects(a, b) {
|
|
|
1488
1793
|
p.y !== q.y ||
|
|
1489
1794
|
p.width !== q.width ||
|
|
1490
1795
|
p.height !== q.height ||
|
|
1796
|
+
p.opacity !== q.opacity ||
|
|
1491
1797
|
!sameBare(p.bare, q.bare)) {
|
|
1492
1798
|
return false;
|
|
1493
1799
|
}
|
|
@@ -1503,7 +1809,8 @@ function sameBare(a, b) {
|
|
|
1503
1809
|
a.height === b.height &&
|
|
1504
1810
|
a.color === b.color &&
|
|
1505
1811
|
a.fontFamily === b.fontFamily &&
|
|
1506
|
-
a.fontSize === b.fontSize
|
|
1812
|
+
a.fontSize === b.fontSize &&
|
|
1813
|
+
a.chevron === b.chevron);
|
|
1507
1814
|
}
|
|
1508
1815
|
// --- walks over the laid-out tree -------------------------------------------
|
|
1509
1816
|
/** Distance from a coordinate to an interval, zero inside it. */
|
|
@@ -1768,7 +2075,10 @@ function fragmentHeights(box, bands, fonts) {
|
|
|
1768
2075
|
}
|
|
1769
2076
|
}
|
|
1770
2077
|
/** The bands of a text range on one block's own lines. */
|
|
1771
|
-
function lineBands(lines, from, to, dx, dy, out
|
|
2078
|
+
function lineBands(lines, from, to, dx, dy, out,
|
|
2079
|
+
/** Where the lines put the text, not where a relative offset moved it
|
|
2080
|
+
* (`TEXT_SHIFTS`): what an inline box's own offset is added to. */
|
|
2081
|
+
laidOut = false) {
|
|
1772
2082
|
// First line whose text can reach `from`, by binary search over the
|
|
1773
2083
|
// sorted text starts; stop at the first line past `to`.
|
|
1774
2084
|
let lo = 0;
|
|
@@ -1794,11 +2104,37 @@ function lineBands(lines, from, to, dx, dy, out) {
|
|
|
1794
2104
|
if (b <= a)
|
|
1795
2105
|
continue;
|
|
1796
2106
|
const offsets = layoutOffsetsOf(text.layout);
|
|
2107
|
+
const moved = laidOut ? (TEXT_SHIFTS.get(text)?.x ?? 0) : 0;
|
|
2108
|
+
// The spaces a line ends on are removed (CSS Text 3, 4.1.2), and
|
|
2109
|
+
// take no room on it: the engine sets them past its line's content,
|
|
2110
|
+
// where a caret after them goes, and a band that took them in made
|
|
2111
|
+
// a link that ends a line a space wider than Chrome's. So the text
|
|
2112
|
+
// a line ends with is measured no further than its content.
|
|
2113
|
+
const ends = endsLine(line, text, natural);
|
|
1797
2114
|
for (const band of bandsFor(text.layout, natural, offsets, layoutOffsetOf(text, a), layoutOffsetOf(text, b, true))) {
|
|
2115
|
+
let left = band.x;
|
|
2116
|
+
let right = band.x + band.width;
|
|
2117
|
+
if (ends) {
|
|
2118
|
+
left = Math.max(left, natural.x);
|
|
2119
|
+
right = Math.max(left, Math.min(right, natural.x + natural.width));
|
|
2120
|
+
}
|
|
2121
|
+
out.push({
|
|
2122
|
+
x: dx + left + text.drawX - moved,
|
|
2123
|
+
y: dy + line.y,
|
|
2124
|
+
width: right - left,
|
|
2125
|
+
height: line.height,
|
|
2126
|
+
});
|
|
2127
|
+
}
|
|
2128
|
+
// the spaces `pre-wrap` keeps that the line ends on, which have no
|
|
2129
|
+
// run: the engine hung them past the line (`LineText.hung`)
|
|
2130
|
+
for (const space of text.hung ?? []) {
|
|
2131
|
+
const at = documentOffsetOf(text, space.at);
|
|
2132
|
+
if (at < a || at >= b)
|
|
2133
|
+
continue;
|
|
1798
2134
|
out.push({
|
|
1799
|
-
x: dx +
|
|
2135
|
+
x: dx + space.x + text.drawX - moved,
|
|
1800
2136
|
y: dy + line.y,
|
|
1801
|
-
width:
|
|
2137
|
+
width: space.width,
|
|
1802
2138
|
height: line.height,
|
|
1803
2139
|
});
|
|
1804
2140
|
}
|
|
@@ -1806,6 +2142,18 @@ function lineBands(lines, from, to, dx, dy, out) {
|
|
|
1806
2142
|
}
|
|
1807
2143
|
}
|
|
1808
2144
|
}
|
|
2145
|
+
/** Whether a line's text is where the line ends: its last, with nothing
|
|
2146
|
+
* placed after it — an inline-block or an image past its content. */
|
|
2147
|
+
function endsLine(line, text, natural) {
|
|
2148
|
+
if (line.texts[line.texts.length - 1] !== text)
|
|
2149
|
+
return false;
|
|
2150
|
+
const right = text.drawX + natural.x + natural.width;
|
|
2151
|
+
for (const placed of line.atomics) {
|
|
2152
|
+
if (placed.x >= right - 0.01)
|
|
2153
|
+
return false;
|
|
2154
|
+
}
|
|
2155
|
+
return true;
|
|
2156
|
+
}
|
|
1809
2157
|
function collectBands(box, from, to, dx, dy, out) {
|
|
1810
2158
|
if (box.subtreeTextEnd <= box.subtreeTextStart)
|
|
1811
2159
|
return;
|
|
@@ -1831,7 +2179,11 @@ const HIT_POSITIONED = 3;
|
|
|
1831
2179
|
function deepestAt(tree, x, y,
|
|
1832
2180
|
/** Told whether what was found was found under text: a run's, rather
|
|
1833
2181
|
* than a box's. */
|
|
1834
|
-
hit
|
|
2182
|
+
hit,
|
|
2183
|
+
/** How far a box fixed to the viewport is drawn from where it was laid
|
|
2184
|
+
* out (`HtmlViewNode._fixedShift`): the point is taken back by it in
|
|
2185
|
+
* there, as paint moved the box by it (`atViewport`). */
|
|
2186
|
+
fixedShift = null) {
|
|
1835
2187
|
const box = tree.root;
|
|
1836
2188
|
let found = box.el;
|
|
1837
2189
|
let foundKey = [];
|
|
@@ -1872,7 +2224,25 @@ hit) {
|
|
|
1872
2224
|
// Zen Garden's archive links are absolute `<li>`s in an `overflow:
|
|
1873
2225
|
// hidden` list with no height of its own, and not one of them could be
|
|
1874
2226
|
// hovered or pressed.
|
|
2227
|
+
let atViewport = false;
|
|
1875
2228
|
const enter = (child, context, clipped) => {
|
|
2229
|
+
if (!fixedShift || atViewport || !fixedToViewport(child)) {
|
|
2230
|
+
enterAt(child, context, clipped);
|
|
2231
|
+
return;
|
|
2232
|
+
}
|
|
2233
|
+
x -= fixedShift.x;
|
|
2234
|
+
y -= fixedShift.y;
|
|
2235
|
+
atViewport = true;
|
|
2236
|
+
try {
|
|
2237
|
+
enterAt(child, context, clipped);
|
|
2238
|
+
}
|
|
2239
|
+
finally {
|
|
2240
|
+
x += fixedShift.x;
|
|
2241
|
+
y += fixedShift.y;
|
|
2242
|
+
atViewport = false;
|
|
2243
|
+
}
|
|
2244
|
+
};
|
|
2245
|
+
const enterAt = (child, context, clipped) => {
|
|
1876
2246
|
const style = child.style;
|
|
1877
2247
|
if (style.position !== 'static' ||
|
|
1878
2248
|
(child.parent?.kind === 'flex' && typeof style.zIndex === 'number')) {
|
|
@@ -2030,21 +2400,112 @@ const FACE_FIELDS = [
|
|
|
2030
2400
|
'letterSpacing',
|
|
2031
2401
|
'features',
|
|
2032
2402
|
];
|
|
2033
|
-
/**
|
|
2034
|
-
*
|
|
2035
|
-
|
|
2036
|
-
|
|
2403
|
+
/** The replaced boxes that are controls (`BoxTree.controls`, `controls.ts`):
|
|
2404
|
+
* a widget mounted beside the document, or a button whose press it reports. */
|
|
2405
|
+
const WIDGETS = new Set([
|
|
2406
|
+
'input',
|
|
2407
|
+
'textarea',
|
|
2408
|
+
'select',
|
|
2409
|
+
'button',
|
|
2410
|
+
'checkbox',
|
|
2411
|
+
'radio',
|
|
2412
|
+
]);
|
|
2413
|
+
/** What only moves how far a box's ink reaches: a shadow, and an outline's
|
|
2414
|
+
* size, which take no room (CSS Backgrounds 3, 7.1; CSS UI 4, 3). */
|
|
2415
|
+
const INK_REACH = new Set([
|
|
2416
|
+
'boxShadow',
|
|
2417
|
+
'outlineStyle',
|
|
2418
|
+
'outlineWidth',
|
|
2419
|
+
'outlineOffset',
|
|
2420
|
+
]);
|
|
2421
|
+
/**
|
|
2422
|
+
* How two styles of one element differ, for a restyle in place: null where
|
|
2423
|
+
* they do not, a `HoverChange` where every difference is ink, how far ink
|
|
2424
|
+
* reaches, a `z-index` that stays a stacking context's, or a translation —
|
|
2425
|
+
* none of which moves anything but the box and what it holds (CSS
|
|
2426
|
+
* Transforms 1: a transform does not affect layout) — and false where
|
|
2427
|
+
* anything else differs.
|
|
2428
|
+
*/
|
|
2429
|
+
function hoverChange(was, now) {
|
|
2037
2430
|
const a = was;
|
|
2038
2431
|
const b = now;
|
|
2039
|
-
let
|
|
2432
|
+
let change = null;
|
|
2040
2433
|
for (const key in b) {
|
|
2041
2434
|
if (key === 'custom' || sameValue(a[key], b[key]))
|
|
2042
2435
|
continue;
|
|
2043
|
-
|
|
2436
|
+
change ??= { reach: false, order: false, move: false };
|
|
2437
|
+
if (PAINT_ONLY.has(key))
|
|
2438
|
+
continue;
|
|
2439
|
+
if (INK_REACH.has(key)) {
|
|
2440
|
+
change.reach = true;
|
|
2441
|
+
continue;
|
|
2442
|
+
}
|
|
2443
|
+
if (key === 'zIndex') {
|
|
2444
|
+
// one stacking context either way, with nothing hoisted below the
|
|
2445
|
+
// flow (`hoistNegative`): only its place among its layers changes
|
|
2446
|
+
const z0 = a[key];
|
|
2447
|
+
const z1 = b[key];
|
|
2448
|
+
if (typeof z0 !== 'number' || typeof z1 !== 'number')
|
|
2449
|
+
return false;
|
|
2450
|
+
if (z0 < 0 || z1 < 0)
|
|
2451
|
+
return false;
|
|
2452
|
+
change.order = true;
|
|
2453
|
+
continue;
|
|
2454
|
+
}
|
|
2455
|
+
if (key === 'transformTranslate' || key === 'translate') {
|
|
2456
|
+
change.move = true;
|
|
2457
|
+
continue;
|
|
2458
|
+
}
|
|
2459
|
+
return false;
|
|
2460
|
+
}
|
|
2461
|
+
return change;
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* Whether a box can take a new translation where it is: a box of its own
|
|
2465
|
+
* rather than an inline one, whose text is what moves; and the same
|
|
2466
|
+
* containing block and stacking context for what is in it, which a
|
|
2467
|
+
* transform makes a box (CSS Transforms 1, 2) — so either transformed
|
|
2468
|
+
* before and after, or already positioned with a `z-index`, and holding
|
|
2469
|
+
* nothing fixed, which only a transform takes in.
|
|
2470
|
+
*/
|
|
2471
|
+
function movable(box, was, now) {
|
|
2472
|
+
if (box.kind === 'inline')
|
|
2473
|
+
return false;
|
|
2474
|
+
if (transformed(was) === transformed(now))
|
|
2475
|
+
return true;
|
|
2476
|
+
if (was.position === 'static')
|
|
2477
|
+
return false;
|
|
2478
|
+
if (typeof was.zIndex !== 'number' || typeof now.zIndex !== 'number') {
|
|
2479
|
+
return false;
|
|
2480
|
+
}
|
|
2481
|
+
const stack = [...box.children];
|
|
2482
|
+
while (stack.length) {
|
|
2483
|
+
const at = stack.pop();
|
|
2484
|
+
if (at.style.position === 'fixed')
|
|
2044
2485
|
return false;
|
|
2045
|
-
|
|
2486
|
+
for (const child of at.children)
|
|
2487
|
+
stack.push(child);
|
|
2046
2488
|
}
|
|
2047
|
-
return
|
|
2489
|
+
return true;
|
|
2490
|
+
}
|
|
2491
|
+
/** Where a box draws, in document coordinates: its ink bounds, or for an
|
|
2492
|
+
* inline box or a run of text, which are drawn on their block's lines and
|
|
2493
|
+
* have none of their own, the block's. */
|
|
2494
|
+
function inkOf(box) {
|
|
2495
|
+
let at = box;
|
|
2496
|
+
while (at && (at.kind === 'text' || at.kind === 'break' || !hasRect(at))) {
|
|
2497
|
+
at = at.parent;
|
|
2498
|
+
}
|
|
2499
|
+
if (!at || at.boundsY === Infinity)
|
|
2500
|
+
return null;
|
|
2501
|
+
if (!(at.boundsWidth > 0 && at.boundsHeight > 0))
|
|
2502
|
+
return null;
|
|
2503
|
+
return {
|
|
2504
|
+
x: at.boundsX,
|
|
2505
|
+
y: at.boundsY,
|
|
2506
|
+
width: at.boundsWidth,
|
|
2507
|
+
height: at.boundsHeight,
|
|
2508
|
+
};
|
|
2048
2509
|
}
|
|
2049
2510
|
/** Structural equality for a computed value: a number, a string, or the
|
|
2050
2511
|
* plain objects and arrays a length or a shadow list is. */
|