react-x11 2.11.0 → 2.13.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/README.md +278 -129
- package/package.json +12 -4
- package/src/Reconciler.js +19 -31
- package/src/a11y.js +2 -2
- package/src/anchor.js +7 -5
- package/src/appcontext.js +59 -30
- package/src/bootstrap.js +14 -0
- package/src/clientmessage.js +1 -1
- package/src/cocoa/app.js +303 -49
- package/src/cocoa/bezels.js +175 -30
- package/src/cocoa/dnd.js +27 -13
- package/src/cocoa/fonts.js +3 -3
- package/src/cocoa/glarea.js +24 -5
- package/src/cocoa/main.d.ts +8 -0
- package/src/cocoa/main.js +43 -0
- package/src/cocoa/overlay.js +159 -0
- package/src/cocoa/panehost.js +15 -5
- package/src/cocoa/presenter.js +13 -9
- package/src/cocoa/promotion.js +17 -7
- package/src/cocoa/relaunch.js +207 -0
- package/src/cocoa/threaded.js +246 -0
- package/src/cocoa/window.js +256 -42
- package/src/components/Select.js +2 -2
- package/src/components/anchor.js +3 -3
- package/src/components/native.js +12 -7
- package/src/components/theme.js +2 -2
- package/src/debug.js +1 -1
- package/src/decorations.js +1 -1
- package/src/editmenu.js +2 -2
- package/src/embedding.js +31 -0
- package/src/errors.js +46 -0
- package/src/events.js +78 -18
- package/src/foreignnodes.js +59 -5
- package/src/frames.js +2 -2
- package/src/glnodes.js +172 -41
- package/src/gloverlay.js +383 -0
- package/src/grid.js +1653 -0
- package/src/host.d.ts +230 -1
- package/src/host.js +11 -3
- package/src/imagesource.js +1 -1
- package/src/index.d.ts +34 -4
- package/src/index.js +9 -1
- package/src/layouts.js +721 -0
- package/src/node.d.ts +16 -3
- package/src/node.js +19 -21
- package/src/nodes/animation.js +644 -0
- package/src/nodes/box.js +21 -0
- package/src/nodes/boxpaint.js +473 -0
- package/src/nodes/canvas.js +269 -0
- package/src/nodes/cascade.js +600 -0
- package/src/nodes/damage.js +183 -0
- package/src/nodes/edithistory.js +124 -0
- package/src/nodes/editmenupopup.js +260 -0
- package/src/nodes/hittest.js +185 -0
- package/src/nodes/image.js +266 -0
- package/src/nodes/install.js +75 -0
- package/src/nodes/invalidate.js +465 -0
- package/src/nodes/kinds.js +31 -0
- package/src/nodes/layout.js +439 -0
- package/src/nodes/layouthost.js +949 -0
- package/src/nodes/node.js +868 -0
- package/src/nodes/paint.js +466 -0
- package/src/nodes/position.js +366 -0
- package/src/nodes/preedit.js +127 -0
- package/src/nodes/queries.js +330 -0
- package/src/nodes/rects.js +102 -0
- package/src/nodes/scrollable.js +891 -0
- package/src/nodes/scrollbars.js +138 -0
- package/src/nodes/scrollblit.js +1034 -0
- package/src/nodes/selectable.js +142 -0
- package/src/nodes/styling.js +225 -0
- package/src/nodes/text.js +649 -0
- package/src/nodes/textarea.js +391 -0
- package/src/nodes/textinput.js +1146 -0
- package/src/nodes/util.js +17 -0
- package/src/nodes/window/anchoring.js +161 -0
- package/src/nodes/window/capabilities.js +190 -0
- package/src/nodes/window/debugpaint.js +83 -0
- package/src/nodes/window/droptarget.js +145 -0
- package/src/nodes/window/floors.js +577 -0
- package/src/nodes/window/flush.js +369 -0
- package/src/nodes/window/hints.js +482 -0
- package/src/nodes/window/listeners.js +222 -0
- package/src/nodes/window/popup.js +71 -0
- package/src/nodes/window/size.js +591 -0
- package/src/nodes/window/window.js +954 -0
- package/src/palette.js +1 -1
- package/src/registry.js +7 -3
- package/src/styles.js +137 -15
- package/src/svgnodes.js +2 -1
- package/src/testing/harness.js +2 -2
- package/src/textselection.js +5 -3
- package/src/trace-registry.js +1 -1
- package/src/types/components.d.ts +38 -6
- package/src/types/elements.d.ts +26 -14
- package/src/types/nodes.d.ts +17 -2
- package/src/types/style.d.ts +94 -3
- package/src/windowstate.js +1 -1
- package/src/yoga.js +1 -1
- package/src/nodes.js +0 -13120
|
@@ -0,0 +1,577 @@
|
|
|
1
|
+
// Content floors (#249, #445): the smallest size each box can take without
|
|
2
|
+
// clipping its content, measured bottom-up and written into yoga as
|
|
3
|
+
// minimums. Free functions over nodes; WindowNode drives them from size.js,
|
|
4
|
+
// and a layout host from layouthost.js.
|
|
5
|
+
|
|
6
|
+
import { Yoga } from '../../yoga.js';
|
|
7
|
+
import { NO_CHILDREN } from '../util.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Record how tall every leaf in this subtree currently is, keyed by node.
|
|
11
|
+
*
|
|
12
|
+
* A leaf's height at a given width is not something it can give: a
|
|
13
|
+
* paragraph wrapped to 300px is as tall as it is. But `align-items` defaults
|
|
14
|
+
* to `stretch`, so a leaf inside a `row` takes the row's height — and in a
|
|
15
|
+
* pass run with no height on offer the row came out at nothing, taking its
|
|
16
|
+
* leaves down with it. A container in that position is recovered by looking
|
|
17
|
+
* inside it; a leaf has nothing inside, which is what this is for.
|
|
18
|
+
*
|
|
19
|
+
* Only the leaves the measurement is going to ask about: the walk goes
|
|
20
|
+
* through the nodes whose height extent is stale and no others.
|
|
21
|
+
*/
|
|
22
|
+
export function captureLeafHeights(node, out) {
|
|
23
|
+
// a layout host is a leaf to the floors, as it is to yoga
|
|
24
|
+
if (node._host !== null) {
|
|
25
|
+
out.set(node, node.yoga.getComputedHeight());
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
let leaf = true;
|
|
29
|
+
for (const child of node.children) {
|
|
30
|
+
if (!child.yoga || child.isWindow) continue;
|
|
31
|
+
if (child.style.display === 'none') continue;
|
|
32
|
+
if (child.style.position !== 'absolute') leaf = false;
|
|
33
|
+
if (child._floorH === undefined) captureLeafHeights(child, out);
|
|
34
|
+
}
|
|
35
|
+
if (leaf) out.set(node, node.yoga.getComputedHeight());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Whether this node has **named a floor of its own** on `axis` — the cases
|
|
40
|
+
* where CSS's `min-*: auto` is not the content-based minimum, so the node may
|
|
41
|
+
* give way to whatever squeezes it and its contents stop counting:
|
|
42
|
+
*
|
|
43
|
+
* - it clips, so what overflows it is not something to make room for. CSS
|
|
44
|
+
* computes `min-*: auto` to `0` on anything whose overflow is not
|
|
45
|
+
* `visible`, and it is the escape hatch Qt spells `QScrollArea` and GTK
|
|
46
|
+
* spells `min-content-width`;
|
|
47
|
+
* - the author wrote a number in `minWidth`/`minHeight`. `0` — "I can be any
|
|
48
|
+
* size" — is the one that matters and the one a scroll container gets
|
|
49
|
+
* given.
|
|
50
|
+
*
|
|
51
|
+
* This used to read "it was told it may shrink", back when `flexShrink`
|
|
52
|
+
* defaulted to yoga's `0` and asking for `1` was therefore a statement. Every
|
|
53
|
+
* node may shrink now (#249), so the clause carried no information and had to
|
|
54
|
+
* go: `minWidth: 0` is how a style says "down to nothing", and `flexShrink`
|
|
55
|
+
* is back to meaning only how eagerly the space *above* the floor is given
|
|
56
|
+
* up.
|
|
57
|
+
*/
|
|
58
|
+
function namesOwnFloor(node, axis) {
|
|
59
|
+
const style = node.style;
|
|
60
|
+
if (style.overflow === 'scroll' || style.overflow === 'hidden') return true;
|
|
61
|
+
return (
|
|
62
|
+
typeof (axis === 'width' ? style.minWidth : style.minHeight) === 'number'
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Whether this node's laid-out extent in a min-content pass is already the
|
|
68
|
+
* answer, so there is no need to look inside it. Everything that names a
|
|
69
|
+
* floor, plus the two other ways a style can bound itself: a **size**, which
|
|
70
|
+
* a min-content measurement here keeps rather than shrinking past (see
|
|
71
|
+
* `writeFloors`), and a **ceiling**, since CSS clamps the content
|
|
72
|
+
* suggestion by the specified `max-*` too.
|
|
73
|
+
*/
|
|
74
|
+
export function declaresOwnMinimum(node, axis) {
|
|
75
|
+
if (namesOwnFloor(node, axis)) return true;
|
|
76
|
+
const style = node.style;
|
|
77
|
+
const [size, max] =
|
|
78
|
+
axis === 'width'
|
|
79
|
+
? [style.width, style.maxWidth]
|
|
80
|
+
: [style.height, style.maxHeight];
|
|
81
|
+
return typeof size === 'number' || typeof max === 'number';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** In this node's parent's flow at all: an absolute or `display: 'none'`
|
|
85
|
+
* child is not a flex item and contributes nothing to what contains it —
|
|
86
|
+
* CSS says the same about both. */
|
|
87
|
+
export function inFlow(node) {
|
|
88
|
+
return (
|
|
89
|
+
node.yoga &&
|
|
90
|
+
!node.isWindow &&
|
|
91
|
+
node.style.position !== 'absolute' &&
|
|
92
|
+
node.style.display !== 'none'
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Which axis this node lays its children out along. */
|
|
97
|
+
const mainAxisOf = (node) => {
|
|
98
|
+
const direction = node.style.flexDirection ?? 'column';
|
|
99
|
+
return direction === 'row' || direction === 'row-reverse'
|
|
100
|
+
? 'width'
|
|
101
|
+
: 'height';
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Whether `child` is one the floors are **written on** along `axis`: a flex
|
|
106
|
+
* item on its container's main axis whose author left the minimum to the
|
|
107
|
+
* content. The same test `writeFloors` applies, asked ahead of time — it is
|
|
108
|
+
* what decides whether a stale extent is one anybody will read.
|
|
109
|
+
*/
|
|
110
|
+
function receivesFloor(child, axis) {
|
|
111
|
+
const parent = child.parent;
|
|
112
|
+
if (
|
|
113
|
+
!parent ||
|
|
114
|
+
parent._host !== null ||
|
|
115
|
+
mainAxisOf(parent) !== axis ||
|
|
116
|
+
!inFlow(child)
|
|
117
|
+
) {
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
const own = axis === 'width' ? 'minWidth' : 'minHeight';
|
|
121
|
+
return typeof child.style[own] !== 'number';
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Find what the last measurement can no longer answer for, before a layout
|
|
126
|
+
* pass clears the evidence.
|
|
127
|
+
*
|
|
128
|
+
* The floors are content, and yoga already keeps the exact record of which
|
|
129
|
+
* content moved: a style setter that changed something, a child that came
|
|
130
|
+
* or went, a text that asked to be re-measured all mark their node dirty
|
|
131
|
+
* and every node above it, and the next `calculateLayout` clears the lot.
|
|
132
|
+
* So this walks the dirty part of the tree — and only that part, since a
|
|
133
|
+
* clean node has clean children — and takes the cached extents off every
|
|
134
|
+
* node it finds (`_floorW`/`_floorH`), which is what "stale" means from
|
|
135
|
+
* here on. The dirty nodes are listed for `writeFloors`: they are the
|
|
136
|
+
* nodes whose children's floors can have moved — as is, later, every node
|
|
137
|
+
* `contentSpan` measures a child of, since a window's natural-size
|
|
138
|
+
* measurement (`_measure`) clears yoga's record before the first floors
|
|
139
|
+
* pass, and a scroll pane's rows measured through a clean scroll pane still
|
|
140
|
+
* need their floors written.
|
|
141
|
+
*
|
|
142
|
+
* `found` records whether any stale node is one a floor is written on: if
|
|
143
|
+
* none is, no measurement is owed on that axis at all, whatever changed —
|
|
144
|
+
* the extents that moved are ones nobody reads. An extent nobody read
|
|
145
|
+
* stays unmeasured, on a clean node, for as long as nobody does; it is
|
|
146
|
+
* asked for at the one moment it can start to matter, which is when its
|
|
147
|
+
* parent changes (a column that turns into a row), and its parent is dirty
|
|
148
|
+
* then. So the children of a dirty node are all looked at, and only the
|
|
149
|
+
* dirty ones are descended into.
|
|
150
|
+
*
|
|
151
|
+
* Until the first floors pass has settled both axes (`sweep`), the walk
|
|
152
|
+
* also goes down through nodes that were never measured, dirty or not: a
|
|
153
|
+
* window's natural-size measurement (`_measure`) lays the tree out before
|
|
154
|
+
* the first floors pass and clears yoga's record on the way, and a scroll
|
|
155
|
+
* pane that names its own minimum receives no floor while the rows inside
|
|
156
|
+
* it do. After that pass every reachable node has an extent on any axis
|
|
157
|
+
* that had a floor to write, and the dirty path is the whole story.
|
|
158
|
+
*
|
|
159
|
+
* A `display: 'none'` subtree is marked and left: it takes part in no
|
|
160
|
+
* layout, and a change inside it is still there when it is shown again,
|
|
161
|
+
* because yoga clears a hidden node's own flag but never its children's.
|
|
162
|
+
*/
|
|
163
|
+
export function collectFloorStale(node, stale, found, sweep) {
|
|
164
|
+
for (const child of node.children) {
|
|
165
|
+
if (!child.yoga || child.isWindow) continue;
|
|
166
|
+
const dirty = child.yoga.isDirty();
|
|
167
|
+
if (dirty) {
|
|
168
|
+
child._floorW = undefined;
|
|
169
|
+
child._floorH = undefined;
|
|
170
|
+
// the minimum yoga holds may not be ours any more: a style change on
|
|
171
|
+
// this node went through `applyLayoutStyle`, which writes the
|
|
172
|
+
// author's minimum over whatever floor was there
|
|
173
|
+
child._floorMinW = null;
|
|
174
|
+
child._floorMinH = null;
|
|
175
|
+
}
|
|
176
|
+
if (child.style.display === 'none') continue;
|
|
177
|
+
// A layout host reads its children's min-content widths
|
|
178
|
+
// (`LayoutChild.intrinsicSizes`), so an extent one of them is missing is a
|
|
179
|
+
// width pass owed, though no flex floor is written from it.
|
|
180
|
+
if (
|
|
181
|
+
child._floorW === undefined &&
|
|
182
|
+
(receivesFloor(child, 'width') || node._host != null)
|
|
183
|
+
) {
|
|
184
|
+
found.width = true;
|
|
185
|
+
}
|
|
186
|
+
if (child._floorH === undefined && receivesFloor(child, 'height')) {
|
|
187
|
+
found.height = true;
|
|
188
|
+
}
|
|
189
|
+
if (dirty) stale.add(child);
|
|
190
|
+
else if (
|
|
191
|
+
!sweep ||
|
|
192
|
+
(child._floorW !== undefined && child._floorH !== undefined)
|
|
193
|
+
) {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
collectFloorStale(child, stale, found, sweep);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* After a pass that settled the widths: which of the height floors, each a
|
|
202
|
+
* height *for a width*, were measured for a width their node no longer has.
|
|
203
|
+
*
|
|
204
|
+
* Walks down through every node whose width moved — a clean subtree whose
|
|
205
|
+
* root is still the width it was measured at holds no surprises, so the
|
|
206
|
+
* walk stops there — and asks each leaf it reaches the one question that
|
|
207
|
+
* matters, in JavaScript rather than through a layout pass: is your height
|
|
208
|
+
* at this width the height you had at the old one? A paragraph that still
|
|
209
|
+
* fits on its line says no, and so does every unwrapped label in a grid
|
|
210
|
+
* whose cells just moved a pixel, which is what makes a relayout of a large
|
|
211
|
+
* tree one pass instead of four. A leaf that wraps differently is marked
|
|
212
|
+
* stale with everything above it, and `hit.owed` says whether any of those
|
|
213
|
+
* is a node a floor is written on.
|
|
214
|
+
*
|
|
215
|
+
* Widths are compared with a pixel of slack: the measuring passes run with
|
|
216
|
+
* the pixel grid off and the real one with it on, so the same layout reads
|
|
217
|
+
* a fraction apart between them.
|
|
218
|
+
*/
|
|
219
|
+
export function probeHeightFloors(node, root, hit) {
|
|
220
|
+
for (const child of node.children) {
|
|
221
|
+
if (!child.yoga || child.isWindow) continue;
|
|
222
|
+
if (child.style.display === 'none') continue;
|
|
223
|
+
const stale = child._floorH === undefined;
|
|
224
|
+
const width = child.yoga.getComputedWidth();
|
|
225
|
+
const at = child._floorAtW;
|
|
226
|
+
if (!stale && at !== undefined && Math.abs(width - at) < 1) continue;
|
|
227
|
+
if (child._measureFn) {
|
|
228
|
+
if (
|
|
229
|
+
!stale &&
|
|
230
|
+
at !== undefined &&
|
|
231
|
+
child._heightForWidth(width) !== child._heightForWidth(at)
|
|
232
|
+
) {
|
|
233
|
+
markHeightStale(child, root, hit);
|
|
234
|
+
}
|
|
235
|
+
} else {
|
|
236
|
+
probeHeightFloors(child, root, hit);
|
|
237
|
+
}
|
|
238
|
+
// whatever the extent is, it is the extent for this width now — a
|
|
239
|
+
// stale one is about to be measured here, a clean one was just checked
|
|
240
|
+
child._floorAtW = width;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/** A leaf whose height moved takes every extent above it with it. */
|
|
245
|
+
function markHeightStale(node, root, hit) {
|
|
246
|
+
hit.marked = true;
|
|
247
|
+
for (let n = node; n && n !== root; n = n.parent) {
|
|
248
|
+
if (n._floorH === undefined) continue;
|
|
249
|
+
n._floorH = undefined;
|
|
250
|
+
root._floorsStale.add(n);
|
|
251
|
+
if (receivesFloor(n, 'height')) hit.owed = true;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Put the tree in the state a **min-content** measurement means: a node that
|
|
257
|
+
* has said how small it can be is let go all the way down to it, and a node
|
|
258
|
+
* that has not cannot give at all, because what it needs is the thing being
|
|
259
|
+
* measured.
|
|
260
|
+
*
|
|
261
|
+
* This is what the layout pass with no room on offer used to get from yoga's
|
|
262
|
+
* own `flexShrink: 0` default. Now that the default is CSS's `1` (#249) the
|
|
263
|
+
* pass has to be told, or every node would shrink to nothing and answer that
|
|
264
|
+
* the content needs no room — which is true of no content anywhere.
|
|
265
|
+
*
|
|
266
|
+
* Every child of a node being measured is told, so that the node's own
|
|
267
|
+
* layout is the one it always was; below a child whose extent is still
|
|
268
|
+
* good nothing is, since nothing in there is read. `out` collects what was
|
|
269
|
+
* written so `restoreShrink` can put back exactly that.
|
|
270
|
+
*/
|
|
271
|
+
export function setMeasuringShrink(node, axis, out) {
|
|
272
|
+
for (const child of node.children) {
|
|
273
|
+
if (!child.yoga || child.isWindow) continue;
|
|
274
|
+
// …and not into a `display: 'none'` subtree, which the measurement does
|
|
275
|
+
// not read and the floors are not written back through
|
|
276
|
+
if (child.style.display === 'none') continue;
|
|
277
|
+
const shrink = namesOwnFloor(child, axis) ? 1 : 0;
|
|
278
|
+
if ((child.style.flexShrink ?? 1) !== shrink) {
|
|
279
|
+
child.yoga.setFlexShrink(shrink);
|
|
280
|
+
out.push(child);
|
|
281
|
+
}
|
|
282
|
+
// not into a layout host's children: they are not flex items, and
|
|
283
|
+
// their own floors are measured tree by tree
|
|
284
|
+
if (floorStale(child, axis) && child._host === null) {
|
|
285
|
+
setMeasuringShrink(child, axis, out);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/** …and back to the layout everything else is run from. */
|
|
291
|
+
export function restoreShrink(shrunk) {
|
|
292
|
+
for (const child of shrunk) {
|
|
293
|
+
child.yoga.setFlexShrink(child.style.flexShrink ?? 1);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Whether `node`'s extent along `axis` has to be measured again. */
|
|
298
|
+
const floorStale = (node, axis) =>
|
|
299
|
+
(axis === 'width' ? node._floorW : node._floorH) === undefined;
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Pin every box under `node` at the width the pass just settled it at, so
|
|
303
|
+
* that the collapse which follows can only take **height** away.
|
|
304
|
+
*
|
|
305
|
+
* A minimum height is always a height *for a width* (`_applyContentFloors`),
|
|
306
|
+
* and the width it is for is the one the first pass settled — the tree at the
|
|
307
|
+
* size it is really about to be laid out at. The collapsing pass is asked
|
|
308
|
+
* only how much of that height the tree can give back, and it has no business
|
|
309
|
+
* re-deciding the widths on the way. Left to itself it does, because offering
|
|
310
|
+
* no height at all is not a small layout but a degenerate one: yoga answers a
|
|
311
|
+
* box measured against a zero cross size out of its bounds, without laying
|
|
312
|
+
* its children out at all, and where the width on offer was *also* undefined
|
|
313
|
+
* that bound is the node's `min-width` — the min-content floor this same
|
|
314
|
+
* routine wrote a moment earlier. Under a horizontally scrolling box the
|
|
315
|
+
* width is exactly what is undefined, a scroll container withholding its
|
|
316
|
+
* main-axis size from a child's flex basis the way browsers do. So the
|
|
317
|
+
* subtree was laid out at min-content **width**, where a label takes two
|
|
318
|
+
* lines, and the two-line height became the floor of the row around it: one
|
|
319
|
+
* line of text in a box that reserved two (issue #311).
|
|
320
|
+
*
|
|
321
|
+
* Pinning is enough because that answer is only wrong where there was no
|
|
322
|
+
* width to answer with — a box that names its own is measured at it whatever
|
|
323
|
+
* else the pass is doing, and its children are laid out inside that. It costs
|
|
324
|
+
* nothing either: every leaf is offered the width it was already measured at,
|
|
325
|
+
* so the paragraphs the first pass shaped come back out of the layout cache.
|
|
326
|
+
*
|
|
327
|
+
* Pinned as far as the measurement reads, like the shrink: the children of
|
|
328
|
+
* every node whose extent is being measured, and no further.
|
|
329
|
+
*/
|
|
330
|
+
export function freezeWidths(node, out) {
|
|
331
|
+
for (const child of node.children) {
|
|
332
|
+
if (!child.yoga || child.isWindow) continue;
|
|
333
|
+
// as in `setMeasuringShrink`: a `display: 'none'` subtree was not laid
|
|
334
|
+
// out, so there is no width in there to keep
|
|
335
|
+
if (child.style.display === 'none') continue;
|
|
336
|
+
child.yoga.setWidth(child.yoga.getComputedWidth());
|
|
337
|
+
out.push(child);
|
|
338
|
+
if (child._floorH === undefined && child._host === null) {
|
|
339
|
+
freezeWidths(child, out);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** …and back to the width the style asks for, on exactly what was pinned. */
|
|
345
|
+
export function restoreWidths(frozen) {
|
|
346
|
+
for (const child of frozen) child.yoga.setWidth(child.style.width);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* How far this node's content actually reaches along one axis, in its own
|
|
351
|
+
* coordinate space — the reading of a layout the root was given no room for,
|
|
352
|
+
* where `getComputedWidth()` says nothing (a root offered 0 is clamped to 0)
|
|
353
|
+
* but the children still sit where their own styles put them.
|
|
354
|
+
*
|
|
355
|
+
* A **span** rather than a rightmost edge, because a `center` or
|
|
356
|
+
* `space-around` row given less room than it needs overflows *both* sides —
|
|
357
|
+
* exactly as CSS says it should — and its first child's edge lands at a
|
|
358
|
+
* negative offset. The padding and border are added back on both sides
|
|
359
|
+
* because the span is measured between the children, inside them.
|
|
360
|
+
*
|
|
361
|
+
* Out-of-flow children are skipped, as they are in CSS: an absolutely
|
|
362
|
+
* positioned node contributes nothing to what contains it, and a
|
|
363
|
+
* `display: 'none'` one is not there at all.
|
|
364
|
+
*
|
|
365
|
+
* `intrinsic` carries what the leaves measured to before the pass being read
|
|
366
|
+
* squashed them — see `_measureContentSpans`, which is the only caller that
|
|
367
|
+
* needs it. A container that came out at nothing is recovered by looking
|
|
368
|
+
* inside it; a leaf has nothing inside, so it has to be remembered.
|
|
369
|
+
*
|
|
370
|
+
* What every node contributes to the box around it — which is exactly that
|
|
371
|
+
* node's automatic minimum size — is written onto the node (`_floorW`,
|
|
372
|
+
* `_floorH`), so one pass and one walk give the whole tree its floors (#249)
|
|
373
|
+
* instead of a measurement per node, and so that the next measurement can
|
|
374
|
+
* **read it back instead of looking again**: a child whose extent is still
|
|
375
|
+
* there is taken at that number, and nothing under it is visited. The
|
|
376
|
+
* extent is a function of the subtree alone — its content and its styles —
|
|
377
|
+
* which is what makes the cache honest: `collectFloorStale` takes it off
|
|
378
|
+
* every node whose subtree changed, and for a height the width it was
|
|
379
|
+
* measured at is kept beside it (`_floorAtW`) for `probeHeightFloors` to
|
|
380
|
+
* check. The recursion is therefore over the stale children only, and
|
|
381
|
+
* still through the ones whose own content does not count towards this
|
|
382
|
+
* one's: a scroll pane contributes nothing to the floor above it and still
|
|
383
|
+
* needs floors written *inside* it, or the column of rows it holds would
|
|
384
|
+
* shrink to the viewport and there would be nothing left to scroll.
|
|
385
|
+
*/
|
|
386
|
+
export function contentSpan(node, axis, intrinsic, root) {
|
|
387
|
+
const yoga = node.yoga;
|
|
388
|
+
const horizontal = axis === 'width';
|
|
389
|
+
const own = horizontal ? yoga.getComputedWidth() : yoga.getComputedHeight();
|
|
390
|
+
const [startEdge, endEdge] = horizontal
|
|
391
|
+
? [Yoga.EDGE_LEFT, Yoga.EDGE_RIGHT]
|
|
392
|
+
: [Yoga.EDGE_TOP, Yoga.EDGE_BOTTOM];
|
|
393
|
+
const axisIsMain = mainAxisOf(node) === axis;
|
|
394
|
+
let start = Infinity;
|
|
395
|
+
let end = -Infinity;
|
|
396
|
+
// What the children after this one were laid out too early by. A node the
|
|
397
|
+
// pass squashed is one its siblings were packed in behind, so recovering
|
|
398
|
+
// its extent without moving them along would lose exactly what was
|
|
399
|
+
// recovered — the span would come out the same as before.
|
|
400
|
+
let shift = 0;
|
|
401
|
+
// A layout host is a leaf here, as it is to yoga: its children are its
|
|
402
|
+
// algorithm's to size, and what it contributes is what the algorithm
|
|
403
|
+
// answers — the leaf case below, which asks its measure function.
|
|
404
|
+
for (const child of node._host === null ? node.children : NO_CHILDREN) {
|
|
405
|
+
// the same set that joins the flex tree: a nested <window> is laid out
|
|
406
|
+
// by itself, and a <text> span has no box of its own
|
|
407
|
+
if (!child.yoga || child.isWindow) continue;
|
|
408
|
+
if (child.style.display === 'none') continue;
|
|
409
|
+
const laidOut = horizontal
|
|
410
|
+
? child.yoga.getComputedWidth()
|
|
411
|
+
: child.yoga.getComputedHeight();
|
|
412
|
+
let extent = horizontal ? child._floorW : child._floorH;
|
|
413
|
+
if (extent === undefined) {
|
|
414
|
+
const span = contentSpan(child, axis, intrinsic, root);
|
|
415
|
+
// What the child needs from this box. A node that has said how small
|
|
416
|
+
// it can be is taken at its word — the measuring pass already let it
|
|
417
|
+
// shrink to exactly that — and anything else is asked what is inside
|
|
418
|
+
// it. Its laid-out size is deliberately *not* a floor under that
|
|
419
|
+
// answer: nothing shrank in this pass, so a box that measures its own
|
|
420
|
+
// content is sitting at its **max**-content size, which is the width
|
|
421
|
+
// a label would like to be rather than the width it can be squeezed
|
|
422
|
+
// to.
|
|
423
|
+
extent = declaresOwnMinimum(child, axis) ? laidOut : span;
|
|
424
|
+
if (horizontal) child._floorW = extent;
|
|
425
|
+
else {
|
|
426
|
+
child._floorH = extent;
|
|
427
|
+
child._floorAtW = child.yoga.getComputedWidth();
|
|
428
|
+
}
|
|
429
|
+
root._floorsMeasured += 1;
|
|
430
|
+
// a fresh extent is a floor to write, whether or not this node was
|
|
431
|
+
// dirty: see `collectFloorStale`
|
|
432
|
+
root._floorsStale.add(node);
|
|
433
|
+
}
|
|
434
|
+
if (child.style.position === 'absolute') continue;
|
|
435
|
+
const at =
|
|
436
|
+
(horizontal
|
|
437
|
+
? child.yoga.getComputedLeft()
|
|
438
|
+
: child.yoga.getComputedTop()) + shift;
|
|
439
|
+
// Only along the axis the children are packed on: on the other one they
|
|
440
|
+
// all start from the same edge, so nothing follows anything.
|
|
441
|
+
if (axisIsMain) shift += extent - laidOut;
|
|
442
|
+
start = Math.min(start, at);
|
|
443
|
+
end = Math.max(end, at + extent);
|
|
444
|
+
}
|
|
445
|
+
if (start === Infinity) {
|
|
446
|
+
// A leaf: nothing inside to look at, and what the pass did to it may
|
|
447
|
+
// have been a stretch rather than a measurement. So it is asked again.
|
|
448
|
+
//
|
|
449
|
+
// **Across**, a leaf that measures itself answers outright: the width it
|
|
450
|
+
// gives when offered none is its min-content width, which for a
|
|
451
|
+
// paragraph is its longest word. That *replaces* the laid-out width
|
|
452
|
+
// rather than joining it in a `max`, because a measured leaf's base size
|
|
453
|
+
// is its max-content width — the whole line, unwrapped — and taking the
|
|
454
|
+
// larger of the two would floor every label at the width it would like
|
|
455
|
+
// to be. A leaf that measures nothing has only its own box to report.
|
|
456
|
+
//
|
|
457
|
+
// **Down**, it is what the leaf measured before the collapse squashed
|
|
458
|
+
// it: a height at a settled width is not a leaf's to give.
|
|
459
|
+
if (horizontal) {
|
|
460
|
+
const measured = node._measureFn?.(
|
|
461
|
+
0,
|
|
462
|
+
Yoga.MEASURE_MODE_AT_MOST,
|
|
463
|
+
undefined,
|
|
464
|
+
Yoga.MEASURE_MODE_UNDEFINED,
|
|
465
|
+
)?.width;
|
|
466
|
+
return measured ?? own;
|
|
467
|
+
}
|
|
468
|
+
return Math.max(own, intrinsic?.get(node) ?? 0);
|
|
469
|
+
}
|
|
470
|
+
const edges =
|
|
471
|
+
yoga.getComputedPadding(startEdge) +
|
|
472
|
+
yoga.getComputedPadding(endEdge) +
|
|
473
|
+
yoga.getComputedBorder(startEdge) +
|
|
474
|
+
yoga.getComputedBorder(endEdge);
|
|
475
|
+
return end - start + edges;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Write CSS's **automatic minimum size** onto the flex items in `node`: a
|
|
480
|
+
* floor of the extent each needs, along the axis its container lays out on.
|
|
481
|
+
*
|
|
482
|
+
* This is the other half of `flexShrink` defaulting to `1` (#249), and
|
|
483
|
+
* neither half is any good without the other. Yoga implements the shrink and
|
|
484
|
+
* not the floor, so a default of `1` on its own shrinks everything to
|
|
485
|
+
* nothing — a scroll pane's content collapses into its viewport and there is
|
|
486
|
+
* nothing left to scroll — while a default of `0` never squeezes a row into
|
|
487
|
+
* the space it has. CSS has both, and what makes its `flex-shrink: 1` safe is
|
|
488
|
+
* that `min-width: auto` on a flex item resolves to the item's min-content
|
|
489
|
+
* size. That is what this writes.
|
|
490
|
+
*
|
|
491
|
+
* Only the **main** axis, as in CSS: shrinking happens along the axis the
|
|
492
|
+
* container packs on, and on the other one an item is stretched or fits its
|
|
493
|
+
* content either way. On the other axis the child gets its style's minimum
|
|
494
|
+
* back — which is what takes a floor off a child whose container turned
|
|
495
|
+
* from a column into a row, since the floor it needs now is on the other
|
|
496
|
+
* axis.
|
|
497
|
+
*
|
|
498
|
+
* Where this deliberately parts company with CSS is a node that named a
|
|
499
|
+
* size: CSS floors that at `min(the size, the content)`, so a `height: 40`
|
|
500
|
+
* box with nothing in it still squashes to nothing in a column too short for
|
|
501
|
+
* it. That rule is survivable on the web because a `<div>` is a *block*
|
|
502
|
+
* container and its children are not flex items at all; here every box lays
|
|
503
|
+
* its children out with flex, so it would apply to the whole tree — and a
|
|
504
|
+
* row of 40px cells silently 8px tall is not what anyone wrote. **A size
|
|
505
|
+
* that was named is a size that is kept**, and `minHeight: 0` is how an
|
|
506
|
+
* author says otherwise.
|
|
507
|
+
*
|
|
508
|
+
* One node's children, from the extents on them (`contentSpan`): the
|
|
509
|
+
* callers run it over every node whose children's extents may have moved,
|
|
510
|
+
* and nothing else — a floor that did not change is not written again, so a
|
|
511
|
+
* clean subtree is neither visited nor dirtied. What was written is kept
|
|
512
|
+
* (`_floorMinW`/`_floorMinH`) so that the next write can tell; a stale
|
|
513
|
+
* extent writes the style's own minimum, which is how a floor comes off a
|
|
514
|
+
* node that is about to be measured, and which is why measuring a node
|
|
515
|
+
* that carries one cannot read it back as content the tree cannot give up.
|
|
516
|
+
*
|
|
517
|
+
* A floor is written **unrounded**, and the measurement it came from ran
|
|
518
|
+
* with the pixel grid off (`measuringExactly`) for the reason given there:
|
|
519
|
+
* rounding a floor grows the tree a pixel per nesting level. What that
|
|
520
|
+
* leaves is a sharp edge in yoga worth knowing about before writing a
|
|
521
|
+
* measure function. A line whose items are all held at their floors is one
|
|
522
|
+
* yoga freezes item by item, subtracting each item's shrink factor from the
|
|
523
|
+
* line's total as it goes; the total only cancels to zero if the sizes add
|
|
524
|
+
* up exactly in binary. Three items of, say, 239.28 in a column that
|
|
525
|
+
* overflows do not, and yoga divides the overflow by the rounding residue
|
|
526
|
+
* instead of skipping the division — the items come back a billion pixels
|
|
527
|
+
* tall (issue #411). Whole pixels cancel exactly, which is why the text
|
|
528
|
+
* measures here answer in them (`TextNode._trim`).
|
|
529
|
+
*/
|
|
530
|
+
export function writeFloors(node, axis) {
|
|
531
|
+
// a layout host's children are sized by its algorithm, never by a flex
|
|
532
|
+
// line's shrink, so there is no floor to hold them at
|
|
533
|
+
if (node._host !== null) return;
|
|
534
|
+
const horizontal = axis === 'width';
|
|
535
|
+
const axisIsMain = mainAxisOf(node) === axis;
|
|
536
|
+
const own = horizontal ? 'minWidth' : 'minHeight';
|
|
537
|
+
for (const child of node.children) {
|
|
538
|
+
if (!child.yoga || child.isWindow) continue;
|
|
539
|
+
let floor;
|
|
540
|
+
// A floor of 0 is what yoga does anyway, and an author who named their
|
|
541
|
+
// own `minWidth`/`minHeight` has already answered — overwriting it would
|
|
542
|
+
// put a measurement of ours above a number they wrote.
|
|
543
|
+
if (axisIsMain && inFlow(child) && typeof child.style[own] !== 'number') {
|
|
544
|
+
const extent = horizontal ? child._floorW : child._floorH;
|
|
545
|
+
if (extent > 0) floor = extent;
|
|
546
|
+
}
|
|
547
|
+
if ((horizontal ? child._floorMinW : child._floorMinH) === floor) continue;
|
|
548
|
+
const value = floor ?? child.style[own];
|
|
549
|
+
if (horizontal) {
|
|
550
|
+
child.yoga.setMinWidth(value);
|
|
551
|
+
child._floorMinW = floor;
|
|
552
|
+
} else {
|
|
553
|
+
child.yoga.setMinHeight(value);
|
|
554
|
+
child._floorMinH = floor;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/** `writeFloors` over the stale nodes of one subtree — a layout host's
|
|
560
|
+
* child, whose height floors are measured tree by tree
|
|
561
|
+
* (`Node._measureHostChildHeights`). */
|
|
562
|
+
export function writeFloorsWithin(node, axis, stale) {
|
|
563
|
+
if (stale.has(node)) writeFloors(node, axis);
|
|
564
|
+
for (const child of node.children) {
|
|
565
|
+
if (child.yoga && !child.isWindow) writeFloorsWithin(child, axis, stale);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
/** Take every height extent under `node` off, and list its nodes to be
|
|
570
|
+
* written again: its width moved, and a height is a height for a width. */
|
|
571
|
+
export function forgetHeightFloors(node, stale) {
|
|
572
|
+
node._floorH = undefined;
|
|
573
|
+
stale.add(node);
|
|
574
|
+
for (const child of node.children) {
|
|
575
|
+
if (child.yoga && !child.isWindow) forgetHeightFloors(child, stale);
|
|
576
|
+
}
|
|
577
|
+
}
|