react-x11 2.11.0 → 2.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.
Files changed (96) hide show
  1. package/README.md +278 -129
  2. package/package.json +10 -3
  3. package/src/Reconciler.js +15 -17
  4. package/src/a11y.js +2 -2
  5. package/src/anchor.js +7 -5
  6. package/src/bootstrap.js +14 -0
  7. package/src/clientmessage.js +1 -1
  8. package/src/cocoa/app.js +292 -49
  9. package/src/cocoa/bezels.js +175 -30
  10. package/src/cocoa/dnd.js +27 -13
  11. package/src/cocoa/fonts.js +3 -3
  12. package/src/cocoa/glarea.js +20 -3
  13. package/src/cocoa/main.d.ts +8 -0
  14. package/src/cocoa/main.js +43 -0
  15. package/src/cocoa/panehost.js +15 -5
  16. package/src/cocoa/presenter.js +13 -9
  17. package/src/cocoa/promotion.js +4 -7
  18. package/src/cocoa/relaunch.js +207 -0
  19. package/src/cocoa/threaded.js +246 -0
  20. package/src/cocoa/window.js +256 -42
  21. package/src/components/Select.js +2 -2
  22. package/src/components/anchor.js +3 -3
  23. package/src/components/native.js +12 -7
  24. package/src/components/theme.js +2 -2
  25. package/src/debug.js +1 -1
  26. package/src/decorations.js +1 -1
  27. package/src/editmenu.js +2 -2
  28. package/src/errors.js +46 -0
  29. package/src/events.js +6 -6
  30. package/src/foreignnodes.js +3 -2
  31. package/src/frames.js +2 -2
  32. package/src/glnodes.js +1 -1
  33. package/src/grid.js +1653 -0
  34. package/src/host.d.ts +230 -0
  35. package/src/host.js +11 -3
  36. package/src/imagesource.js +1 -1
  37. package/src/index.d.ts +21 -4
  38. package/src/index.js +9 -1
  39. package/src/layouts.js +721 -0
  40. package/src/node.d.ts +4 -2
  41. package/src/node.js +19 -21
  42. package/src/nodes/animation.js +644 -0
  43. package/src/nodes/box.js +21 -0
  44. package/src/nodes/boxpaint.js +473 -0
  45. package/src/nodes/canvas.js +269 -0
  46. package/src/nodes/cascade.js +600 -0
  47. package/src/nodes/damage.js +183 -0
  48. package/src/nodes/edithistory.js +124 -0
  49. package/src/nodes/editmenupopup.js +260 -0
  50. package/src/nodes/hittest.js +185 -0
  51. package/src/nodes/image.js +266 -0
  52. package/src/nodes/install.js +75 -0
  53. package/src/nodes/invalidate.js +465 -0
  54. package/src/nodes/kinds.js +31 -0
  55. package/src/nodes/layout.js +439 -0
  56. package/src/nodes/layouthost.js +949 -0
  57. package/src/nodes/node.js +868 -0
  58. package/src/nodes/paint.js +466 -0
  59. package/src/nodes/position.js +366 -0
  60. package/src/nodes/preedit.js +127 -0
  61. package/src/nodes/queries.js +330 -0
  62. package/src/nodes/rects.js +102 -0
  63. package/src/nodes/scrollable.js +891 -0
  64. package/src/nodes/scrollbars.js +138 -0
  65. package/src/nodes/scrollblit.js +1034 -0
  66. package/src/nodes/selectable.js +142 -0
  67. package/src/nodes/styling.js +225 -0
  68. package/src/nodes/text.js +649 -0
  69. package/src/nodes/textarea.js +391 -0
  70. package/src/nodes/textinput.js +1146 -0
  71. package/src/nodes/util.js +17 -0
  72. package/src/nodes/window/anchoring.js +161 -0
  73. package/src/nodes/window/capabilities.js +190 -0
  74. package/src/nodes/window/debugpaint.js +83 -0
  75. package/src/nodes/window/droptarget.js +145 -0
  76. package/src/nodes/window/floors.js +577 -0
  77. package/src/nodes/window/flush.js +334 -0
  78. package/src/nodes/window/hints.js +482 -0
  79. package/src/nodes/window/listeners.js +222 -0
  80. package/src/nodes/window/popup.js +71 -0
  81. package/src/nodes/window/size.js +591 -0
  82. package/src/nodes/window/window.js +945 -0
  83. package/src/palette.js +1 -1
  84. package/src/registry.js +7 -3
  85. package/src/styles.js +137 -15
  86. package/src/svgnodes.js +2 -1
  87. package/src/testing/harness.js +2 -2
  88. package/src/textselection.js +5 -3
  89. package/src/trace-registry.js +1 -1
  90. package/src/types/components.d.ts +38 -6
  91. package/src/types/elements.d.ts +11 -1
  92. package/src/types/nodes.d.ts +17 -2
  93. package/src/types/style.d.ts +94 -3
  94. package/src/windowstate.js +1 -1
  95. package/src/yoga.js +1 -1
  96. 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
+ }