react-x11 2.22.4 → 2.22.6
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/package.json +1 -1
- package/src/nodes/layout.js +87 -0
- package/src/nodes/node.js +46 -7
- package/src/nodes/scrollblit.js +31 -5
- package/src/nodes/window/floors.js +151 -1
- package/src/nodes/window/size.js +31 -6
- package/src/nodes/window/window.js +3 -0
package/package.json
CHANGED
package/src/nodes/layout.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
import { Yoga } from '../yoga.js';
|
|
6
6
|
import { callHandler } from '../errors.js';
|
|
7
7
|
import { DAMAGE_SLOP, layoutDiff } from './damage.js';
|
|
8
|
+
import { resolveBorderWidths } from '../styles.js';
|
|
8
9
|
import { insetRect, intersectRects, outerPixels, outside } from './rects.js';
|
|
9
10
|
import { DEV } from './util.js';
|
|
10
11
|
|
|
@@ -82,6 +83,9 @@ export function offsetInParent(node) {
|
|
|
82
83
|
};
|
|
83
84
|
}
|
|
84
85
|
|
|
86
|
+
/** What a leaf a width pass reads nothing from answers with. */
|
|
87
|
+
const UNREAD_SIZE = Object.freeze({ width: 0, height: 0 });
|
|
88
|
+
|
|
85
89
|
/** Layout, installed onto `Node.prototype` by node.js. */
|
|
86
90
|
export class NodeLayout {
|
|
87
91
|
/**
|
|
@@ -140,6 +144,10 @@ export class NodeLayout {
|
|
|
140
144
|
if (heightMode === Yoga.MEASURE_MODE_UNDEFINED) {
|
|
141
145
|
this._floorMeasureMode = MEASURE_MODES[widthMode];
|
|
142
146
|
}
|
|
147
|
+
// A leaf a pass over the whole tree reads nothing from
|
|
148
|
+
// (`markUnreadLeaves`): its width there is one yoga replaces and its
|
|
149
|
+
// height one the pass does not read, so it is not shaped to find them.
|
|
150
|
+
if (this._widthUnread) return UNREAD_SIZE;
|
|
143
151
|
const size = this.measureContent({
|
|
144
152
|
width: measureOffer(width, widthMode),
|
|
145
153
|
height: measureOffer(height, heightMode),
|
|
@@ -520,12 +528,33 @@ export class NodeLayout {
|
|
|
520
528
|
) {
|
|
521
529
|
return;
|
|
522
530
|
}
|
|
531
|
+
// …and one that rode it and only grew or shrank changed the pixels
|
|
532
|
+
// along the edges that moved — the column a virtualized document's
|
|
533
|
+
// blocks sit in, whose spacers re-slice in the scroll's own frame
|
|
534
|
+
const bands =
|
|
535
|
+
had && was.x === x && was.y === y && width > 0 && height > 0
|
|
536
|
+
? this._edgeBands(was, grow)
|
|
537
|
+
: null;
|
|
538
|
+
if (bands) {
|
|
539
|
+
for (const band of bands) layoutDiff.sink(band, this);
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
523
542
|
if (had) layoutDiff.sink(insetRect(was, -grow), this);
|
|
524
543
|
if (width > 0 && height > 0) {
|
|
525
544
|
layoutDiff.sink(insetRect(this.abs, -grow), this);
|
|
526
545
|
}
|
|
527
546
|
return;
|
|
528
547
|
}
|
|
548
|
+
// A box that stayed where it was and only grew or shrank changed the
|
|
549
|
+
// pixels along the edges that moved, and no others (`_edgeBands`)
|
|
550
|
+
const bands =
|
|
551
|
+
had && old.x === x && old.y === y && width > 0 && height > 0
|
|
552
|
+
? this._edgeBands(old, grow)
|
|
553
|
+
: null;
|
|
554
|
+
if (bands) {
|
|
555
|
+
for (const band of bands) layoutDiff.sink(band, this);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
529
558
|
if (had) {
|
|
530
559
|
layoutDiff.sink(insetRect(old, -grow), this);
|
|
531
560
|
}
|
|
@@ -535,6 +564,64 @@ export class NodeLayout {
|
|
|
535
564
|
}
|
|
536
565
|
}
|
|
537
566
|
|
|
567
|
+
/**
|
|
568
|
+
* The pixels a resize changed, for a box whose origin held: a band along
|
|
569
|
+
* each edge that moved, from where it was to where it is, as deep as the
|
|
570
|
+
* box's paint reaches in from that edge — a rounded corner, a border, a
|
|
571
|
+
* shadow's or an outline's reach. Its own paint anywhere else is the same
|
|
572
|
+
* solid fill it was, and its children claim for themselves when they move.
|
|
573
|
+
*
|
|
574
|
+
* A tall box that grows at its far end is the case: a document or a log
|
|
575
|
+
* or a virtualized list whose column gained a block far below the fold
|
|
576
|
+
* claimed the column, clipped to its scroll box — the whole viewport — on
|
|
577
|
+
* every frame it grew, for pixels that were all out of sight.
|
|
578
|
+
*
|
|
579
|
+
* Null where the whole box has to be claimed: anything that is not a
|
|
580
|
+
* plain box, one that scrolls (its bars are sized by it), one whose
|
|
581
|
+
* gradient is stretched over it, and one drawn through a group surface.
|
|
582
|
+
*/
|
|
583
|
+
_edgeBands(old, grow) {
|
|
584
|
+
if (this.kind !== 'box' || this.isScroller?.()) return null;
|
|
585
|
+
const style = this.style;
|
|
586
|
+
if (style.backgroundImage) return null;
|
|
587
|
+
if (style.opacity !== undefined && style.opacity < 1) return null;
|
|
588
|
+
const bw = resolveBorderWidths(style, this.direction);
|
|
589
|
+
const reach =
|
|
590
|
+
Math.max(
|
|
591
|
+
style.borderRadius ?? 0,
|
|
592
|
+
bw.top,
|
|
593
|
+
bw.right,
|
|
594
|
+
bw.bottom,
|
|
595
|
+
bw.left,
|
|
596
|
+
grow,
|
|
597
|
+
) + DAMAGE_SLOP;
|
|
598
|
+
const now = this.abs;
|
|
599
|
+
const bands = [];
|
|
600
|
+
const oldBottom = old.y + old.height;
|
|
601
|
+
const newBottom = now.y + now.height;
|
|
602
|
+
if (oldBottom !== newBottom) {
|
|
603
|
+
const top = Math.min(oldBottom, newBottom) - reach;
|
|
604
|
+
bands.push({
|
|
605
|
+
x: now.x - reach,
|
|
606
|
+
y: top,
|
|
607
|
+
width: Math.max(old.width, now.width) + 2 * reach,
|
|
608
|
+
height: Math.max(oldBottom, newBottom) + reach - top,
|
|
609
|
+
});
|
|
610
|
+
}
|
|
611
|
+
const oldRight = old.x + old.width;
|
|
612
|
+
const newRight = now.x + now.width;
|
|
613
|
+
if (oldRight !== newRight) {
|
|
614
|
+
const left = Math.min(oldRight, newRight) - reach;
|
|
615
|
+
bands.push({
|
|
616
|
+
x: left,
|
|
617
|
+
y: now.y - reach,
|
|
618
|
+
width: Math.max(oldRight, newRight) + reach - left,
|
|
619
|
+
height: Math.max(old.height, now.height) + 2 * reach,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
return bands;
|
|
623
|
+
}
|
|
624
|
+
|
|
538
625
|
/**
|
|
539
626
|
* Move an already-laid-out subtree by a constant, without asking yoga
|
|
540
627
|
* anything — the scroll fast path's walk (issue #405).
|
package/src/nodes/node.js
CHANGED
|
@@ -111,6 +111,9 @@ export class Node {
|
|
|
111
111
|
// the width mode yoga last measured this leaf in with no height on
|
|
112
112
|
// offer — the question `_heightForWidth` repeats
|
|
113
113
|
this._floorMeasureMode = null;
|
|
114
|
+
// set only for the length of a width pass that reads nothing from this
|
|
115
|
+
// leaf (`markUnreadLeaves`)
|
|
116
|
+
this._widthUnread = false;
|
|
114
117
|
this.root = null; // owning WindowNode once attached
|
|
115
118
|
this.hidden = false;
|
|
116
119
|
// Composited on a layer of its own above the window's bitmap, by a
|
|
@@ -384,8 +387,10 @@ export class Node {
|
|
|
384
387
|
// captured before the child joins, so it covers the arrangement that is
|
|
385
388
|
// about to be replaced (see _childListChanged). A viewport mid-blit has
|
|
386
389
|
// nothing vacating — the child being added had no pixels — and the
|
|
387
|
-
// layout diff claims where it lands, so it names no region at all.
|
|
388
|
-
|
|
390
|
+
// layout diff claims where it lands, so it names no region at all. A
|
|
391
|
+
// child already here is a move, and says so (`_childListFine`).
|
|
392
|
+
const fine = this._childListFine(child.parent !== this);
|
|
393
|
+
const before = fine ? null : this._childListBefore();
|
|
389
394
|
// a move has to leave the yoga tree too — yoga aborts on insertChild of
|
|
390
395
|
// a node that still has a parent
|
|
391
396
|
if (child.parent === this && this._joinsYoga(child)) {
|
|
@@ -414,7 +419,7 @@ export class Node {
|
|
|
414
419
|
// against the app's, and only now can see the `scale` props above it.
|
|
415
420
|
child._rescaleSubtree(mounting);
|
|
416
421
|
this._textContentChanged();
|
|
417
|
-
this._childListChanged(before);
|
|
422
|
+
this._childListChanged(before, fine);
|
|
418
423
|
a11yHooks.attached?.(this, child);
|
|
419
424
|
}
|
|
420
425
|
|
|
@@ -464,7 +469,7 @@ export class Node {
|
|
|
464
469
|
* through the layout diff in flush(), which is what lets this claim stay
|
|
465
470
|
* bounded without requiring the node's own size to be pinned.
|
|
466
471
|
*/
|
|
467
|
-
_childListChanged(before) {
|
|
472
|
+
_childListChanged(before, fine = this._childListFine(true)) {
|
|
468
473
|
// belt for a subtree attached imperatively with its rect already laid
|
|
469
474
|
// out — nothing then re-runs _assignAbs to notice the reach grew
|
|
470
475
|
this._clearHitBounds();
|
|
@@ -477,7 +482,7 @@ export class Node {
|
|
|
477
482
|
// and says nothing about the ones that only rode the scroll. Joining
|
|
478
483
|
// `_reflowed` would undo both — its post-layout claim is this node's
|
|
479
484
|
// box, the whole band the blit is about to move.
|
|
480
|
-
if (
|
|
485
|
+
if (fine) {
|
|
481
486
|
root.invalidate(true, before ?? NO_DAMAGE, 'child-list', this);
|
|
482
487
|
return;
|
|
483
488
|
}
|
|
@@ -485,6 +490,39 @@ export class Node {
|
|
|
485
490
|
root._reflowed.add(this);
|
|
486
491
|
}
|
|
487
492
|
|
|
493
|
+
/**
|
|
494
|
+
* Whether a child-list change here is claimed the fine way — the child
|
|
495
|
+
* that leaves claims its own rect, and the layout diff names where an
|
|
496
|
+
* entering child lands and whatever its arrival displaced — rather than
|
|
497
|
+
* as this node's box before and after the pass.
|
|
498
|
+
*
|
|
499
|
+
* Always for a viewport keeping a ledger this frame (issue #398). And,
|
|
500
|
+
* for a child that enters or leaves (`entersOrLeaves`), for any node
|
|
501
|
+
* inside a scroll box, scrolling or not: a document's blocks, the rows
|
|
502
|
+
* of a virtualized list one box down from its scroll box, a log that
|
|
503
|
+
* grows. Such a node is usually taller than the view, and its box
|
|
504
|
+
* clipped to the viewport is the whole viewport — so every re-slice of
|
|
505
|
+
* a list inside a column, all of it below the fold, repainted the view,
|
|
506
|
+
* and one landing in a scroll's frame poisoned its blit. The diff runs
|
|
507
|
+
* over every node that pass laid out, however deep, and a node that
|
|
508
|
+
* only grew claims the edge that moved (`_edgeBands`).
|
|
509
|
+
*
|
|
510
|
+
* Not for a move: a child moved within the list keeps its rect when
|
|
511
|
+
* what changed is only which sibling it is painted over, and no rect
|
|
512
|
+
* says that. Nor for a scroll box's own children outside a ledger frame:
|
|
513
|
+
* its box is also the claim that repaints its bars, which its content
|
|
514
|
+
* sizes.
|
|
515
|
+
*/
|
|
516
|
+
_childListFine(entersOrLeaves) {
|
|
517
|
+
if (this._blitLedgerOpen()) return true;
|
|
518
|
+
if (!entersOrLeaves) return false;
|
|
519
|
+
for (let n = this.parent; n; n = n.parent) {
|
|
520
|
+
if (n.isWindow) return false;
|
|
521
|
+
if (n.isScroller?.()) return true;
|
|
522
|
+
}
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
|
|
488
526
|
removeChild(child) {
|
|
489
527
|
const index = this._indexOfChild(child);
|
|
490
528
|
if (index === -1) return;
|
|
@@ -494,7 +532,8 @@ export class Node {
|
|
|
494
532
|
// captured while the child is still attached, so it covers the rect the
|
|
495
533
|
// child is about to stop occupying — the child's own, for a viewport
|
|
496
534
|
// mid-blit, where this node's box is the whole scrolled band
|
|
497
|
-
const
|
|
535
|
+
const fine = this._childListFine(true);
|
|
536
|
+
const before = fine
|
|
498
537
|
? (child._claimBounds() ?? NO_DAMAGE)
|
|
499
538
|
: this._childListBefore();
|
|
500
539
|
this.children.splice(index, 1);
|
|
@@ -515,7 +554,7 @@ export class Node {
|
|
|
515
554
|
child.yoga = null;
|
|
516
555
|
}
|
|
517
556
|
this._textContentChanged();
|
|
518
|
-
this._childListChanged(before);
|
|
557
|
+
this._childListChanged(before, fine);
|
|
519
558
|
}
|
|
520
559
|
|
|
521
560
|
/** Destroy real resources (X windows) in this subtree. Yoga nodes are
|
package/src/nodes/scrollblit.js
CHANGED
|
@@ -855,7 +855,15 @@ export class WindowScrollBlit {
|
|
|
855
855
|
? unionArea([strip, ...repairs]) - rectArea(strip)
|
|
856
856
|
: 0;
|
|
857
857
|
if (repairArea > area * BLIT_MAX_CLAIM_AREA) return;
|
|
858
|
-
|
|
858
|
+
// An ancestor's rounded corners reach into the viewport: a scroll box
|
|
859
|
+
// filling a rounded card, the commonest place a scroll box is. Their
|
|
860
|
+
// arcs do not translate, but they live in four small squares and
|
|
861
|
+
// nowhere else, so they are pinned the way an element blit pins them
|
|
862
|
+
// (issue #691) and repaired below, where they are and where the copy
|
|
863
|
+
// dragged their image. Refused instead, a list or a document inside a
|
|
864
|
+
// rounded frame repainted its whole viewport on every step of a scroll.
|
|
865
|
+
const corners = this._cornerPins(node, vp, target.top);
|
|
866
|
+
if (!this._scrollBlitSafe(node, vp, target.top, null, corners)) return;
|
|
859
867
|
// The band the scrolled bar's thumb travels in is repaired on its own.
|
|
860
868
|
// The thumb's rects are thin and run along the viewport's edge, and a
|
|
861
869
|
// rect reaching into the band from inside — a column held down the
|
|
@@ -932,10 +940,28 @@ export class WindowScrollBlit {
|
|
|
932
940
|
if (dragged) add(dragged);
|
|
933
941
|
}
|
|
934
942
|
for (const repair of repairs) add(repair);
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
)
|
|
943
|
+
// Each pinned corner where it is and where the copy put its image — one
|
|
944
|
+
// box, since a step moves it a few pixels, and less the strip, which
|
|
945
|
+
// repaints anyway.
|
|
946
|
+
for (const pin of corners ?? []) {
|
|
947
|
+
const image = { ...pin, x: pin.x + dx, y: pin.y + dy };
|
|
948
|
+
const repair = intersectRects(unionRect(pin, image), vp);
|
|
949
|
+
if (!repair) continue;
|
|
950
|
+
for (const piece of outside(repair, strip)) add(piece);
|
|
951
|
+
}
|
|
952
|
+
// Capped in one call where corners were pinned: two at each end of the
|
|
953
|
+
// viewport and the strip at one of them are more pieces than the cap
|
|
954
|
+
// holds, and squeezed under it one at a time a corner joined whatever
|
|
955
|
+
// box an earlier merge had grown — scrolling back up merged the bottom
|
|
956
|
+
// corners with the strip at the top, which is the viewport. Offered
|
|
957
|
+
// together, neighbours merge first (`addDamageRects`), with the room an
|
|
958
|
+
// element blit's pins get.
|
|
959
|
+
rects = corners
|
|
960
|
+
? addDamageRects([], joinAlongEdges(rects), CONTENTS_BLIT_MAX_RECTS)
|
|
961
|
+
: joinAlongEdges(rects).reduce(
|
|
962
|
+
(capped, rect) => addDamageRect(capped, rect),
|
|
963
|
+
[],
|
|
964
|
+
);
|
|
939
965
|
// The last gate, and the only one that has to wait until the rects are
|
|
940
966
|
// assembled: damage rects must not overlap, and the frame carries at
|
|
941
967
|
// most MAX_DAMAGE_RECTS of them, so repairs that meet — a column and a
|
|
@@ -445,6 +445,142 @@ export function setMeasuringShrink(node, axis, out) {
|
|
|
445
445
|
}
|
|
446
446
|
}
|
|
447
447
|
|
|
448
|
+
/**
|
|
449
|
+
* Mark the leaves whose min-content width the pass over the whole tree has
|
|
450
|
+
* no use for (`_widthUnread`), listing them in `out`: their measure
|
|
451
|
+
* function answers without shaping them (`NodeLayout._useMeasureContent`)
|
|
452
|
+
* until `_measureContentSpans` takes the marks off.
|
|
453
|
+
*
|
|
454
|
+
* The pass lays every box out with no room on offer, and for a document that
|
|
455
|
+
* is every paragraph set a word to a line — 1 s of CoreText for 600 KB of
|
|
456
|
+
* Markdown, and more than half of the first frame — to read extents that
|
|
457
|
+
* almost nobody reads. An extent is read where a floor is written from it,
|
|
458
|
+
* which is a flex item on a `row`'s main axis (`receivesFloor`); by the
|
|
459
|
+
* extent of a node that is read, which adds up the ones of its children in
|
|
460
|
+
* flow (`contentSpan`); and at the root, when the window's own size hint is
|
|
461
|
+
* what is being measured. Down a column from a root nobody reads, none of
|
|
462
|
+
* that happens: a paragraph in a document is laid out, and nothing reads
|
|
463
|
+
* what it says.
|
|
464
|
+
*
|
|
465
|
+
* It is still laid out, though, and a leaf's answer could reach something
|
|
466
|
+
* that is read through its size. So it is marked only where it cannot:
|
|
467
|
+
*
|
|
468
|
+
* - every box from the root down to it is as wide as something other than
|
|
469
|
+
* its content — the root offered no room, a width it names, a column
|
|
470
|
+
* stretching it — so the width the leaf answers is one yoga replaces, and
|
|
471
|
+
* nothing above it is sized from it;
|
|
472
|
+
* - no box is handed a share of space the leaf's height changed, which is
|
|
473
|
+
* how a height could become somebody's width — an aspect ratio, a column
|
|
474
|
+
* that wraps, an image sized to the height it is given. Every column from
|
|
475
|
+
* the root down to it is either as tall as its content, and then holds no
|
|
476
|
+
* box out of flow whose height it sets, or has a height of its own, and
|
|
477
|
+
* then holds nothing that grows or gives way in the pass;
|
|
478
|
+
* - none of those columns wraps. (One may keep an aspect ratio: a box as
|
|
479
|
+
* wide as something other than its content is as tall as that makes it.)
|
|
480
|
+
*
|
|
481
|
+
* What is left of its answer is a height, which a pass measuring widths
|
|
482
|
+
* never reads, and the layouts after the pass run in another config version
|
|
483
|
+
* (`measuringExactly`), which yoga answers by measuring every leaf again.
|
|
484
|
+
*/
|
|
485
|
+
export function markUnreadLeaves(node, out) {
|
|
486
|
+
const yoga = node.yoga;
|
|
487
|
+
const direction = yoga.getFlexDirection();
|
|
488
|
+
if (
|
|
489
|
+
(direction !== Yoga.FLEX_DIRECTION_COLUMN &&
|
|
490
|
+
direction !== Yoga.FLEX_DIRECTION_COLUMN_REVERSE) ||
|
|
491
|
+
yoga.getFlexWrap() !== Yoga.WRAP_NO_WRAP
|
|
492
|
+
) {
|
|
493
|
+
// a row's items are read, and a column that wraps is as wide as its lines
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
const height = heightOf(yoga);
|
|
497
|
+
if (height === 'unknown') return;
|
|
498
|
+
for (const child of node.children) {
|
|
499
|
+
if (!child.yoga || child.isWindow || child.style.display === 'none') {
|
|
500
|
+
continue;
|
|
501
|
+
}
|
|
502
|
+
if (child.style.position === 'absolute') {
|
|
503
|
+
// a height that follows this column's follows what is in it
|
|
504
|
+
if (height === 'content' && heightFollowsParent(child.yoga)) return;
|
|
505
|
+
} else if (
|
|
506
|
+
height === 'own' &&
|
|
507
|
+
// what the pass lets grow or give way (`setMeasuringShrink`)
|
|
508
|
+
(child.yoga.getFlexGrow() > 0 || namesOwnFloor(child, 'width'))
|
|
509
|
+
) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
const items = yoga.getAlignItems();
|
|
514
|
+
for (const child of node.children) {
|
|
515
|
+
if (!inFlow(child) || child._host !== null) continue;
|
|
516
|
+
if (!widthNotItsOwn(child.yoga, items)) continue;
|
|
517
|
+
if (child._measureFn) {
|
|
518
|
+
child._widthUnread = true;
|
|
519
|
+
out.push(child);
|
|
520
|
+
} else if (child.children.length !== 0) {
|
|
521
|
+
markUnreadLeaves(child, out);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
/** The edges a horizontal margin can be set through. */
|
|
527
|
+
const ACROSS_EDGES = [
|
|
528
|
+
Yoga.EDGE_LEFT,
|
|
529
|
+
Yoga.EDGE_RIGHT,
|
|
530
|
+
Yoga.EDGE_START,
|
|
531
|
+
Yoga.EDGE_END,
|
|
532
|
+
Yoga.EDGE_HORIZONTAL,
|
|
533
|
+
Yoga.EDGE_ALL,
|
|
534
|
+
];
|
|
535
|
+
|
|
536
|
+
/** Whether an in-flow child of a column is as wide as something other than
|
|
537
|
+
* its content: a width it names, or the column's, which it stretches to
|
|
538
|
+
* unless an alignment or an `auto` margin says otherwise. */
|
|
539
|
+
function widthNotItsOwn(yoga, items) {
|
|
540
|
+
const width = yoga.getWidth().unit;
|
|
541
|
+
if (width === Yoga.UNIT_POINT || width === Yoga.UNIT_PERCENT) return true;
|
|
542
|
+
const self = yoga.getAlignSelf();
|
|
543
|
+
if ((self === Yoga.ALIGN_AUTO ? items : self) !== Yoga.ALIGN_STRETCH) {
|
|
544
|
+
return false;
|
|
545
|
+
}
|
|
546
|
+
for (const edge of ACROSS_EDGES) {
|
|
547
|
+
if (yoga.getMargin(edge).unit === Yoga.UNIT_AUTO) return false;
|
|
548
|
+
}
|
|
549
|
+
return true;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
/**
|
|
553
|
+
* Where a column's height comes from, down a pass with none on offer: its
|
|
554
|
+
* `'content'`, when it names nothing that bounds it; its `'own'`, when all it
|
|
555
|
+
* names are numbers — a height, a basis (a height, down a column), a floor or
|
|
556
|
+
* a ceiling; and `'unknown'` for a share of something (a percentage), which
|
|
557
|
+
* a pass with no height on offer resolves as it likes.
|
|
558
|
+
*/
|
|
559
|
+
function heightOf(yoga) {
|
|
560
|
+
let own = false;
|
|
561
|
+
for (const value of [
|
|
562
|
+
yoga.getHeight(),
|
|
563
|
+
yoga.getFlexBasis(),
|
|
564
|
+
yoga.getMinHeight(),
|
|
565
|
+
yoga.getMaxHeight(),
|
|
566
|
+
]) {
|
|
567
|
+
if (value.unit === Yoga.UNIT_PERCENT) return 'unknown';
|
|
568
|
+
if (value.unit === Yoga.UNIT_POINT) own = true;
|
|
569
|
+
}
|
|
570
|
+
return own ? 'own' : 'content';
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
/** Whether a box out of flow takes its height from its parent's: pinned to
|
|
574
|
+
* both edges, or a share of it. */
|
|
575
|
+
function heightFollowsParent(yoga) {
|
|
576
|
+
const pinned = (edge) => yoga.getPosition(edge).unit !== Yoga.UNIT_UNDEFINED;
|
|
577
|
+
const both = pinned(Yoga.EDGE_VERTICAL) || pinned(Yoga.EDGE_ALL);
|
|
578
|
+
if (both || (pinned(Yoga.EDGE_TOP) && pinned(Yoga.EDGE_BOTTOM))) return true;
|
|
579
|
+
return [yoga.getHeight(), yoga.getMinHeight(), yoga.getMaxHeight()].some(
|
|
580
|
+
(value) => value.unit === Yoga.UNIT_PERCENT,
|
|
581
|
+
);
|
|
582
|
+
}
|
|
583
|
+
|
|
448
584
|
/** …and back to the layout everything else is run from. */
|
|
449
585
|
export function restoreShrink(shrunk) {
|
|
450
586
|
for (const child of shrunk) {
|
|
@@ -540,8 +676,15 @@ export function restoreWidths(frozen) {
|
|
|
540
676
|
* one's: a scroll pane contributes nothing to the floor above it and still
|
|
541
677
|
* needs floors written *inside* it, or the column of rows it holds would
|
|
542
678
|
* shrink to the viewport and there would be nothing left to scroll.
|
|
679
|
+
*
|
|
680
|
+
* `read` false is a node whose own extent nobody reads (`markUnreadLeaves`
|
|
681
|
+
* says which those are), so neither are those of its children in flow: the
|
|
682
|
+
* walk goes on through them to the rows inside, whose items' floors are
|
|
683
|
+
* written, and measures nothing on the way. An extent left unmeasured is
|
|
684
|
+
* asked for when it starts to count — its parent turned into a row, which
|
|
685
|
+
* leaves the parent dirty (`collectFloorStale`).
|
|
543
686
|
*/
|
|
544
|
-
export function contentSpan(node, axis, intrinsic, root) {
|
|
687
|
+
export function contentSpan(node, axis, intrinsic, root, read = true) {
|
|
545
688
|
const yoga = node.yoga;
|
|
546
689
|
const horizontal = axis === 'width';
|
|
547
690
|
const own = horizontal ? yoga.getComputedWidth() : yoga.getComputedHeight();
|
|
@@ -564,6 +707,11 @@ export function contentSpan(node, axis, intrinsic, root) {
|
|
|
564
707
|
// by itself, and a <text> span has no box of its own
|
|
565
708
|
if (!child.yoga || child.isWindow) continue;
|
|
566
709
|
if (child.style.display === 'none') continue;
|
|
710
|
+
if (!read && !receivesFloor(child, axis)) {
|
|
711
|
+
if (child._host === null)
|
|
712
|
+
contentSpan(child, axis, intrinsic, root, false);
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
567
715
|
const laidOut = horizontal
|
|
568
716
|
? child.yoga.getComputedWidth()
|
|
569
717
|
: child.yoga.getComputedHeight();
|
|
@@ -600,6 +748,8 @@ export function contentSpan(node, axis, intrinsic, root) {
|
|
|
600
748
|
start = Math.min(start, at);
|
|
601
749
|
end = Math.max(end, at + extent);
|
|
602
750
|
}
|
|
751
|
+
// a span nobody reads, and — for a leaf — a measurement nobody needs
|
|
752
|
+
if (!read) return 0;
|
|
603
753
|
if (start === Infinity) {
|
|
604
754
|
// A leaf: nothing inside to look at, and what the pass did to it may
|
|
605
755
|
// have been a stretch rather than a measurement. So it is asked again.
|
package/src/nodes/window/size.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
captureLeafHeights,
|
|
10
10
|
collectFloorStale,
|
|
11
11
|
columnHeightSpan,
|
|
12
|
+
markUnreadLeaves,
|
|
12
13
|
declaresOwnMinimum,
|
|
13
14
|
dirtyOutside,
|
|
14
15
|
floorBoundaryOf,
|
|
@@ -41,6 +42,10 @@ import {
|
|
|
41
42
|
// REACT_X11_NO_SCROLL_BLIT gives.
|
|
42
43
|
const NO_SCOPED_FLOORS = process.env.REACT_X11_NO_SCOPED_FLOORS === '1';
|
|
43
44
|
|
|
45
|
+
// …and for the width pass reading only the extents a floor is written from
|
|
46
|
+
// (`markUnreadLeaves`): with it set, every leaf is shaped at no width again.
|
|
47
|
+
const NO_UNREAD_WIDTHS = process.env.REACT_X11_NO_UNREAD_WIDTHS === '1';
|
|
48
|
+
|
|
44
49
|
/** Take a node's extents off, as `collectFloorStale` does a dirty child's:
|
|
45
50
|
* both are measured again, and the minimum yoga holds may no longer be
|
|
46
51
|
* the one the floors wrote. */
|
|
@@ -64,8 +69,13 @@ export class WindowSize {
|
|
|
64
69
|
* they are when nothing has looked yet this frame, and a leaf the probe
|
|
65
70
|
* finds moved has its floor taken off and the pass run again, since a
|
|
66
71
|
* floor still on a node being measured would be read back as content.
|
|
72
|
+
*
|
|
73
|
+
* `wantRoot` is whether the root's own width is wanted — a window whose
|
|
74
|
+
* minimum is its content's (`_measureMinimum`). Otherwise the width pass
|
|
75
|
+
* reads only the extents a floor is written from, and the leaves nothing
|
|
76
|
+
* reads are not shaped (`markUnreadLeaves`).
|
|
67
77
|
*/
|
|
68
|
-
_measureContentSpans(axis, forWidth, probe = false) {
|
|
78
|
+
_measureContentSpans(axis, forWidth, probe = false, wantRoot = false) {
|
|
69
79
|
this._sweepLayoutHosts();
|
|
70
80
|
const yoga = this.yoga;
|
|
71
81
|
const dir = this._rootDirection;
|
|
@@ -79,9 +89,24 @@ export class WindowSize {
|
|
|
79
89
|
if (this._layoutHosts.size !== 0) this._measureHostChildWidths();
|
|
80
90
|
const shrunk = [];
|
|
81
91
|
setMeasuringShrink(this, axis, shrunk);
|
|
92
|
+
// A layout host reads its own extent and its children's, and a root
|
|
93
|
+
// with a ceiling is as wide as its content up to it: both read what
|
|
94
|
+
// nothing else here does, so both are measured whole.
|
|
95
|
+
const read =
|
|
96
|
+
wantRoot ||
|
|
97
|
+
NO_UNREAD_WIDTHS ||
|
|
98
|
+
this._readEveryWidth ||
|
|
99
|
+
this._layoutHosts.size !== 0 ||
|
|
100
|
+
yoga.getMaxWidth().unit !== Yoga.UNIT_UNDEFINED;
|
|
101
|
+
const unread = [];
|
|
102
|
+
if (!read) markUnreadLeaves(this, unread);
|
|
82
103
|
this._layoutPasses += 1;
|
|
83
|
-
|
|
84
|
-
|
|
104
|
+
try {
|
|
105
|
+
yoga.calculateLayout(0, undefined, dir);
|
|
106
|
+
} finally {
|
|
107
|
+
for (const leaf of unread) leaf._widthUnread = false;
|
|
108
|
+
}
|
|
109
|
+
const span = contentSpan(this, axis, null, this, read);
|
|
85
110
|
restoreShrink(shrunk);
|
|
86
111
|
return span;
|
|
87
112
|
}
|
|
@@ -429,9 +454,9 @@ export class WindowSize {
|
|
|
429
454
|
* (a stale extent writes the style's own minimum), which is what keeps a
|
|
430
455
|
* floor from ratcheting: read back as content, it could only ever grow.
|
|
431
456
|
*/
|
|
432
|
-
_measureWidthFloors() {
|
|
457
|
+
_measureWidthFloors(wantRoot = false) {
|
|
433
458
|
this._writeFloors('width');
|
|
434
|
-
const span = this._measureContentSpans('width');
|
|
459
|
+
const span = this._measureContentSpans('width', undefined, false, wantRoot);
|
|
435
460
|
this._writeFloors('width');
|
|
436
461
|
return span;
|
|
437
462
|
}
|
|
@@ -452,7 +477,7 @@ export class WindowSize {
|
|
|
452
477
|
return measuringExactly(() =>
|
|
453
478
|
Math.ceil(
|
|
454
479
|
axis === 'width'
|
|
455
|
-
? this._measureWidthFloors()
|
|
480
|
+
? this._measureWidthFloors(true)
|
|
456
481
|
: this._measureHeightFloors(forWidth, true),
|
|
457
482
|
),
|
|
458
483
|
);
|
|
@@ -282,6 +282,9 @@ export class WindowNode extends Scrollable(Node) {
|
|
|
282
282
|
// (spine.js), and the spines this frame's measurement runs on
|
|
283
283
|
this._floorsOtherSources = new Set();
|
|
284
284
|
this._floorsUnscoped = false;
|
|
285
|
+
// every width extent measured, read or not (`REACT_X11_NO_UNREAD_WIDTHS`,
|
|
286
|
+
// per window): what a test holds the width pass that skips them to
|
|
287
|
+
this._readEveryWidth = false;
|
|
285
288
|
this._floorsScopeNow = null;
|
|
286
289
|
this._floorsSpineNow = null;
|
|
287
290
|
// whether the next layout walk must visit every box (`invalidate`)
|