@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/src/html/node.ts
CHANGED
|
@@ -60,8 +60,12 @@ import {
|
|
|
60
60
|
BOX_RAISES,
|
|
61
61
|
buildBoxes,
|
|
62
62
|
CONTENT_IMAGES,
|
|
63
|
+
CUT_BLOCKS,
|
|
64
|
+
GENERATED_FROM,
|
|
65
|
+
INLINE_OFFSETS,
|
|
63
66
|
LINE_BOX_RAISES,
|
|
64
67
|
SHIFTED_LINES,
|
|
68
|
+
TEXT_SHIFTS,
|
|
65
69
|
} from './layout/boxes.js';
|
|
66
70
|
import type {
|
|
67
71
|
Box,
|
|
@@ -71,10 +75,11 @@ import type {
|
|
|
71
75
|
ReplacedKind,
|
|
72
76
|
TextLayoutLike,
|
|
73
77
|
} from './layout/boxes.js';
|
|
74
|
-
import { layoutDocument } from './layout/block.js';
|
|
78
|
+
import { layoutDocument, retranslate, transformed } from './layout/block.js';
|
|
75
79
|
import { TextLayoutCache } from './layout/cache.js';
|
|
76
80
|
import { shapingSafe } from './layout/shaping.js';
|
|
77
|
-
import { SurfaceCache } from './surfaces.js';
|
|
81
|
+
import { SurfaceCache, newSurface } from './surfaces.js';
|
|
82
|
+
import type { SurfaceLike } from './surfaces.js';
|
|
78
83
|
import { faceExtentOf, inlineDecoration, runFor } from './layout/inline.js';
|
|
79
84
|
import type { FontsLike } from './layout/inline.js';
|
|
80
85
|
import type { TextRun } from '../richtext/index.js';
|
|
@@ -88,12 +93,18 @@ import {
|
|
|
88
93
|
import { lineBands as bandsFor } from '../richtext/runs.js';
|
|
89
94
|
import {
|
|
90
95
|
clipsOverflow,
|
|
96
|
+
computePaintBounds,
|
|
91
97
|
containingBlockOf,
|
|
98
|
+
FIXED_BOXES,
|
|
99
|
+
fixedToViewport,
|
|
100
|
+
forgetDecoratedAncestors,
|
|
92
101
|
hasRect,
|
|
102
|
+
hoistNegative,
|
|
93
103
|
holds,
|
|
94
104
|
holdsAbsolute,
|
|
95
105
|
paintDocument,
|
|
96
106
|
queryChildIndex,
|
|
107
|
+
stackLayers,
|
|
97
108
|
} from './paint.js';
|
|
98
109
|
import type { PaintContext } from './paint.js';
|
|
99
110
|
import { controlRectsOf, measureControl } from './controls.js';
|
|
@@ -221,6 +232,8 @@ export class HtmlViewNode extends Node {
|
|
|
221
232
|
private _sheetsRead: SheetsRead | null = null;
|
|
222
233
|
/** Blurred shadows, drawn once each. */
|
|
223
234
|
private _shadowCache: SurfaceCache | null = null;
|
|
235
|
+
/** Whether the backend was found to have no offscreen surface. */
|
|
236
|
+
private _noSurface = false;
|
|
224
237
|
private _cascade: Cascade | null = null;
|
|
225
238
|
private _tree: BoxTree | null = null;
|
|
226
239
|
/** The faces `_warmFaces` has asked the font matcher for, and of which
|
|
@@ -440,13 +453,15 @@ export class HtmlViewNode extends Node {
|
|
|
440
453
|
*/
|
|
441
454
|
private _requestBackgrounds(tree: BoxTree): void {
|
|
442
455
|
for (const box of tree.backgrounds) {
|
|
443
|
-
|
|
456
|
+
// a pseudo-element's are its element's
|
|
457
|
+
const element = box.el ?? GENERATED_FROM.get(box);
|
|
458
|
+
if (!element) continue;
|
|
444
459
|
// each layer's, where there is more than one
|
|
445
460
|
for (const url of box.style.backgroundImages ?? [
|
|
446
461
|
box.style.backgroundImage,
|
|
447
462
|
]) {
|
|
448
463
|
if (typeof url === 'string') {
|
|
449
|
-
this._resources.request({ url, kind: 'image', element
|
|
464
|
+
this._resources.request({ url, kind: 'image', element });
|
|
450
465
|
}
|
|
451
466
|
}
|
|
452
467
|
const border = box.style.borderImage.source;
|
|
@@ -454,9 +469,15 @@ export class HtmlViewNode extends Node {
|
|
|
454
469
|
this._resources.request({
|
|
455
470
|
url: border,
|
|
456
471
|
kind: 'image',
|
|
457
|
-
element
|
|
472
|
+
element,
|
|
458
473
|
});
|
|
459
474
|
}
|
|
475
|
+
// and each mask layer's, which is an image as a background's is
|
|
476
|
+
for (const url of box.style.mask.images) {
|
|
477
|
+
if (typeof url === 'string') {
|
|
478
|
+
this._resources.request({ url, kind: 'image', element });
|
|
479
|
+
}
|
|
480
|
+
}
|
|
460
481
|
}
|
|
461
482
|
// and every image generated content names, which is only known there
|
|
462
483
|
// too
|
|
@@ -705,6 +726,55 @@ export class HtmlViewNode extends Node {
|
|
|
705
726
|
return true;
|
|
706
727
|
}
|
|
707
728
|
|
|
729
|
+
/**
|
|
730
|
+
* The viewport the document is seen through, in window coordinates: the
|
|
731
|
+
* content box of the pane that scrolls the element, where one does —
|
|
732
|
+
* what `position: fixed` and a fixed background are placed against, as
|
|
733
|
+
* a browser places them against its page area. Null where nothing
|
|
734
|
+
* scrolls it, and the element is its own.
|
|
735
|
+
*/
|
|
736
|
+
private _viewport(): Rect | null {
|
|
737
|
+
for (let node = this.parent; node; node = node.parent) {
|
|
738
|
+
// `Scrollable`'s, and not every node's: asked the way core asks it
|
|
739
|
+
const scroller = node as { isScroller?: () => boolean };
|
|
740
|
+
if (!scroller.isScroller?.()) continue;
|
|
741
|
+
const box = node.contentBox();
|
|
742
|
+
return box.width > 0 && box.height > 0 ? box : null;
|
|
743
|
+
}
|
|
744
|
+
return null;
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
/**
|
|
748
|
+
* What the document draws fixed to the viewport of the pane that scrolls
|
|
749
|
+
* it, for that pane's scroll blit to repaint rather than copy with the
|
|
750
|
+
* text (react-x11's `viewportFixedRects`): the whole viewport where a
|
|
751
|
+
* fixed background shows through it, which makes a scroll a repaint, and
|
|
752
|
+
* where each fixed box is drawn. Null for a document with neither, and
|
|
753
|
+
* where nothing scrolls the element.
|
|
754
|
+
*/
|
|
755
|
+
override viewportFixedRects(): Rect[] | null {
|
|
756
|
+
const tree = this._tree;
|
|
757
|
+
const viewport = tree && this._viewport();
|
|
758
|
+
if (!tree || !viewport) return null;
|
|
759
|
+
if (hasFixedBackground(tree)) return [viewport];
|
|
760
|
+
const boxes = FIXED_BOXES.get(tree);
|
|
761
|
+
if (!boxes) return null;
|
|
762
|
+
// laid out against the viewport at the document's top, and drawn at the
|
|
763
|
+
// viewport (`atViewport`)
|
|
764
|
+
const root = tree.root;
|
|
765
|
+
const out: Rect[] = [];
|
|
766
|
+
for (const box of boxes) {
|
|
767
|
+
if (!(box.boundsWidth > 0 && box.boundsHeight > 0)) continue;
|
|
768
|
+
out.push({
|
|
769
|
+
x: viewport.x + box.boundsX - root.x,
|
|
770
|
+
y: viewport.y + box.boundsY - root.y,
|
|
771
|
+
width: box.boundsWidth,
|
|
772
|
+
height: box.boundsHeight,
|
|
773
|
+
});
|
|
774
|
+
}
|
|
775
|
+
return out.length ? out : null;
|
|
776
|
+
}
|
|
777
|
+
|
|
708
778
|
private _viewportHeight(): number {
|
|
709
779
|
// The viewport a `vh` resolves against is the one the document is seen
|
|
710
780
|
// through, not the document — a document taller than it does not make
|
|
@@ -1196,39 +1266,49 @@ export class HtmlViewNode extends Node {
|
|
|
1196
1266
|
rect = { x: box.x, y: box.y, width: box.width, height: box.height };
|
|
1197
1267
|
} else {
|
|
1198
1268
|
const bands: Rect[] = [];
|
|
1199
|
-
|
|
1200
|
-
//
|
|
1201
|
-
//
|
|
1202
|
-
//
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
box.subtreeTextStart,
|
|
1210
|
-
box.subtreeTextEnd,
|
|
1211
|
-
0,
|
|
1212
|
-
0,
|
|
1213
|
-
bands,
|
|
1214
|
-
);
|
|
1215
|
-
} else {
|
|
1216
|
-
collectBands(
|
|
1217
|
-
tree.root,
|
|
1218
|
-
box.subtreeTextStart,
|
|
1219
|
-
box.subtreeTextEnd,
|
|
1220
|
-
0,
|
|
1221
|
-
0,
|
|
1222
|
-
bands,
|
|
1223
|
-
);
|
|
1224
|
-
}
|
|
1269
|
+
const fonts = this._layouts?.fonts ?? null;
|
|
1270
|
+
// an inline box broken around the blocks in it is its pieces, a box
|
|
1271
|
+
// of the element's each (CSS 2.1 9.2.1.1), and its fragments are
|
|
1272
|
+
// theirs; and on the lines between them it has a fragment around each
|
|
1273
|
+
// block, across the box the block is in and as tall as its border
|
|
1274
|
+
// box, as a browser's has — Blink's block-in-inline, Gecko's split
|
|
1275
|
+
// inline's anonymous block. A list item made inline around a block
|
|
1276
|
+
// link measured as the empty edge after it
|
|
1277
|
+
for (const piece of piecesOf(tree.root, box)) {
|
|
1278
|
+
inlineBands(tree.root, piece, fonts, bands);
|
|
1225
1279
|
}
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1280
|
+
for (const block of (box.cut ? CUT_BLOCKS.get(box) : null) ?? []) {
|
|
1281
|
+
const around = block.parent;
|
|
1282
|
+
if (!around) continue;
|
|
1283
|
+
bands.push({
|
|
1284
|
+
x: around.contentX,
|
|
1285
|
+
y: block.y,
|
|
1286
|
+
width: around.contentWidth,
|
|
1287
|
+
height: block.height,
|
|
1288
|
+
});
|
|
1230
1289
|
}
|
|
1231
1290
|
for (const band of bands) rect = rect ? unionRect(rect, band) : band;
|
|
1291
|
+
if (!rect) {
|
|
1292
|
+
// on no line, where its block has none: text set at no size takes
|
|
1293
|
+
// no room and gets no box, and CSS puts it on lines of no height
|
|
1294
|
+
// at the block's top (9.4.2), as a browser measures it — nothing,
|
|
1295
|
+
// where the block's content starts. Where the text after it
|
|
1296
|
+
// starts was the next text that took room, elsewhere in the
|
|
1297
|
+
// document: the Zen Garden's 170 hides a heading at `font-size:
|
|
1298
|
+
// 0`, and an `<abbr>` in it measured as the end of the paragraph
|
|
1299
|
+
// above it
|
|
1300
|
+
let block = box.parent;
|
|
1301
|
+
while (block && block.kind === 'inline') block = block.parent;
|
|
1302
|
+
if (block && !block.lines?.length) {
|
|
1303
|
+
const rtl = block.style.direction === 'rtl';
|
|
1304
|
+
rect = {
|
|
1305
|
+
x: rtl ? block.contentX + block.contentWidth : block.contentX,
|
|
1306
|
+
y: block.contentY,
|
|
1307
|
+
width: 0,
|
|
1308
|
+
height: 0,
|
|
1309
|
+
};
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1232
1312
|
if (!rect) {
|
|
1233
1313
|
// an empty subtree's range is `[0, 0)`, wherever it stands: where
|
|
1234
1314
|
// its text would be is where the text after it starts
|
|
@@ -1251,7 +1331,20 @@ export class HtmlViewNode extends Node {
|
|
|
1251
1331
|
const tree = this._tree;
|
|
1252
1332
|
if (!tree) return null;
|
|
1253
1333
|
const local = this._toDocument(x, y);
|
|
1254
|
-
return deepestAt(tree, local.x, local.y);
|
|
1334
|
+
return deepestAt(tree, local.x, local.y, undefined, this._fixedShift());
|
|
1335
|
+
}
|
|
1336
|
+
|
|
1337
|
+
/** How far a box fixed to the viewport is drawn from where it was laid
|
|
1338
|
+
* out, in the document's pixels: the scroll of the pane the element is
|
|
1339
|
+
* in (`atViewport`). Null where nothing scrolls it. */
|
|
1340
|
+
private _fixedShift(): { x: number; y: number } | null {
|
|
1341
|
+
const tree = this._tree;
|
|
1342
|
+
const viewport = tree && FIXED_BOXES.has(tree) && this._viewport();
|
|
1343
|
+
if (!tree || !viewport) return null;
|
|
1344
|
+
return {
|
|
1345
|
+
x: viewport.x - this.abs.x - tree.root.x,
|
|
1346
|
+
y: viewport.y - this.abs.y - tree.root.y,
|
|
1347
|
+
};
|
|
1255
1348
|
}
|
|
1256
1349
|
|
|
1257
1350
|
/**
|
|
@@ -1268,7 +1361,13 @@ export class HtmlViewNode extends Node {
|
|
|
1268
1361
|
const tree = this._tree;
|
|
1269
1362
|
if (!tree) return null;
|
|
1270
1363
|
const hit = { text: false };
|
|
1271
|
-
const el = deepestAt(
|
|
1364
|
+
const el = deepestAt(
|
|
1365
|
+
tree,
|
|
1366
|
+
x - this.abs.x,
|
|
1367
|
+
y - this.abs.y,
|
|
1368
|
+
hit,
|
|
1369
|
+
this._fixedShift(),
|
|
1370
|
+
);
|
|
1272
1371
|
const cursor = el ? tree.styles.get(el)?.style.cursor : null;
|
|
1273
1372
|
// a keyword; a `url()` this cannot load falls back, as its list would
|
|
1274
1373
|
if (cursor && cursor !== 'auto' && /^[a-z-]+$/.test(cursor)) return cursor;
|
|
@@ -1309,9 +1408,8 @@ export class HtmlViewNode extends Node {
|
|
|
1309
1408
|
|
|
1310
1409
|
/** The hovered chain moved: restyle where it did, or the document. */
|
|
1311
1410
|
private _restyleHover(was: readonly Element[], now: readonly Element[]) {
|
|
1312
|
-
|
|
1313
|
-
if (
|
|
1314
|
-
else if (done === false) this._invalidate(Stale.Boxes);
|
|
1411
|
+
// a restyle in place names what it repainted (`_hoverInPlace`)
|
|
1412
|
+
if (this._hoverInPlace(was, now) === false) this._invalidate(Stale.Boxes);
|
|
1315
1413
|
}
|
|
1316
1414
|
|
|
1317
1415
|
/**
|
|
@@ -1398,6 +1496,11 @@ export class HtmlViewNode extends Node {
|
|
|
1398
1496
|
const fresh = new Map<Element, ComputedStyle>();
|
|
1399
1497
|
const changed = new Map<Element, ComputedStyle>();
|
|
1400
1498
|
let refused = false;
|
|
1499
|
+
// what changed beyond ink (`hoverChange`): how far some box's ink
|
|
1500
|
+
// reaches, the order its layer paints in, where one is
|
|
1501
|
+
let reaches = false;
|
|
1502
|
+
let reorders = false;
|
|
1503
|
+
const moving = new Set<Element>();
|
|
1401
1504
|
const styleOf = (el: Element): ComputedStyle | null => {
|
|
1402
1505
|
const kept = tree.styles.get(el);
|
|
1403
1506
|
if (!kept) return null;
|
|
@@ -1414,9 +1517,14 @@ export class HtmlViewNode extends Node {
|
|
|
1414
1517
|
}
|
|
1415
1518
|
const style = cascade.styleFor(el, parentStyle, kept.inFlex);
|
|
1416
1519
|
fresh.set(el, style);
|
|
1417
|
-
const diff =
|
|
1520
|
+
const diff = hoverChange(kept.style, style);
|
|
1418
1521
|
if (diff === false) refused = true;
|
|
1419
|
-
else if (diff)
|
|
1522
|
+
else if (diff) {
|
|
1523
|
+
changed.set(el, style);
|
|
1524
|
+
if (diff.reach) reaches = true;
|
|
1525
|
+
if (diff.order) reorders = true;
|
|
1526
|
+
if (diff.move) moving.add(el);
|
|
1527
|
+
}
|
|
1420
1528
|
return style;
|
|
1421
1529
|
};
|
|
1422
1530
|
for (const el of reach) {
|
|
@@ -1445,9 +1553,12 @@ export class HtmlViewNode extends Node {
|
|
|
1445
1553
|
if (!style) continue;
|
|
1446
1554
|
const kept = tree.styles.get(owner)!;
|
|
1447
1555
|
// an anonymous box, a pseudo-element, a marker, a control: a style
|
|
1448
|
-
// derived from the element's, or drawn from it somewhere else
|
|
1556
|
+
// derived from the element's, or drawn from it somewhere else — a
|
|
1557
|
+
// widget mounted beside the document takes its look from it. An
|
|
1558
|
+
// image, a drawing or a rule is drawn here, from its box, like any.
|
|
1449
1559
|
if (!box.el || box.style !== kept.style) return false;
|
|
1450
|
-
if (box.marker || box.replaced
|
|
1560
|
+
if (box.marker || WIDGETS.has(box.replaced)) return false;
|
|
1561
|
+
if (moving.has(owner) && !movable(box, kept.style, style)) return false;
|
|
1451
1562
|
restyled.push([box, style]);
|
|
1452
1563
|
if (box.kind === 'inline') {
|
|
1453
1564
|
const decoration = inlineDecoration(fonts, box, style);
|
|
@@ -1479,9 +1590,28 @@ export class HtmlViewNode extends Node {
|
|
|
1479
1590
|
}
|
|
1480
1591
|
}
|
|
1481
1592
|
|
|
1593
|
+
// what each box drew before, to be repainted with what it draws after
|
|
1594
|
+
const inks: Rect[] = [];
|
|
1595
|
+
for (const [box] of restyled) {
|
|
1596
|
+
const ink = inkOf(box);
|
|
1597
|
+
if (ink) inks.push(ink);
|
|
1598
|
+
}
|
|
1599
|
+
|
|
1482
1600
|
// and only now, all of it
|
|
1483
|
-
|
|
1484
|
-
for (const [box,
|
|
1601
|
+
const moved: [Box, ComputedStyle][] = [];
|
|
1602
|
+
for (const [box, style] of restyled) {
|
|
1603
|
+
const was = box.style;
|
|
1604
|
+
box.style = style;
|
|
1605
|
+
if (moving.has(box.el!)) moved.push([box, was]);
|
|
1606
|
+
}
|
|
1607
|
+
for (const [box, decoration] of redecorated) {
|
|
1608
|
+
// what is inside it keeps which boxes around it paint, and this is
|
|
1609
|
+
// one more or one fewer of them
|
|
1610
|
+
if ((decoration === null) !== (box.decoration === null)) {
|
|
1611
|
+
forgetDecoratedAncestors(box);
|
|
1612
|
+
}
|
|
1613
|
+
box.decoration = decoration;
|
|
1614
|
+
}
|
|
1485
1615
|
for (const text of texts) {
|
|
1486
1616
|
const next = relaid.get(text.layout);
|
|
1487
1617
|
if (next) text.layout = next;
|
|
@@ -1489,9 +1619,67 @@ export class HtmlViewNode extends Node {
|
|
|
1489
1619
|
for (const [el, style] of changed) {
|
|
1490
1620
|
tree.styles.set(el, { style, inFlex: tree.styles.get(el)!.inFlex });
|
|
1491
1621
|
}
|
|
1622
|
+
// A box that moved moves what is in it, as layout moved it the first
|
|
1623
|
+
// time (`applyRelativeOffsets`), and a later layout moves it the same
|
|
1624
|
+
// way; where it reached the document's end, before or after, the
|
|
1625
|
+
// document's height may have changed with it, which only a layout says.
|
|
1626
|
+
let relayout = false;
|
|
1627
|
+
for (const [box, was] of moved) {
|
|
1628
|
+
const before = box.boundsY + box.boundsHeight;
|
|
1629
|
+
retranslate(box, was);
|
|
1630
|
+
tree.relative = true;
|
|
1631
|
+
if (before >= this._documentHeight) relayout = true;
|
|
1632
|
+
}
|
|
1633
|
+
// how far the boxes' ink reaches, and the order their layers paint in,
|
|
1634
|
+
// as layout leaves them (`layoutDocument`)
|
|
1635
|
+
if (reaches || reorders || moved.length) {
|
|
1636
|
+
computePaintBounds(tree.root, tree.movedInline);
|
|
1637
|
+
if (tree.negative) hoistNegative(tree.root);
|
|
1638
|
+
stackLayers(tree.root);
|
|
1639
|
+
}
|
|
1640
|
+
for (const [box] of moved) {
|
|
1641
|
+
if (box.boundsY + box.boundsHeight >= this._documentHeight) {
|
|
1642
|
+
relayout = true;
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
if (moved.length) this._reportControls();
|
|
1646
|
+
if (relayout) {
|
|
1647
|
+
this._invalidate(Stale.Layout);
|
|
1648
|
+
return 'painted';
|
|
1649
|
+
}
|
|
1650
|
+
for (const [box] of restyled) {
|
|
1651
|
+
const ink = inkOf(box);
|
|
1652
|
+
if (ink) inks.push(ink);
|
|
1653
|
+
}
|
|
1654
|
+
this._repaintInk(inks);
|
|
1492
1655
|
return 'painted';
|
|
1493
1656
|
}
|
|
1494
1657
|
|
|
1658
|
+
/**
|
|
1659
|
+
* Repaint the document where `inks` are, in its own coordinates: the ink
|
|
1660
|
+
* of the boxes a hover restyled, before and after. Past a handful of rects
|
|
1661
|
+
* the damage is one around them all, which is what core would make of
|
|
1662
|
+
* them anyway.
|
|
1663
|
+
*/
|
|
1664
|
+
private _repaintInk(inks: Rect[]): void {
|
|
1665
|
+
if (!inks.length) return;
|
|
1666
|
+
const x = this.abs.x;
|
|
1667
|
+
const y = this.abs.y;
|
|
1668
|
+
const place = (r: Rect): Rect => ({
|
|
1669
|
+
x: Math.floor(x + r.x),
|
|
1670
|
+
y: Math.floor(y + r.y),
|
|
1671
|
+
width: Math.ceil(r.width) + 1,
|
|
1672
|
+
height: Math.ceil(r.height) + 1,
|
|
1673
|
+
});
|
|
1674
|
+
if (inks.length > 6) {
|
|
1675
|
+
let all = inks[0];
|
|
1676
|
+
for (const r of inks) all = unionRect(all, r);
|
|
1677
|
+
this.invalidate(false, place(all), 'props');
|
|
1678
|
+
return;
|
|
1679
|
+
}
|
|
1680
|
+
for (const r of inks) this.invalidate(false, place(r), 'props');
|
|
1681
|
+
}
|
|
1682
|
+
|
|
1495
1683
|
/** The document, for an application that wants to read or change it. */
|
|
1496
1684
|
get document(): Document {
|
|
1497
1685
|
return this._source.document;
|
|
@@ -1570,6 +1758,15 @@ export class HtmlViewNode extends Node {
|
|
|
1570
1758
|
}
|
|
1571
1759
|
}
|
|
1572
1760
|
|
|
1761
|
+
/** A surface for the painter to draw a masked element on, asked of the
|
|
1762
|
+
* backend until it is found to have none. */
|
|
1763
|
+
private _surface(width: number, height: number): SurfaceLike | null {
|
|
1764
|
+
if (this._noSurface) return null;
|
|
1765
|
+
const surface = newSurface(this.app, width, height);
|
|
1766
|
+
if (!surface) this._noSurface = true;
|
|
1767
|
+
return surface;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1573
1770
|
private _paint(
|
|
1574
1771
|
ctx: Context2D,
|
|
1575
1772
|
tree: BoxTree,
|
|
@@ -1582,6 +1779,7 @@ export class HtmlViewNode extends Node {
|
|
|
1582
1779
|
// the root's background covers the whole element, not only the
|
|
1583
1780
|
// document: an element grown past its content is canvas too
|
|
1584
1781
|
canvas: this.abs,
|
|
1782
|
+
viewport: this._viewport() ?? undefined,
|
|
1585
1783
|
scale: this._scale,
|
|
1586
1784
|
damage,
|
|
1587
1785
|
selection: range
|
|
@@ -1597,6 +1795,7 @@ export class HtmlViewNode extends Node {
|
|
|
1597
1795
|
const size = image ? this._resources.imageSize(url) : null;
|
|
1598
1796
|
return image && size ? { image, ...size } : null;
|
|
1599
1797
|
},
|
|
1798
|
+
surface: (width, height) => this._surface(width, height),
|
|
1600
1799
|
cached: (key, width, height, draw) =>
|
|
1601
1800
|
(this._shadowCache ??= new SurfaceCache(this.app)).get(
|
|
1602
1801
|
key,
|
|
@@ -1610,6 +1809,30 @@ export class HtmlViewNode extends Node {
|
|
|
1610
1809
|
|
|
1611
1810
|
const EMPTY_SET: ReadonlySet<Element> = new Set();
|
|
1612
1811
|
|
|
1812
|
+
/** Whether a tree has a background fixed to the viewport: an image a box
|
|
1813
|
+
* names (`tree.backgrounds`), or the canvas's image or gradient — the
|
|
1814
|
+
* root's, or the body's it paints, which the root box stands in for where
|
|
1815
|
+
* the markup has neither element. */
|
|
1816
|
+
function hasFixedBackground(tree: BoxTree): boolean {
|
|
1817
|
+
for (const box of tree.backgrounds) {
|
|
1818
|
+
if (box.style.backgroundAttachment === 'fixed') return true;
|
|
1819
|
+
}
|
|
1820
|
+
const root = tree.root;
|
|
1821
|
+
const body = root.children.find((child) => child.el?.name === 'body');
|
|
1822
|
+
for (const box of body ? [root, body] : [root]) {
|
|
1823
|
+
const style = box.style;
|
|
1824
|
+
if (
|
|
1825
|
+
style.backgroundAttachment === 'fixed' &&
|
|
1826
|
+
(style.backgroundImage ||
|
|
1827
|
+
style.backgroundImages ||
|
|
1828
|
+
style.backgroundGradient)
|
|
1829
|
+
) {
|
|
1830
|
+
return true;
|
|
1831
|
+
}
|
|
1832
|
+
}
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1613
1836
|
/** A sheet as the restyle reads it: its text, the encoding it was decoded
|
|
1614
1837
|
* from, the element that brought it, and what its URLs resolve against. */
|
|
1615
1838
|
interface SheetText {
|
|
@@ -1656,6 +1879,94 @@ function boxFor(root: Box, element: Element): Box | null {
|
|
|
1656
1879
|
return null;
|
|
1657
1880
|
}
|
|
1658
1881
|
|
|
1882
|
+
/**
|
|
1883
|
+
* An inline element's boxes: the one it has, or where a block in it broke
|
|
1884
|
+
* it (`breakAround`), each piece, in document order.
|
|
1885
|
+
*/
|
|
1886
|
+
function piecesOf(root: Box, first: Box): Box[] {
|
|
1887
|
+
// the first piece is cut at its end where there are more
|
|
1888
|
+
if (first.kind !== 'inline' || !(first.cut & 2)) return [first];
|
|
1889
|
+
const pieces: Box[] = [];
|
|
1890
|
+
const stack: Box[] = [root];
|
|
1891
|
+
while (stack.length) {
|
|
1892
|
+
const box = stack.pop()!;
|
|
1893
|
+
if (box.el === first.el && box.kind === 'inline') pieces.push(box);
|
|
1894
|
+
for (let i = box.children.length - 1; i >= 0; i -= 1) {
|
|
1895
|
+
stack.push(box.children[i]);
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
return pieces;
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
/**
|
|
1902
|
+
* The bands of one inline box's fragments, or a text's: on the lines of
|
|
1903
|
+
* the block it is laid out in, and no text of a float or a positioned box
|
|
1904
|
+
* inside it — on lines of their own — is one of them (CSSOM View 6.1): a
|
|
1905
|
+
* list item made inline around an absolute link measured as the link.
|
|
1906
|
+
* Each across its text, the inline-blocks and inline boxes' edges it holds
|
|
1907
|
+
* and its own padding and border where it starts or ends there, and as
|
|
1908
|
+
* tall as its border box.
|
|
1909
|
+
*/
|
|
1910
|
+
function inlineBands(
|
|
1911
|
+
root: Box,
|
|
1912
|
+
box: Box,
|
|
1913
|
+
fonts: FontsLike | null,
|
|
1914
|
+
out: Rect[],
|
|
1915
|
+
): void {
|
|
1916
|
+
// its own, apart from any other piece's: `fragmentHeights` makes each of
|
|
1917
|
+
// the bands it is handed as tall as this box on that band's line
|
|
1918
|
+
const bands: Rect[] = [];
|
|
1919
|
+
let block: Box | null = box.parent;
|
|
1920
|
+
while (block && !block.lines) block = block.parent;
|
|
1921
|
+
if (box.subtreeTextEnd > box.subtreeTextStart) {
|
|
1922
|
+
if (box.kind === 'inline' && block?.lines) {
|
|
1923
|
+
lineBands(
|
|
1924
|
+
block.lines,
|
|
1925
|
+
box.subtreeTextStart,
|
|
1926
|
+
box.subtreeTextEnd,
|
|
1927
|
+
0,
|
|
1928
|
+
0,
|
|
1929
|
+
bands,
|
|
1930
|
+
true,
|
|
1931
|
+
);
|
|
1932
|
+
} else {
|
|
1933
|
+
collectBands(root, box.subtreeTextStart, box.subtreeTextEnd, 0, 0, bands);
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
if (box.kind === 'inline') {
|
|
1937
|
+
fragmentReach(box, bands);
|
|
1938
|
+
if (fonts) fragmentHeights(box, bands, fonts);
|
|
1939
|
+
// where `position: relative` on it or an inline box around it moved
|
|
1940
|
+
// it, all of it (CSS 2.1 9.4.3), as `getBoundingClientRect` has it
|
|
1941
|
+
// (CSSOM View 6.1): the bands are where the lines put it, and a link
|
|
1942
|
+
// moved 120px down measured on its line, as wide as from where it was
|
|
1943
|
+
// laid out to where it was drawn
|
|
1944
|
+
const { x, y } = relativeOffsetOf(box);
|
|
1945
|
+
if (x || y) {
|
|
1946
|
+
for (const band of bands) {
|
|
1947
|
+
band.x += x;
|
|
1948
|
+
band.y += y;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
out.push(...bands);
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1955
|
+
/** How far an inline box and the inline boxes around it moved it by their
|
|
1956
|
+
* relative offsets (`INLINE_OFFSETS`). */
|
|
1957
|
+
function relativeOffsetOf(box: Box): { x: number; y: number } {
|
|
1958
|
+
let x = 0;
|
|
1959
|
+
let y = 0;
|
|
1960
|
+
for (let at: Box | null = box; at?.kind === 'inline'; at = at.parent) {
|
|
1961
|
+
const offset = INLINE_OFFSETS.get(at);
|
|
1962
|
+
if (offset) {
|
|
1963
|
+
x += offset.x;
|
|
1964
|
+
y += offset.y;
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
return { x, y };
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1659
1970
|
/** Where the document's text goes on after a box with none: the start of
|
|
1660
1971
|
* the next text in document order, or the end of the last before it. */
|
|
1661
1972
|
function textAfter(root: Box, target: Box): number {
|
|
@@ -1715,6 +2026,7 @@ function sameRects(a: ControlRect[], b: ControlRect[]): boolean {
|
|
|
1715
2026
|
p.y !== q.y ||
|
|
1716
2027
|
p.width !== q.width ||
|
|
1717
2028
|
p.height !== q.height ||
|
|
2029
|
+
p.opacity !== q.opacity ||
|
|
1718
2030
|
!sameBare(p.bare, q.bare)
|
|
1719
2031
|
) {
|
|
1720
2032
|
return false;
|
|
@@ -1732,7 +2044,8 @@ function sameBare(a?: BareField, b?: BareField): boolean {
|
|
|
1732
2044
|
a.height === b.height &&
|
|
1733
2045
|
a.color === b.color &&
|
|
1734
2046
|
a.fontFamily === b.fontFamily &&
|
|
1735
|
-
a.fontSize === b.fontSize
|
|
2047
|
+
a.fontSize === b.fontSize &&
|
|
2048
|
+
a.chevron === b.chevron
|
|
1736
2049
|
);
|
|
1737
2050
|
}
|
|
1738
2051
|
|
|
@@ -2004,6 +2317,9 @@ function lineBands(
|
|
|
2004
2317
|
dx: number,
|
|
2005
2318
|
dy: number,
|
|
2006
2319
|
out: Rect[],
|
|
2320
|
+
/** Where the lines put the text, not where a relative offset moved it
|
|
2321
|
+
* (`TEXT_SHIFTS`): what an inline box's own offset is added to. */
|
|
2322
|
+
laidOut = false,
|
|
2007
2323
|
): void {
|
|
2008
2324
|
// First line whose text can reach `from`, by binary search over the
|
|
2009
2325
|
// sorted text starts; stop at the first line past `to`.
|
|
@@ -2025,6 +2341,13 @@ function lineBands(
|
|
|
2025
2341
|
const b = Math.min(to, text.textEnd);
|
|
2026
2342
|
if (b <= a) continue;
|
|
2027
2343
|
const offsets = layoutOffsetsOf(text.layout);
|
|
2344
|
+
const moved = laidOut ? (TEXT_SHIFTS.get(text)?.x ?? 0) : 0;
|
|
2345
|
+
// The spaces a line ends on are removed (CSS Text 3, 4.1.2), and
|
|
2346
|
+
// take no room on it: the engine sets them past its line's content,
|
|
2347
|
+
// where a caret after them goes, and a band that took them in made
|
|
2348
|
+
// a link that ends a line a space wider than Chrome's. So the text
|
|
2349
|
+
// a line ends with is measured no further than its content.
|
|
2350
|
+
const ends = endsLine(line, text, natural);
|
|
2028
2351
|
for (const band of bandsFor(
|
|
2029
2352
|
text.layout,
|
|
2030
2353
|
natural,
|
|
@@ -2032,10 +2355,28 @@ function lineBands(
|
|
|
2032
2355
|
layoutOffsetOf(text, a),
|
|
2033
2356
|
layoutOffsetOf(text, b, true),
|
|
2034
2357
|
)) {
|
|
2358
|
+
let left = band.x;
|
|
2359
|
+
let right = band.x + band.width;
|
|
2360
|
+
if (ends) {
|
|
2361
|
+
left = Math.max(left, natural.x);
|
|
2362
|
+
right = Math.max(left, Math.min(right, natural.x + natural.width));
|
|
2363
|
+
}
|
|
2035
2364
|
out.push({
|
|
2036
|
-
x: dx +
|
|
2365
|
+
x: dx + left + text.drawX - moved,
|
|
2037
2366
|
y: dy + line.y,
|
|
2038
|
-
width:
|
|
2367
|
+
width: right - left,
|
|
2368
|
+
height: line.height,
|
|
2369
|
+
});
|
|
2370
|
+
}
|
|
2371
|
+
// the spaces `pre-wrap` keeps that the line ends on, which have no
|
|
2372
|
+
// run: the engine hung them past the line (`LineText.hung`)
|
|
2373
|
+
for (const space of text.hung ?? []) {
|
|
2374
|
+
const at = documentOffsetOf(text, space.at);
|
|
2375
|
+
if (at < a || at >= b) continue;
|
|
2376
|
+
out.push({
|
|
2377
|
+
x: dx + space.x + text.drawX - moved,
|
|
2378
|
+
y: dy + line.y,
|
|
2379
|
+
width: space.width,
|
|
2039
2380
|
height: line.height,
|
|
2040
2381
|
});
|
|
2041
2382
|
}
|
|
@@ -2044,6 +2385,21 @@ function lineBands(
|
|
|
2044
2385
|
}
|
|
2045
2386
|
}
|
|
2046
2387
|
|
|
2388
|
+
/** Whether a line's text is where the line ends: its last, with nothing
|
|
2389
|
+
* placed after it — an inline-block or an image past its content. */
|
|
2390
|
+
function endsLine(
|
|
2391
|
+
line: LineBox,
|
|
2392
|
+
text: LineText,
|
|
2393
|
+
natural: { x: number; width: number },
|
|
2394
|
+
): boolean {
|
|
2395
|
+
if (line.texts[line.texts.length - 1] !== text) return false;
|
|
2396
|
+
const right = text.drawX + natural.x + natural.width;
|
|
2397
|
+
for (const placed of line.atomics) {
|
|
2398
|
+
if (placed.x >= right - 0.01) return false;
|
|
2399
|
+
}
|
|
2400
|
+
return true;
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2047
2403
|
function collectBands(
|
|
2048
2404
|
box: Box,
|
|
2049
2405
|
from: number,
|
|
@@ -2078,6 +2434,10 @@ function deepestAt(
|
|
|
2078
2434
|
/** Told whether what was found was found under text: a run's, rather
|
|
2079
2435
|
* than a box's. */
|
|
2080
2436
|
hit?: { text: boolean },
|
|
2437
|
+
/** How far a box fixed to the viewport is drawn from where it was laid
|
|
2438
|
+
* out (`HtmlViewNode._fixedShift`): the point is taken back by it in
|
|
2439
|
+
* there, as paint moved the box by it (`atViewport`). */
|
|
2440
|
+
fixedShift: { x: number; y: number } | null = null,
|
|
2081
2441
|
): Element | null {
|
|
2082
2442
|
const box = tree.root;
|
|
2083
2443
|
let found: Element | null = box.el;
|
|
@@ -2118,10 +2478,31 @@ function deepestAt(
|
|
|
2118
2478
|
// Zen Garden's archive links are absolute `<li>`s in an `overflow:
|
|
2119
2479
|
// hidden` list with no height of its own, and not one of them could be
|
|
2120
2480
|
// hovered or pressed.
|
|
2481
|
+
let atViewport = false;
|
|
2121
2482
|
const enter = (
|
|
2122
2483
|
child: Box,
|
|
2123
2484
|
context: readonly number[],
|
|
2124
2485
|
clipped: readonly Box[],
|
|
2486
|
+
): void => {
|
|
2487
|
+
if (!fixedShift || atViewport || !fixedToViewport(child)) {
|
|
2488
|
+
enterAt(child, context, clipped);
|
|
2489
|
+
return;
|
|
2490
|
+
}
|
|
2491
|
+
x -= fixedShift.x;
|
|
2492
|
+
y -= fixedShift.y;
|
|
2493
|
+
atViewport = true;
|
|
2494
|
+
try {
|
|
2495
|
+
enterAt(child, context, clipped);
|
|
2496
|
+
} finally {
|
|
2497
|
+
x += fixedShift.x;
|
|
2498
|
+
y += fixedShift.y;
|
|
2499
|
+
atViewport = false;
|
|
2500
|
+
}
|
|
2501
|
+
};
|
|
2502
|
+
const enterAt = (
|
|
2503
|
+
child: Box,
|
|
2504
|
+
context: readonly number[],
|
|
2505
|
+
clipped: readonly Box[],
|
|
2125
2506
|
): void => {
|
|
2126
2507
|
const style = child.style;
|
|
2127
2508
|
if (
|
|
@@ -2292,16 +2673,116 @@ const FACE_FIELDS = [
|
|
|
2292
2673
|
/** Whether two styles differ in ink alone: true where they do, null where
|
|
2293
2674
|
* they do not differ, false where something else does. Custom properties
|
|
2294
2675
|
* are read through the properties that use them. */
|
|
2295
|
-
|
|
2676
|
+
/** What a restyle in place changed beyond ink: how far a box's ink
|
|
2677
|
+
* reaches, the order its layer paints in, or where it is. */
|
|
2678
|
+
interface HoverChange {
|
|
2679
|
+
reach: boolean;
|
|
2680
|
+
order: boolean;
|
|
2681
|
+
move: boolean;
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
/** The replaced boxes that are controls (`BoxTree.controls`, `controls.ts`):
|
|
2685
|
+
* a widget mounted beside the document, or a button whose press it reports. */
|
|
2686
|
+
const WIDGETS = new Set<ReplacedKind>([
|
|
2687
|
+
'input',
|
|
2688
|
+
'textarea',
|
|
2689
|
+
'select',
|
|
2690
|
+
'button',
|
|
2691
|
+
'checkbox',
|
|
2692
|
+
'radio',
|
|
2693
|
+
]);
|
|
2694
|
+
|
|
2695
|
+
/** What only moves how far a box's ink reaches: a shadow, and an outline's
|
|
2696
|
+
* size, which take no room (CSS Backgrounds 3, 7.1; CSS UI 4, 3). */
|
|
2697
|
+
const INK_REACH = new Set([
|
|
2698
|
+
'boxShadow',
|
|
2699
|
+
'outlineStyle',
|
|
2700
|
+
'outlineWidth',
|
|
2701
|
+
'outlineOffset',
|
|
2702
|
+
]);
|
|
2703
|
+
|
|
2704
|
+
/**
|
|
2705
|
+
* How two styles of one element differ, for a restyle in place: null where
|
|
2706
|
+
* they do not, a `HoverChange` where every difference is ink, how far ink
|
|
2707
|
+
* reaches, a `z-index` that stays a stacking context's, or a translation —
|
|
2708
|
+
* none of which moves anything but the box and what it holds (CSS
|
|
2709
|
+
* Transforms 1: a transform does not affect layout) — and false where
|
|
2710
|
+
* anything else differs.
|
|
2711
|
+
*/
|
|
2712
|
+
function hoverChange(
|
|
2713
|
+
was: ComputedStyle,
|
|
2714
|
+
now: ComputedStyle,
|
|
2715
|
+
): HoverChange | null | false {
|
|
2296
2716
|
const a = was as unknown as Record<string, unknown>;
|
|
2297
2717
|
const b = now as unknown as Record<string, unknown>;
|
|
2298
|
-
let
|
|
2718
|
+
let change: HoverChange | null = null;
|
|
2299
2719
|
for (const key in b) {
|
|
2300
2720
|
if (key === 'custom' || sameValue(a[key], b[key])) continue;
|
|
2301
|
-
|
|
2302
|
-
|
|
2721
|
+
change ??= { reach: false, order: false, move: false };
|
|
2722
|
+
if (PAINT_ONLY.has(key)) continue;
|
|
2723
|
+
if (INK_REACH.has(key)) {
|
|
2724
|
+
change.reach = true;
|
|
2725
|
+
continue;
|
|
2726
|
+
}
|
|
2727
|
+
if (key === 'zIndex') {
|
|
2728
|
+
// one stacking context either way, with nothing hoisted below the
|
|
2729
|
+
// flow (`hoistNegative`): only its place among its layers changes
|
|
2730
|
+
const z0 = a[key];
|
|
2731
|
+
const z1 = b[key];
|
|
2732
|
+
if (typeof z0 !== 'number' || typeof z1 !== 'number') return false;
|
|
2733
|
+
if (z0 < 0 || z1 < 0) return false;
|
|
2734
|
+
change.order = true;
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
if (key === 'transformTranslate' || key === 'translate') {
|
|
2738
|
+
change.move = true;
|
|
2739
|
+
continue;
|
|
2740
|
+
}
|
|
2741
|
+
return false;
|
|
2742
|
+
}
|
|
2743
|
+
return change;
|
|
2744
|
+
}
|
|
2745
|
+
|
|
2746
|
+
/**
|
|
2747
|
+
* Whether a box can take a new translation where it is: a box of its own
|
|
2748
|
+
* rather than an inline one, whose text is what moves; and the same
|
|
2749
|
+
* containing block and stacking context for what is in it, which a
|
|
2750
|
+
* transform makes a box (CSS Transforms 1, 2) — so either transformed
|
|
2751
|
+
* before and after, or already positioned with a `z-index`, and holding
|
|
2752
|
+
* nothing fixed, which only a transform takes in.
|
|
2753
|
+
*/
|
|
2754
|
+
function movable(box: Box, was: ComputedStyle, now: ComputedStyle): boolean {
|
|
2755
|
+
if (box.kind === 'inline') return false;
|
|
2756
|
+
if (transformed(was) === transformed(now)) return true;
|
|
2757
|
+
if (was.position === 'static') return false;
|
|
2758
|
+
if (typeof was.zIndex !== 'number' || typeof now.zIndex !== 'number') {
|
|
2759
|
+
return false;
|
|
2303
2760
|
}
|
|
2304
|
-
|
|
2761
|
+
const stack: Box[] = [...box.children];
|
|
2762
|
+
while (stack.length) {
|
|
2763
|
+
const at = stack.pop()!;
|
|
2764
|
+
if (at.style.position === 'fixed') return false;
|
|
2765
|
+
for (const child of at.children) stack.push(child);
|
|
2766
|
+
}
|
|
2767
|
+
return true;
|
|
2768
|
+
}
|
|
2769
|
+
|
|
2770
|
+
/** Where a box draws, in document coordinates: its ink bounds, or for an
|
|
2771
|
+
* inline box or a run of text, which are drawn on their block's lines and
|
|
2772
|
+
* have none of their own, the block's. */
|
|
2773
|
+
function inkOf(box: Box): Rect | null {
|
|
2774
|
+
let at: Box | null = box;
|
|
2775
|
+
while (at && (at.kind === 'text' || at.kind === 'break' || !hasRect(at))) {
|
|
2776
|
+
at = at.parent;
|
|
2777
|
+
}
|
|
2778
|
+
if (!at || at.boundsY === Infinity) return null;
|
|
2779
|
+
if (!(at.boundsWidth > 0 && at.boundsHeight > 0)) return null;
|
|
2780
|
+
return {
|
|
2781
|
+
x: at.boundsX,
|
|
2782
|
+
y: at.boundsY,
|
|
2783
|
+
width: at.boundsWidth,
|
|
2784
|
+
height: at.boundsHeight,
|
|
2785
|
+
};
|
|
2305
2786
|
}
|
|
2306
2787
|
|
|
2307
2788
|
/** Structural equality for a computed value: a number, a string, or the
|