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,591 @@
1
+ // Windows sized by their content: measuring the tree against its content
2
+ // floors, laying the root out, deferring floors during a live resize and
3
+ // catching up after, and refitting the window to what it holds.
4
+
5
+ import { measuringExactly } from '../../styles.js';
6
+ import { availableArea } from '../../screens.js';
7
+ import {
8
+ captureLeafHeights,
9
+ collectFloorStale,
10
+ probeHeightFloors,
11
+ setMeasuringShrink,
12
+ restoreShrink,
13
+ freezeWidths,
14
+ restoreWidths,
15
+ contentSpan,
16
+ writeFloors,
17
+ } from './floors.js';
18
+ import {
19
+ MAX_WINDOW_EXTENT,
20
+ clampExtent,
21
+ clampBound,
22
+ screenOriginOf,
23
+ CONTENT_BOUND_PROPS,
24
+ isContentBound,
25
+ numericBound,
26
+ isAutoSize,
27
+ scaleWindowGeometry,
28
+ } from './hints.js';
29
+
30
+ /** Content-sized windows, installed onto `WindowNode.prototype` by window.js. */
31
+ export class WindowSize {
32
+ /**
33
+ * One measuring pass over the tree, read back into the extents of every
34
+ * node still to be measured (`contentSpan`), and the root's own span
35
+ * returned — the number a `minWidth="auto"` window sends as its hint.
36
+ *
37
+ * `forWidth` is the width the heights are measured for; `probe` says
38
+ * whether the widths that pass settles are still to be checked against
39
+ * the ones the height floors were measured at (`probeHeightFloors`) —
40
+ * they are when nothing has looked yet this frame, and a leaf the probe
41
+ * finds moved has its floor taken off and the pass run again, since a
42
+ * floor still on a node being measured would be read back as content.
43
+ */
44
+ _measureContentSpans(axis, forWidth, probe = false) {
45
+ this._sweepLayoutHosts();
46
+ const yoga = this.yoga;
47
+ const dir = this._rootDirection;
48
+ // The root carries whatever size the last pass pinned on it, and an
49
+ // available size means nothing to a root that has one of its own.
50
+ yoga.setWidth(undefined);
51
+ yoga.setHeight(undefined);
52
+ if (axis === 'width') {
53
+ // the layout hosts' children first, tree by tree: what they can be
54
+ // squeezed to is what the hosts answer the pass below with
55
+ if (this._layoutHosts.size !== 0) this._measureHostChildWidths();
56
+ const shrunk = [];
57
+ setMeasuringShrink(this, axis, shrunk);
58
+ this._layoutPasses += 1;
59
+ yoga.calculateLayout(0, undefined, dir);
60
+ const span = contentSpan(this, axis, null, this);
61
+ restoreShrink(shrunk);
62
+ return span;
63
+ }
64
+ // Height takes two passes. The first is the tree at its real width with
65
+ // no bound on the height, which is where every leaf reports the height
66
+ // it actually needs there — a wrapped paragraph's is settled by the
67
+ // width, and no leaf can give any of it back. It runs before the shrink
68
+ // is borrowed, since the widths it settles are the real ones. The second
69
+ // is the one that collapses, and it squashes a leaf that a `row`
70
+ // stretches: those are the ones the map above puts back. The widths the
71
+ // first pass settled are held across the second (`freezeWidths`), which
72
+ // is the only thing keeping it a collapse rather than a second opinion.
73
+ this._layoutPasses += 1;
74
+ yoga.calculateLayout(forWidth, undefined, dir);
75
+ if (probe && this._probeHeightFloors().marked) {
76
+ this._writeFloors('height');
77
+ this._layoutPasses += 1;
78
+ yoga.calculateLayout(forWidth, undefined, dir);
79
+ }
80
+ const intrinsic = new Map();
81
+ captureLeafHeights(this, intrinsic);
82
+ const frozen = [];
83
+ freezeWidths(this, frozen);
84
+ const shrunk = [];
85
+ setMeasuringShrink(this, axis, shrunk);
86
+ this._layoutPasses += 1;
87
+ yoga.calculateLayout(forWidth, 0, dir);
88
+ const span = contentSpan(this, axis, intrinsic, this);
89
+ restoreWidths(frozen);
90
+ restoreShrink(shrunk);
91
+ return span;
92
+ }
93
+
94
+ /**
95
+ * What the last measurement can no longer answer for, taken off the
96
+ * nodes and listed (`collectFloorStale`). Run ahead of anything that lays
97
+ * the tree out, since a pass clears yoga's record of what changed — and
98
+ * cheap enough to run twice in a frame, because the second walk finds the
99
+ * marks the first one left.
100
+ */
101
+ _collectFloorStale() {
102
+ this._sweepLayoutHosts();
103
+ const found = { width: false, height: false };
104
+ this._floorsStale.clear();
105
+ // the root's own children are written from here too, and its direction
106
+ // can move like any node's
107
+ this._floorsStale.add(this);
108
+ collectFloorStale(this, this._floorsStale, found, !this._floorsSwept);
109
+ return found;
110
+ }
111
+
112
+ /** The floors on the children of every node found stale, from the extents
113
+ * they carry — `writeFloors` for each. */
114
+ _writeFloors(axis) {
115
+ for (const node of this._floorsStale) {
116
+ if (!node.destroyed) writeFloors(node, axis);
117
+ }
118
+ }
119
+
120
+ /** `probeHeightFloors` over this window's tree. */
121
+ _probeHeightFloors() {
122
+ const hit = { marked: false, owed: false };
123
+ probeHeightFloors(this, this, hit);
124
+ return hit;
125
+ }
126
+
127
+ /**
128
+ * Measure the width extents that are stale and write the width floors
129
+ * from them. The floors on the nodes about to be measured come off first
130
+ * (a stale extent writes the style's own minimum), which is what keeps a
131
+ * floor from ratcheting: read back as content, it could only ever grow.
132
+ */
133
+ _measureWidthFloors() {
134
+ this._writeFloors('width');
135
+ const span = this._measureContentSpans('width');
136
+ this._writeFloors('width');
137
+ return span;
138
+ }
139
+
140
+ /** The same for the heights, at `forWidth`. */
141
+ _measureHeightFloors(forWidth, probe) {
142
+ this._writeFloors('height');
143
+ const span = this._measureContentSpans('height', forWidth, probe);
144
+ this._writeFloors('height');
145
+ return span;
146
+ }
147
+
148
+ _measureMinimum(axis, forWidth) {
149
+ // Measured from the styles alone: the stale nodes' own floors come off
150
+ // in the measurement, and a clean node's extent was measured the same
151
+ // way before it was floored.
152
+ this._collectFloorStale();
153
+ return measuringExactly(() =>
154
+ Math.ceil(
155
+ axis === 'width'
156
+ ? this._measureWidthFloors()
157
+ : this._measureHeightFloors(forWidth, true),
158
+ ),
159
+ );
160
+ }
161
+
162
+ /** The real layout pass: the tree at the window's size, on the pixel grid. */
163
+ _layoutRoot(width, height) {
164
+ this._sweepLayoutHosts();
165
+ this._layoutPasses += 1;
166
+ this.yoga.setWidth(width);
167
+ this.yoga.setHeight(height);
168
+ this.yoga.calculateLayout(width, height, this._rootDirection);
169
+ }
170
+
171
+ /**
172
+ * Give every flex item in this window's tree the floor CSS calls its
173
+ * automatic minimum size, so that `flexShrink`'s default of `1` squeezes a
174
+ * row into the space it has without squeezing its contents out of
175
+ * existence, and lay the tree out with them. See `writeFloors` for what
176
+ * that means and why both halves are needed.
177
+ *
178
+ * Two measurements, in this order because they depend that way round: the
179
+ * widths from a pass with no room on offer at all, then — with those floors
180
+ * already applied — the heights at the width the window is about to be laid
181
+ * out at, since a minimum height is always a height *for a width*.
182
+ *
183
+ * Nothing about this is per frame: the floors are content, so they survive
184
+ * every frame that did not change any (`_floorsDirty`), which is what keeps
185
+ * a wheel notch to the one layout pass it always was. And nothing about
186
+ * it is per node either: every node keeps the extent it was last measured
187
+ * at, and a measurement re-reads only the nodes whose subtree changed
188
+ * (`collectFloorStale`), taking the rest at the number they carry. So a
189
+ * padding change on a container measures the container and nothing
190
+ * below it, a row that mounts measures itself alone, and a colour change
191
+ * measures nothing.
192
+ *
193
+ * The passes are paid for only where a floor is going to be **written**
194
+ * from what they find. The width pass runs when a stale node is one on a
195
+ * row's main axis; and the heights are settled the other way round — the
196
+ * real layout runs first, `probeHeightFloors` walks the nodes it moved
197
+ * and asks each leaf whether its height at its new width is the height
198
+ * it had, and only if one says otherwise (or content changed under a
199
+ * node a floor is written on) do the two height passes run and the
200
+ * layout with them. A relayout of a large tree whose labels all still
201
+ * fit — a panel toggle, a theme switch, a resize that wraps nothing — is
202
+ * one pass over yoga where it was four.
203
+ */
204
+ _applyContentFloors(width, height) {
205
+ const found = this._collectFloorStale();
206
+ measuringExactly(() => {
207
+ if (found.width) this._measureWidthFloors();
208
+ else this._writeFloors('width');
209
+ // from the extents on hand; a stale one writes the style's minimum,
210
+ // which is the floor coming off ahead of its measurement below
211
+ this._writeFloors('height');
212
+ });
213
+ let heights = found.height;
214
+ let probed = false;
215
+ if (!heights) {
216
+ this._layoutRoot(width, height);
217
+ heights = this._probeHeightFloors().owed;
218
+ probed = true;
219
+ }
220
+ if (heights) {
221
+ measuringExactly(() => this._measureHeightFloors(width, !probed));
222
+ this._layoutRoot(width, height);
223
+ }
224
+ this._floorsDirty = false;
225
+ this._floorsContentDirty = false;
226
+ this._floorsSwept = true;
227
+ this._floorsWidth = width;
228
+ }
229
+
230
+ /**
231
+ * Answer a live resize with the floors already in hand, and measure fresh
232
+ * ones once the drag is over.
233
+ *
234
+ * The floors were half of a relayout on a large tree — three extra layout
235
+ * passes and their walks, measured at 21 of a 44ms frame on 3,600 nodes
236
+ * (`npm run bench:presenters -- --scenario=layout`) before they were
237
+ * measured incrementally — and a resize is the one layout change they
238
+ * cannot follow at input rate: AppKit's resize loop calls the frame for
239
+ * every pointer move, from inside the event, and the next move waits for
240
+ * the frame. So a tick of a drag lays the tree out against the floors the
241
+ * last measurement left, which are exact along the main axis (a
242
+ * min-content width is content, and the content did not move) and a
243
+ * frame stale for wrapped text along the other, and the frame after the
244
+ * release measures once and lays out again — "answer the input, then
245
+ * catch up". Only while the window says it is being resized live
246
+ * (`liveResizing`, set between AppKit's begin and end of the drag; an X
247
+ * window has no such thing and takes the measured path every time), only
248
+ * when the floors exist to reuse, and never over a content change the
249
+ * floors have not seen — a row that mounted mid-drag has no floor at all,
250
+ * and no floor is the collapse #249 exists to prevent.
251
+ */
252
+ _deferContentFloors(width) {
253
+ // nothing to measure: `_applyContentFloors` returns at once, and a
254
+ // catch-up frame would owe nothing
255
+ if (!this._floorsDirty && this._floorsWidth === width) return false;
256
+ return (
257
+ this.window?.liveResizing === true &&
258
+ this._floorsWidth != null &&
259
+ !this._floorsContentDirty
260
+ );
261
+ }
262
+
263
+ /**
264
+ * The frame a deferred measurement owes: a full relayout with fresh
265
+ * floors, run on the first frame tick after the live resize ends. One at
266
+ * a time — a drag is many ticks, and the catch-up waits for the last of
267
+ * them rather than following each.
268
+ */
269
+ _scheduleFloorsCatchUp() {
270
+ if (this._floorsCatchUp) return;
271
+ this._floorsCatchUp = true;
272
+ const schedule =
273
+ typeof this.window?.requestAnimationFrame === 'function'
274
+ ? (cb) => this.window.requestAnimationFrame(cb)
275
+ : (cb) => setImmediate(cb);
276
+ const run = () => {
277
+ if (this.destroyed || !this.window) {
278
+ this._floorsCatchUp = false;
279
+ return;
280
+ }
281
+ // still dragging — a drag that pauses has not ended: wait on
282
+ if (this.window.liveResizing) {
283
+ schedule(run);
284
+ return;
285
+ }
286
+ this._floorsCatchUp = false;
287
+ this._floorsDirty = true;
288
+ this.invalidate(true, null, 'resize');
289
+ this.flush();
290
+ };
291
+ schedule(run);
292
+ }
293
+
294
+ /**
295
+ * What the content has to say about this window's size: the size it wants
296
+ * for whichever of `width`/`height` is `'auto'`, and the numbers an
297
+ * `'auto'` bound resolves to.
298
+ *
299
+ * The **natural** size is CSS shrink-to-fit, then height-for-width:
300
+ *
301
+ * 1. Lay the tree out with **no available width**, which is what yoga's
302
+ * `undefined` means: every measure function is asked in
303
+ * `MEASURE_MODE_UNDEFINED`, text does not wrap, and the root reports
304
+ * its max-content width.
305
+ * 2. Clamp that into `[minWidth, min(maxWidth, the screen)]`.
306
+ * 3. **Lay out again at the clamped width.** This is the pass that
307
+ * matters and the one it is tempting to skip: a paragraph that had to
308
+ * wrap at the clamped width is taller than the max-content pass said,
309
+ * and a window sized from that first height would cut its own text off.
310
+ *
311
+ * Where CSS and X part ways: shrink-to-fit is
312
+ * `min(max(min-content, available), max-content)`, and that `max(...)`
313
+ * means a CSS box never goes below its min-content size even when it
314
+ * overflows. A window cannot be wider than the screen, so the clamp wins
315
+ * and the content is cut instead.
316
+ *
317
+ * The two answers are the pair Qt and GTK both hand their toplevels —
318
+ * `sizeHint()`/`minimumSizeHint()`, `gtk_widget_measure`'s
319
+ * `(minimum, natural)` — which is why `'auto'` reads as the natural size
320
+ * on a cap and as the minimum on a floor: it means "ask the content",
321
+ * and the content's answer to *how big* is not its answer to *how small*.
322
+ *
323
+ * Runs before `CreateWindow`, so it must not need one: text measures
324
+ * through `app.fonts`, which is the connection's, and the clamp was
325
+ * resolved during `createRoot`. That is the whole point — the window is
326
+ * *created* at its natural size rather than resized into it after mapping,
327
+ * so nothing is ever on screen at the wrong size.
328
+ *
329
+ * Leaves the tree laid out at a size that is nobody's arrangement, so it
330
+ * may only be called on a frame that goes on to lay out — `realize()`,
331
+ * which invalidates, and `_refit()`, which `flush()` only calls when it
332
+ * owes a layout pass anyway.
333
+ */
334
+ _measure() {
335
+ // Every number below — yoga's answers, the monitor rects, the window's
336
+ // live size — is device pixels, so the geometry props convert on entry
337
+ // and the rest of the function never thinks about units again.
338
+ const props = scaleWindowGeometry(this.props, this.scale);
339
+ const autoW = isAutoSize(props.width);
340
+ const autoH = isAutoSize(props.height);
341
+ const yoga = this.yoga;
342
+ // Where the window will open, for picking a monitor: next to its owner
343
+ // where it has one, and wherever the WM puts it otherwise.
344
+ const area = availableArea(this.app, screenOriginOf(props.transientFor));
345
+ // An `'auto'` cap never bounds the pass that resolves it: `maxWidth`
346
+ // there *is* the natural width, so letting it in would be the answer
347
+ // bounding the question.
348
+ const limit = (max, screen) =>
349
+ Math.min(
350
+ numericBound(max) ?? Infinity,
351
+ screen ?? Infinity,
352
+ MAX_WINDOW_EXTENT,
353
+ );
354
+ const availW = limit(props.maxWidth, area?.width);
355
+ const availH = limit(props.maxHeight, area?.height);
356
+ const hints = {};
357
+ if (!yoga) {
358
+ // Only reachable on a torn-down window, and a size still has to be a
359
+ // size: fall back to the space on offer rather than handing `'auto'`
360
+ // through to CreateWindow. Nothing left to measure a bound against.
361
+ return {
362
+ width: autoW
363
+ ? clampExtent(availW, numericBound(props.minWidth), availW)
364
+ : props.width,
365
+ height: autoH
366
+ ? clampExtent(availH, numericBound(props.minHeight), availH)
367
+ : props.height,
368
+ hints,
369
+ };
370
+ }
371
+
372
+ // A numeric floor applies to the natural size as it always has; an
373
+ // `'auto'` one is measured below. The height's needs a width to be
374
+ // measured for, and it can never exceed the natural height anyway —
375
+ // same width, every node at or below the size it settled at — so
376
+ // nothing is lost by clamping the height without it.
377
+ const minH = numericBound(props.minHeight);
378
+
379
+ // Also run for an axis that is not `'auto'` but whose cap is: a
380
+ // `maxWidth="auto"` on a window with a `width` still has to find out
381
+ // what the content wanted.
382
+ const needW = autoW || isContentBound(props.maxWidth);
383
+ const needH = autoH || isContentBound(props.maxHeight);
384
+ if (!needW && !needH) {
385
+ // Both sizes are the app's, so there is nothing to measure but the
386
+ // bounds — and nothing re-resolves `@width` blocks here: the styles
387
+ // are the ones the window's real size resolved on the last frame,
388
+ // which is the size the floors are wanted for.
389
+ if (isContentBound(props.minWidth)) {
390
+ hints.minWidth = clampBound(this._measureMinimum('width'), availW);
391
+ }
392
+ this._finishHeightFloor(
393
+ hints,
394
+ props,
395
+ this.window?.width ?? props.width,
396
+ availH,
397
+ );
398
+ return { width: props.width, height: props.height, hints };
399
+ }
400
+
401
+ const dir = this._rootDirection;
402
+ const measure = () => {
403
+ this._sweepLayoutHosts();
404
+ // The root carries whatever size the last flush() pinned on it — and
405
+ // whatever the floor pass below cleared — so this is re-stated per
406
+ // call rather than hoisted: clearing an axis is what makes yoga
407
+ // measure it rather than fill it.
408
+ yoga.setWidth(needW ? undefined : props.width);
409
+ yoga.setHeight(needH ? undefined : props.height);
410
+ let naturalW;
411
+ if (needW) {
412
+ yoga.calculateLayout(undefined, needH ? undefined : props.height, dir);
413
+ naturalW = clampExtent(yoga.getComputedWidth(), undefined, availW);
414
+ }
415
+ const width = autoW ? clampExtent(naturalW, minW, availW) : props.width;
416
+ // The height-for-width pass. Run even when only the width is auto: it
417
+ // is the layout the window is about to be created at, so leaving the
418
+ // tree holding the max-content one would hand `flush()` a stale
419
+ // arrangement.
420
+ yoga.calculateLayout(width, needH ? undefined : props.height, dir);
421
+ const naturalH = needH
422
+ ? clampExtent(yoga.getComputedHeight(), undefined, availH)
423
+ : undefined;
424
+ const height = autoH ? clampExtent(naturalH, minH, availH) : props.height;
425
+ return { width, height, naturalW, naturalH };
426
+ };
427
+
428
+ // `@width`/`@height` blocks and an auto size are mutually circular: the
429
+ // query wants a size the measurement has not produced yet. Broken the way
430
+ // CSS breaks the same cycle for container queries — measure against the
431
+ // space on offer, then re-resolve against the answer, and measure once
432
+ // more if that moved anything. **Once**: a second look settles the common
433
+ // case (a block that turns on below the width the content would have
434
+ // taken) and a third would only be chasing a layout that oscillates,
435
+ // which no size can satisfy.
436
+ this._resolveSizeQueries(
437
+ autoW ? availW : props.width,
438
+ autoH ? availH : props.height,
439
+ );
440
+
441
+ // The width floor, measured against the styles the pass below starts
442
+ // from and before it, because it is what the natural width is clamped
443
+ // into. Bounded by the same space the size is: a floor wider than the
444
+ // screen is a window that cannot be put on it, and a floor past
445
+ // `maxWidth` is a `WM_NORMAL_HINTS` that contradicts itself.
446
+ const minW = isContentBound(props.minWidth)
447
+ ? (hints.minWidth = clampBound(this._measureMinimum('width'), availW))
448
+ : props.minWidth;
449
+
450
+ let size = measure();
451
+ if (this._resolveSizeQueries(size.width, size.height)) size = measure();
452
+ // …and the container blocks against the arrangement that produced it,
453
+ // so the window is created at the size its content will actually take
454
+ if (this._containerQueryNodes.size !== 0) {
455
+ this._settleContainerQueries(() => {
456
+ size = measure();
457
+ });
458
+ }
459
+
460
+ // A cap the content decides is its natural size, never below a floor
461
+ // that was named as a number: `WM_NORMAL_HINTS` with a min above its own
462
+ // max is a struct no window manager can honour.
463
+ if (isContentBound(props.maxWidth)) {
464
+ hints.maxWidth = Math.max(size.naturalW, minW ?? 0);
465
+ }
466
+ if (isContentBound(props.maxHeight)) {
467
+ hints.maxHeight = Math.max(size.naturalH, minH ?? 0);
468
+ }
469
+ // Last, because it is a height *for a width*: the width the window is
470
+ // about to have where the width is still ours to choose, and the one it
471
+ // has where it is not.
472
+ const forWidth =
473
+ autoW && !this._userSized
474
+ ? size.width
475
+ : (this.window?.width ?? size.width);
476
+ this._finishHeightFloor(hints, props, forWidth, availH);
477
+ return { width: size.width, height: size.height, hints };
478
+ }
479
+
480
+ /** The `minHeight="auto"` floor, measured for the width just settled. */
481
+ _finishHeightFloor(hints, props, forWidth, availH) {
482
+ if (!isContentBound(props.minHeight)) return;
483
+ hints.minHeight = clampBound(
484
+ this._measureMinimum('height', forWidth),
485
+ availH,
486
+ );
487
+ if (isContentBound(props.maxHeight)) {
488
+ hints.maxHeight = Math.max(hints.maxHeight ?? 0, hints.minHeight);
489
+ }
490
+ }
491
+
492
+ /**
493
+ * Keep an `'auto'` window the size of its content while it still owns its
494
+ * own size. Called from `flush()` on any frame that lays out, which is
495
+ * every frame where the natural size could have moved.
496
+ *
497
+ * One rule covers both kinds of window, which is why it is a rule and not
498
+ * two behaviours: **auto tracks the content until something else sets the
499
+ * size.** A `<window>` grows as rows are added to it and stops the moment
500
+ * the user drags an edge — GTK's behaviour, and right for the same reason:
501
+ * the size is the app's opinion until it is the user's. Nothing can ever
502
+ * take a `<popup>`'s size over — it is override-redirect and has no
503
+ * resize handles — so a menu tracks its items for good.
504
+ *
505
+ * The result is applied through the window rather than through props: an
506
+ * auto size is not something React said, so nothing about it should read
507
+ * as a prop change or wait for one.
508
+ *
509
+ * A **bound** the content decides is not covered by that rule and outlives
510
+ * it: `minWidth="auto"` still means the same thing after the user has
511
+ * taken the size over — it is what stops them taking it *too far* — and it
512
+ * means it on a window with a `width` of its own, which never tracked
513
+ * anything. So the floor is re-measured on every frame that lays out, and
514
+ * the size only while it is still the window's to choose.
515
+ */
516
+ _refit() {
517
+ if (this.destroyed) return;
518
+ const wnd = this.window;
519
+ if (!wnd) return;
520
+ const props = this.props;
521
+ const tracking =
522
+ !this._userSized && (isAutoSize(props.width) || isAutoSize(props.height));
523
+ const bounded = CONTENT_BOUND_PROPS.some((key) =>
524
+ isContentBound(props[key]),
525
+ );
526
+ if (!tracking && !bounded) return;
527
+ const asked = this._requestedSize;
528
+ const next = this._measure();
529
+ this._sendSizeHints(props, next.hints);
530
+ if (!tracking) return;
531
+ if (asked && next.width === asked.width && next.height === asked.height) {
532
+ return;
533
+ }
534
+ this._requestedSize = { width: next.width, height: next.height };
535
+ // Asked for, not assumed. `window.width` stays what the server last said
536
+ // until the ConfigureNotify lands, and this frame lays out against that
537
+ // — the echo brings `needsLayout` and an unbounded repaint with it (see
538
+ // the 'resize' listener), which is the same one-frame settle a
539
+ // controlled `width` prop change has always had. Writing the new size
540
+ // onto the window here would be worse than the wait: ntk allocates the
541
+ // backing pixmap from the resize event, so a frame painted at a size the
542
+ // pixmap has not reached yet is a frame clipped to the old one.
543
+ if (typeof wnd.setState === 'function') {
544
+ wnd.setState({ width: next.width, height: next.height });
545
+ } else {
546
+ wnd.resize?.(next.width, next.height);
547
+ }
548
+ // A window that grew is a window whose *placement* moved with it, and
549
+ // the anchored ones have to be told: a completion list that gains a row
550
+ // near the bottom of the screen is one that now flips above the caret.
551
+ // From `next` rather than from the window, which is still the size the
552
+ // server last confirmed.
553
+ this._followAnchor({ width: next.width, height: next.height });
554
+ }
555
+
556
+ /** One layout pass at the window's size, with the content floors it
557
+ * needs: fresh ones when something changed them, the ones in hand during
558
+ * a live resize, none when nothing moved them. */
559
+ _layoutStep(width, height) {
560
+ // whether this step keeps the content floors: a live resize lays out
561
+ // against the ones in hand, and the layout hosts' children follow suit
562
+ let floors = true;
563
+ if (!this._floorsDirty && this._floorsWidth === width) {
564
+ this._layoutRoot(width, height);
565
+ } else if (this._deferContentFloors(width)) {
566
+ this._scheduleFloorsCatchUp();
567
+ this._layoutRoot(width, height);
568
+ floors = false;
569
+ } else {
570
+ this._applyContentFloors(width, height);
571
+ }
572
+ // The layout hosts' final calls, now the pass has given each its box —
573
+ // before anything reads a child's size (the container queries settle on
574
+ // what this leaves) — and, if an algorithm threw, the same step again
575
+ // with it turned off, so the frame it threw in is already the flexbox
576
+ // one.
577
+ if (this._layoutHosts.size !== 0) {
578
+ // A child's height floors are measured after the pass, at the width it
579
+ // was placed at, and off the pixel grid (`measuringExactly`), which
580
+ // leaves yoga holding a tree laid out under a grid it no longer has:
581
+ // the next pass would lay the whole of it out again, a scroll's
582
+ // included. One more pass now, on the grid, and the frame ends where
583
+ // every other frame does.
584
+ if (this._placeLayoutHosts(floors)) {
585
+ this._layoutRoot(width, height);
586
+ this._placeLayoutHosts(false);
587
+ }
588
+ if (this._abandonFailedHosts()) this._layoutStep(width, height);
589
+ }
590
+ }
591
+ }