@react-x11/components 0.12.0 → 0.14.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 (150) hide show
  1. package/dist/html/controls.d.ts +41 -5
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +85 -57
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +134 -20
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +545 -126
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +18 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +27 -0
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/parse.d.ts +47 -6
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +336 -30
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/style.d.ts +142 -15
  18. package/dist/html/css/style.d.ts.map +1 -1
  19. package/dist/html/css/style.js +563 -147
  20. package/dist/html/css/style.js.map +1 -1
  21. package/dist/html/css/transform.d.ts +85 -0
  22. package/dist/html/css/transform.d.ts.map +1 -0
  23. package/dist/html/css/transform.js +425 -0
  24. package/dist/html/css/transform.js.map +1 -0
  25. package/dist/html/css/ua.d.ts +11 -1
  26. package/dist/html/css/ua.d.ts.map +1 -1
  27. package/dist/html/css/ua.js +73 -9
  28. package/dist/html/css/ua.js.map +1 -1
  29. package/dist/html/css/values.d.ts +4 -0
  30. package/dist/html/css/values.d.ts.map +1 -1
  31. package/dist/html/css/values.js.map +1 -1
  32. package/dist/html/dom.d.ts.map +1 -1
  33. package/dist/html/dom.js +3 -1
  34. package/dist/html/dom.js.map +1 -1
  35. package/dist/html/fonts.d.ts +78 -4
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +373 -19
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/form.d.ts +200 -0
  40. package/dist/html/form.d.ts.map +1 -0
  41. package/dist/html/form.js +801 -0
  42. package/dist/html/form.js.map +1 -0
  43. package/dist/html/index.d.ts +12 -0
  44. package/dist/html/index.d.ts.map +1 -1
  45. package/dist/html/index.js +30 -301
  46. package/dist/html/index.js.map +1 -1
  47. package/dist/html/layout/axes.d.ts +14 -0
  48. package/dist/html/layout/axes.d.ts.map +1 -0
  49. package/dist/html/layout/axes.js +28 -0
  50. package/dist/html/layout/axes.js.map +1 -0
  51. package/dist/html/layout/block.d.ts +93 -12
  52. package/dist/html/layout/block.d.ts.map +1 -1
  53. package/dist/html/layout/block.js +393 -39
  54. package/dist/html/layout/block.js.map +1 -1
  55. package/dist/html/layout/boxes.d.ts +90 -2
  56. package/dist/html/layout/boxes.d.ts.map +1 -1
  57. package/dist/html/layout/boxes.js +74 -10
  58. package/dist/html/layout/boxes.js.map +1 -1
  59. package/dist/html/layout/cache.d.ts +1 -1
  60. package/dist/html/layout/cache.d.ts.map +1 -1
  61. package/dist/html/layout/cache.js +6 -0
  62. package/dist/html/layout/cache.js.map +1 -1
  63. package/dist/html/layout/css-grid.d.ts.map +1 -1
  64. package/dist/html/layout/css-grid.js +90 -18
  65. package/dist/html/layout/css-grid.js.map +1 -1
  66. package/dist/html/layout/flex.d.ts.map +1 -1
  67. package/dist/html/layout/flex.js +574 -84
  68. package/dist/html/layout/flex.js.map +1 -1
  69. package/dist/html/layout/inline.d.ts +3 -0
  70. package/dist/html/layout/inline.d.ts.map +1 -1
  71. package/dist/html/layout/inline.js +154 -54
  72. package/dist/html/layout/inline.js.map +1 -1
  73. package/dist/html/layout/multicol.d.ts +42 -0
  74. package/dist/html/layout/multicol.d.ts.map +1 -0
  75. package/dist/html/layout/multicol.js +897 -0
  76. package/dist/html/layout/multicol.js.map +1 -0
  77. package/dist/html/layout/shaping.d.ts +7 -0
  78. package/dist/html/layout/shaping.d.ts.map +1 -1
  79. package/dist/html/layout/shaping.js +25 -20
  80. package/dist/html/layout/shaping.js.map +1 -1
  81. package/dist/html/layout/table.d.ts.map +1 -1
  82. package/dist/html/layout/table.js +62 -18
  83. package/dist/html/layout/table.js.map +1 -1
  84. package/dist/html/node.d.ts +90 -15
  85. package/dist/html/node.d.ts.map +1 -1
  86. package/dist/html/node.js +771 -186
  87. package/dist/html/node.js.map +1 -1
  88. package/dist/html/paint.d.ts +101 -5
  89. package/dist/html/paint.d.ts.map +1 -1
  90. package/dist/html/paint.js +1502 -160
  91. package/dist/html/paint.js.map +1 -1
  92. package/dist/html/svg.d.ts +15 -2
  93. package/dist/html/svg.d.ts.map +1 -1
  94. package/dist/html/svg.js +249 -17
  95. package/dist/html/svg.js.map +1 -1
  96. package/dist/html/widgets.d.ts +29 -0
  97. package/dist/html/widgets.d.ts.map +1 -0
  98. package/dist/html/widgets.js +700 -0
  99. package/dist/html/widgets.js.map +1 -0
  100. package/dist/html/woff2.d.ts +14 -0
  101. package/dist/html/woff2.d.ts.map +1 -0
  102. package/dist/html/woff2.js +608 -0
  103. package/dist/html/woff2.js.map +1 -0
  104. package/dist/index.d.ts +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/maps/controller.d.ts +17 -1
  108. package/dist/maps/controller.d.ts.map +1 -1
  109. package/dist/maps/controller.js +27 -7
  110. package/dist/maps/controller.js.map +1 -1
  111. package/dist/richtext/node.d.ts +8 -1
  112. package/dist/richtext/node.d.ts.map +1 -1
  113. package/dist/richtext/node.js +5 -1
  114. package/dist/richtext/node.js.map +1 -1
  115. package/dist/richtext/runs.d.ts +25 -6
  116. package/dist/richtext/runs.d.ts.map +1 -1
  117. package/dist/richtext/runs.js +240 -24
  118. package/dist/richtext/runs.js.map +1 -1
  119. package/package.json +2 -2
  120. package/src/html/controls.ts +114 -51
  121. package/src/html/css/cascade.ts +614 -140
  122. package/src/html/css/color.ts +29 -0
  123. package/src/html/css/parse.ts +388 -33
  124. package/src/html/css/style.ts +688 -150
  125. package/src/html/css/transform.ts +459 -0
  126. package/src/html/css/ua.ts +77 -10
  127. package/src/html/css/values.ts +4 -0
  128. package/src/html/dom.ts +3 -1
  129. package/src/html/fonts.ts +417 -24
  130. package/src/html/form.ts +962 -0
  131. package/src/html/index.ts +49 -338
  132. package/src/html/layout/axes.ts +62 -0
  133. package/src/html/layout/block.ts +447 -37
  134. package/src/html/layout/boxes.ts +143 -14
  135. package/src/html/layout/cache.ts +6 -0
  136. package/src/html/layout/css-grid.ts +95 -22
  137. package/src/html/layout/flex.ts +675 -78
  138. package/src/html/layout/inline.ts +159 -55
  139. package/src/html/layout/multicol.ts +977 -0
  140. package/src/html/layout/shaping.ts +27 -18
  141. package/src/html/layout/table.ts +62 -19
  142. package/src/html/node.ts +798 -172
  143. package/src/html/paint.ts +1742 -241
  144. package/src/html/svg.ts +277 -19
  145. package/src/html/widgets.ts +821 -0
  146. package/src/html/woff2.ts +612 -0
  147. package/src/index.ts +1 -0
  148. package/src/maps/controller.ts +28 -7
  149. package/src/richtext/node.ts +13 -2
  150. package/src/richtext/runs.ts +313 -35
@@ -20,13 +20,15 @@
20
20
  // independent Yoga pass inside the measure call. Not a nested Yoga node:
21
21
  // that would need the whole child subtree mirrored into Yoga's tree, and the
22
22
  // measure seam already answers the only question the outer pass asks. What
23
- // the leaf shape costs is stretch — a stretched item's box grows but its
24
- // contents are not re-laid at the stretched height.
23
+ // the leaf shape costs is a layout: an item the flex pass made taller or
24
+ // shorter than its content, which lays what is in it out by its height — a
25
+ // flex or grid container, or a box holding a percentage of a height — is
26
+ // laid out again at that height (`layoutItemAt`).
25
27
  import { Yoga, layoutLoaded } from 'react-x11/yoga';
26
28
  import { AUTO, gapOf, isPct, resolve, resolveOrNull } from '../css/values.js';
27
29
  import { scrolls } from '../css/style.js';
28
30
  import { Box, PAINT_ORDER, isBlank } from './boxes.js';
29
- import { FLEXED_HEIGHT, MIN_CONTENT_PROBE, clampHeight, clampWidth, contentSizedWidth, exactMinContent, intrinsicWidth, measureIntrinsicWidth, moveTo, percentBaseInside, percentHeightsIn, positionOutOfFlow, replacedRatio, transferredHeight, transferredWidth, heightFromWidth, widthFromHeight, resolveEdges, } from './block.js';
31
+ import { FLEXED_HEIGHT, MIN_CONTENT_PROBE, USED_HEIGHT, centreButton, clampHeight, clampWidth, contentSizedWidth, exactMinContent, intrinsicWidth, measureIntrinsicWidth, moveTo, percentBaseInside, percentHeightsIn, positionOutOfFlow, replacedRatio, transferredHeight, transferredWidth, heightFromWidth, widthFromHeight, resolveEdges, } from './block.js';
30
32
  import { layoutGrid } from './css-grid.js';
31
33
  import { firstBaselineIn } from './inline.js';
32
34
  // `react-x11/yoga` re-exports yoga's own declarations, so the node shape and
@@ -97,9 +99,14 @@ export function layoutFlex(box, ctx, contentWidth) {
97
99
  // percentage that resolves, or what `aspect-ratio` makes of the width —
98
100
  // less the padding and borders a `border-box` height holds, which put
99
101
  // `h-16 py-2 items-center` eight pixels low. Where it is not, the limits
100
- // on it, so a column `min-h-screen` gives its `flex-1` the rest.
102
+ // on it, so a column `min-h-screen` gives its `flex-1` the rest — or the
103
+ // height the column it is an item of flexed it to (`USED_HEIGHT`), which
104
+ // its items are laid out in and take no percentage of.
101
105
  const definite = percentBaseInside(box);
102
- const height = Number.isFinite(definite) ? definite : null;
106
+ const own = Number.isFinite(definite);
107
+ const height = USED_HEIGHT.get(box) ?? (own ? definite : null);
108
+ // whether its content may come to more than it is tall
109
+ let capped = height !== null;
103
110
  if (height !== null)
104
111
  root.setHeight(height);
105
112
  else {
@@ -109,8 +116,10 @@ export function layoutFlex(box, ctx, contentWidth) {
109
116
  if (min > 0)
110
117
  root.setMinHeight(min);
111
118
  const max = clampHeight(box, Infinity) - extra;
112
- if (Number.isFinite(max))
119
+ if (Number.isFinite(max)) {
113
120
  root.setMaxHeight(Math.max(0, max));
121
+ capped = true;
122
+ }
114
123
  }
115
124
  const flowing = [];
116
125
  let reordered = false;
@@ -138,21 +147,59 @@ export function layoutFlex(box, ctx, contentWidth) {
138
147
  for (const child of flowing) {
139
148
  const node = Y.Node.create(flexConfig());
140
149
  resolveEdges(child, contentWidth);
141
- const laid = { width: NaN, height: NaN, set: NaN, stretch: NaN };
142
- applyItem(node, child, ctx, contentWidth, laid, height !== null);
150
+ const laid = {
151
+ width: NaN,
152
+ height: NaN,
153
+ tall: NaN,
154
+ set: NaN,
155
+ stretch: NaN,
156
+ main: NaN,
157
+ least: NaN,
158
+ held: NaN,
159
+ auto: false,
160
+ };
161
+ applyItem(node, child, ctx, contentWidth, laid, height !== null, own);
143
162
  root.insertChild(node, items.length);
144
163
  items.push({ box: child, node, laid });
145
164
  }
146
165
  const direction = box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
147
166
  const row = box.style.flexDirection.startsWith('row');
148
- const calculate = () => root.calculateLayout(bounded ? contentWidth : Number.NaN, height ?? Number.NaN, direction);
167
+ // frozen at its least size on the one line of a box that does not
168
+ // wrap, by a basis where Yoga takes one along the main axis
169
+ // (`applyItem`'s `setBasis`)
170
+ const hold = {
171
+ frozen: box.style.flexWrap === 'nowrap',
172
+ basis: row ? bounded : height !== null,
173
+ };
174
+ const ask = () => root.calculateLayout(bounded ? contentWidth : Number.NaN, height ?? Number.NaN, direction);
175
+ // the one line of a box that does not wrap may be short of room, where
176
+ // the box has a size along it that its items do not make: and Yoga's
177
+ // answer for a line every item of which its minimum stops is put right
178
+ // (`stoppedLine`)
179
+ const short = hold.frozen && (row ? bounded : capped);
180
+ // each item's size along the main axis, read the once a layout (`Laid`)
181
+ const read = () => {
182
+ for (const { node, laid } of items) {
183
+ laid.main = row ? node.getComputedWidth() : node.getComputedHeight();
184
+ }
185
+ };
186
+ const calculate = () => {
187
+ ask();
188
+ read();
189
+ if (!short)
190
+ return;
191
+ const room = row ? contentWidth : (height ?? root.getComputedHeight());
192
+ const gap = row ? columnGap : rowGap;
193
+ if (stoppedLine(items, row, room, gap, hold, ask))
194
+ read();
195
+ };
149
196
  ctx.flexDepth = depth + 1;
150
197
  try {
151
198
  calculate();
152
199
  // an item Yoga shrank under what its content comes to is kept to it,
153
200
  // and the row is laid out again (`autoMinimums`) — which may shrink
154
201
  // another under its own, a few times over at most
155
- for (let pass = 0; pass < 4 && autoMinimums(items, row, ctx, contentWidth); pass += 1) {
202
+ for (let pass = 0; pass < 4 && autoMinimums(items, row, ctx, contentWidth, hold); pass += 1) {
156
203
  calculate();
157
204
  }
158
205
  }
@@ -163,7 +210,7 @@ export function layoutFlex(box, ctx, contentWidth) {
163
210
  // one pass of its own, which drops a margin (`lineMarginFix`)
164
211
  const baselines = box.style.alignItems === 'baseline' ||
165
212
  items.some(({ box: child }) => child.style.alignSelf === 'baseline');
166
- let bottom = placeItems(box, ctx, items, row, height, box.style.flexWrap === 'wrap' || baselines);
213
+ let bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap' || baselines, contentWidth);
167
214
  // Items aligned by their baselines, which Yoga cannot see, are aligned
168
215
  // with their baselines known (`baselineLines`)
169
216
  // by the flex layout again, set at their lines' starts with the margins
@@ -191,7 +238,7 @@ export function layoutFlex(box, ctx, contentWidth) {
191
238
  finally {
192
239
  ctx.flexDepth = depth;
193
240
  }
194
- bottom = placeItems(box, ctx, items, row, height, box.style.flexWrap === 'wrap', started);
241
+ bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap', contentWidth, started);
195
242
  }
196
243
  // A replaced item with a ratio that is stretched across a line of a
197
244
  // definite size takes its flex base size from the stretched one through
@@ -209,7 +256,7 @@ export function layoutFlex(box, ctx, contentWidth) {
209
256
  finally {
210
257
  ctx.flexDepth = depth;
211
258
  }
212
- bottom = placeItems(box, ctx, items, row, height, box.style.flexWrap === 'wrap' || baselines);
259
+ bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap' || baselines, contentWidth);
213
260
  }
214
261
  const contentHeight = root.getComputedHeight();
215
262
  root.freeRecursive();
@@ -219,7 +266,7 @@ export function layoutFlex(box, ctx, contentWidth) {
219
266
  * base size the ratio makes of the stretched size; whether any changed. */
220
267
  function keepRatios(container, items, row) {
221
268
  let changed = false;
222
- for (const { box, node } of items) {
269
+ for (const { box, node, laid } of items) {
223
270
  if (box.kind !== 'replaced' || !stretches(box, container.style, row)) {
224
271
  continue;
225
272
  }
@@ -234,10 +281,17 @@ function keepRatios(container, items, row) {
234
281
  box.horizontalExtra
235
282
  : Math.max(0, node.getComputedWidth() - box.horizontalExtra) / ratio +
236
283
  box.verticalExtra;
237
- const main = row ? node.getComputedWidth() : node.getComputedHeight();
238
- if (Math.abs(basis - main) < 0.5)
284
+ if (Math.abs(basis - laid.main) < 0.5)
239
285
  continue;
240
286
  node.setFlexBasis(basis);
287
+ // one held at its least size (`holdAt`) flexes from the new basis, no
288
+ // further down than that
289
+ if (!Number.isNaN(laid.held)) {
290
+ laid.held = NaN;
291
+ laid.auto = false;
292
+ node.setFlexGrow(style.flexGrow);
293
+ node.setFlexShrink(style.flexShrink);
294
+ }
241
295
  changed = true;
242
296
  }
243
297
  return changed;
@@ -357,9 +411,13 @@ function orderOf(box) {
357
411
  /** Lay each item out where the flex layout put it, at the size it gave it,
358
412
  * and answer where the lowest ends. `started` are items set at their
359
413
  * lines' starts whatever their own alignment says (`baselineLines`). */
360
- function placeItems(box, ctx, items, row, height,
414
+ function placeItems(box, ctx, items, row,
415
+ /** Whether the flex box has a definite height. */
416
+ own,
361
417
  /** Whether Yoga aligned the lines in its pass that drops a margin. */
362
- lined, started) {
418
+ lined,
419
+ /** The flex box's content width: its items' containing block's. */
420
+ contentWidth, started) {
363
421
  let bottom = 0;
364
422
  for (const { box: child, node, laid } of items) {
365
423
  const across = lined
@@ -367,8 +425,8 @@ lined, started) {
367
425
  : 0;
368
426
  const left = box.contentX + node.getComputedLeft() + (row ? 0 : across);
369
427
  const top = box.contentY + node.getComputedTop() + (row ? across : 0);
370
- const width = meant(node.getComputedWidth(), laid.set, laid.stretch, laid.width);
371
- const itemHeight = node.getComputedHeight();
428
+ const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
429
+ const itemHeight = row ? node.getComputedHeight() : laid.main;
372
430
  // The item is laid out again at the width the flex pass settled on: the
373
431
  // measure function answered a question, and the answer is not a layout —
374
432
  // its line breaks were computed against a width that may have changed
@@ -378,22 +436,30 @@ lined, started) {
378
436
  // 9.8): stretched across a row's line, or flexed in a column of a
379
437
  // height of its own. What is in it takes its percentages of that —
380
438
  // the `h-full` in a stretched sidebar — where they had nothing to be
381
- // of, and the item is laid out again for them.
439
+ // of, and the item is laid out again for them; and so is one that is a
440
+ // flex or grid container itself, whose own items are laid out in that
441
+ // height (9.4, step 11: "redo layout for its contents, treating this
442
+ // used size as its definite cross size").
382
443
  const definite = row
383
444
  ? stretches(child, box.style)
384
445
  ? itemHeight
385
446
  : null
386
- : height !== null
447
+ : own
387
448
  ? itemHeight
388
449
  : null;
389
- layoutItemAt(child, ctx, left, top, width, itemHeight, laid, definite, !row);
390
- bottom = Math.max(bottom, top + child.height);
450
+ layoutItemAt(child, ctx, left, top, width, itemHeight, laid, definite, contentWidth, !row);
451
+ // to the end of its margin box, as Yoga measured the box: a negative
452
+ // margin takes the item's end back in, which Codex's search field
453
+ // hangs a pixel over its form's border with, top and bottom
454
+ bottom = Math.max(bottom, top + child.height + child.marginBottom);
391
455
  }
392
456
  return bottom;
393
457
  }
394
458
  function layoutItemAt(box, ctx, x, y, width, height, laid,
395
459
  /** Its border box's height where the flex layout made it definite. */
396
460
  definite,
461
+ /** The flex box's content width, the item's containing block's. */
462
+ containing,
397
463
  /** Whether its height is the one the flex layout gave it, in a column,
398
464
  * whatever its own says: flexed, it is shrunk as well as grown. */
399
465
  column = false) {
@@ -406,32 +472,65 @@ column = false) {
406
472
  box.height = height;
407
473
  return;
408
474
  }
409
- const inner = definite === null || !percentHeightsIn(box)
410
- ? null
411
- : Math.max(0, definite - box.verticalExtra);
412
- if (inner !== null && !Object.is(inner, percentBaseInside(box))) {
413
- FLEXED_HEIGHT.set(box, inner);
414
- try {
415
- ctx.layoutSubtree(box, width);
475
+ const sameWidth = Math.abs(laid.width - width) <= 0.01;
476
+ // The content height the item is laid out at, where the flex layout gave
477
+ // it one that is not its own already: NaN where its content says how
478
+ // tall it is.
479
+ let tall = NaN;
480
+ // A flex or grid container shares its height out among its items: an
481
+ // `auto` margin takes what is free of it (8.1), a `flex: 1` grows into it
482
+ // (9.7), its lines are as tall as it (9.4), its `fr` rows fill it. So it
483
+ // is laid out in the height the flex layout gave it, definite or not —
484
+ // the one a column with no height of its own flexed it to is its used
485
+ // size all the same (9.7), and `min-h-screen flex flex-col` around a
486
+ // `flex-1 flex items-center` centres what is in that. At its content's
487
+ // own height there is nothing to share, and the layout it has is that
488
+ // one: the tallest card of a row is laid out the once.
489
+ const given = definite ?? (column ? height : null);
490
+ const shares = box.kind === 'flex' &&
491
+ given !== null &&
492
+ !(Math.abs(naturalHeight(box, ctx, width, containing, laid) - given) <= 0.01);
493
+ if (given !== null) {
494
+ const inner = Math.max(0, given - box.verticalExtra);
495
+ if (Object.is(inner, percentBaseInside(box))) {
496
+ // the height it has of its own
416
497
  }
417
- finally {
418
- FLEXED_HEIGHT.delete(box);
498
+ else if (shares || (definite !== null && percentHeightsIn(box))) {
499
+ tall = inner;
419
500
  }
420
501
  }
421
502
  // `measureBox` lays the box out at (0, 0); re-running it at the final width
422
503
  // and then moving it is one pass, not two, because the second call is the
423
- // one whose result is kept — or no pass, where the last measure was at
424
- // this width. An item Yoga never measured has none, NaN, and is laid out.
425
- else if (Math.abs(laid.width - width) <= 0.01)
504
+ // one whose result is kept — or no pass, where the last layout was at
505
+ // this width and this height. An item Yoga never measured has none, NaN,
506
+ // and is laid out.
507
+ if (sameWidth && Object.is(laid.tall, tall))
426
508
  box.height = laid.height;
427
- else
428
- ctx.layoutSubtree(box, width);
509
+ else if (Number.isNaN(tall)) {
510
+ layoutNaturally(box, ctx, width, containing, laid);
511
+ }
512
+ else {
513
+ // what is in it takes its percentages of a definite height alone (9.8)
514
+ const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
515
+ heights.set(box, tall);
516
+ try {
517
+ ctx.layoutSubtree(box, width, containing);
518
+ }
519
+ finally {
520
+ heights.delete(box);
521
+ }
522
+ laid.width = box.width;
523
+ laid.height = box.height;
524
+ laid.tall = tall;
525
+ }
429
526
  // A stretched item is taller than its content, and the box has to say so
430
527
  // or its background stops short of the row — and it is no taller than its
431
528
  // line where its content is, or than what its ratio makes of its width:
432
529
  // its height is the line's (CSS Flexbox 9.4, step 11).
433
530
  if (height > box.height || column || definite !== null)
434
531
  box.height = height;
532
+ // and a button's content is centred in the height it came to
533
+ centreButton(box);
435
534
  moveTo(box, x, y);
436
535
  }
437
536
  // What a new Yoga node already is is not said again. A call into the
@@ -458,8 +557,11 @@ function applyContainer(node, style) {
458
557
  node.setAlignContent(lines);
459
558
  }
460
559
  function applyItem(node, box, ctx, containingWidth, laid,
461
- /** Whether the flex box has a height of its own. */
462
- tall) {
560
+ /** Whether the flex box has a height its items are laid out in. */
561
+ tall,
562
+ /** Whether that height is definite, and a percentage may be of it: one a
563
+ * column flexed the box to is not (`USED_HEIGHT`). */
564
+ own) {
463
565
  const style = box.style;
464
566
  laid.stretch = containingWidth - box.marginLeft - box.marginRight;
465
567
  // an `auto` margin takes the free space on its side, which is how
@@ -489,11 +591,20 @@ tall) {
489
591
  if (style.width !== AUTO) {
490
592
  laid.set = setLength(node, true, style.width, containingWidth, across);
491
593
  }
492
- if (style.height !== AUTO)
594
+ // a percentage of a height that is not definite is `auto` (CSS 2.1 10.5),
595
+ // which Yoga makes of one where the flex box has no height at all
596
+ if (style.height !== AUTO && (own || !tall || !isPct(style.height))) {
493
597
  setLength(node, false, style.height, NaN, down);
598
+ }
599
+ // the least it may be along each axis, its border box's, as Yoga is
600
+ // told it
601
+ let leastWidth = NaN;
602
+ let leastHeight = NaN;
494
603
  const minWidth = resolveOrNull(style.minWidth, containingWidth);
495
- if (minWidth !== null)
496
- node.setMinWidth(minWidth + across);
604
+ if (minWidth !== null) {
605
+ leastWidth = minWidth + across;
606
+ node.setMinWidth(leastWidth);
607
+ }
497
608
  if (style.maxWidth !== 'none') {
498
609
  const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
499
610
  if (maxWidth !== null)
@@ -509,20 +620,25 @@ tall) {
509
620
  laid.set = size(style.widthKeyword);
510
621
  node.setWidth(laid.set);
511
622
  }
512
- if (style.minWidthKeyword)
513
- node.setMinWidth(size(style.minWidthKeyword));
623
+ if (style.minWidthKeyword) {
624
+ leastWidth = size(style.minWidthKeyword);
625
+ node.setMinWidth(leastWidth);
626
+ }
514
627
  if (style.maxWidthKeyword)
515
628
  node.setMaxWidth(size(style.maxWidthKeyword));
516
629
  }
517
630
  const minHeight = resolveOrNull(style.minHeight, NaN);
518
- if (minHeight !== null)
519
- node.setMinHeight(minHeight + down);
631
+ if (minHeight !== null) {
632
+ leastHeight = minHeight + down;
633
+ node.setMinHeight(leastHeight);
634
+ }
520
635
  if (style.maxHeight !== 'none') {
521
636
  const maxHeight = resolveOrNull(style.maxHeight, NaN);
522
637
  if (maxHeight !== null)
523
638
  node.setMaxHeight(maxHeight + down);
524
639
  }
525
640
  const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
641
+ laid.least = row ? leastWidth : leastHeight;
526
642
  // Yoga takes a length for a basis only where the flex box's main size is
527
643
  // definite, and else the item's own size along it: down a column of no
528
644
  // height of its own it read `flex: 0 0 3rem` as the item's height, or
@@ -558,6 +674,10 @@ tall) {
558
674
  else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
559
675
  // Yoga's own
560
676
  }
677
+ else if (isPct(style.flexBasis) && !row && tall && !own) {
678
+ // a percentage of a main size that is not definite is `content` (CSS
679
+ // Flexbox 7.2.3): Yoga's own, down a column
680
+ }
561
681
  else if (isPct(style.flexBasis)) {
562
682
  // a percentage of the main size, which is known across a row
563
683
  const basis = style.flexBasis;
@@ -653,7 +773,12 @@ tall) {
653
773
  const width = meant(w, laid.set - extra, laid.stretch - extra);
654
774
  const given = hm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(h);
655
775
  const exact = wm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width);
656
- const through = widthThrough && given && !exact ? widthThrough(h) : null;
776
+ // the height it was flexed to, that is: one its automatic minimum
777
+ // holds it at (`holdAt`) is its content's, which a replaced element's
778
+ // width follows through its ratio and no other's does (CSS Sizing 4,
779
+ // 5.1)
780
+ const flexed = box.kind === 'replaced' || !laid.auto;
781
+ const through = widthThrough && given && !exact && flexed ? widthThrough(h) : null;
657
782
  // along a row, a replaced element is its own width wherever there is
658
783
  // room for less — its flex base size is not fitted to the room (9.2)
659
784
  // — and across a column it is fitted, as any item is
@@ -670,10 +795,8 @@ tall) {
670
795
  }
671
796
  let answer = answers.get(inner);
672
797
  if (answer === undefined) {
673
- answer = measureBox(box, ctx, inner);
798
+ answer = measureBox(box, ctx, inner, containingWidth, laid);
674
799
  answers.set(inner, answer);
675
- laid.width = box.width;
676
- laid.height = box.height;
677
800
  }
678
801
  return answer;
679
802
  });
@@ -749,13 +872,174 @@ extra) {
749
872
  * 9.2, `flex-basis: auto`), rather than as wide as the row; asked for one
750
873
  * exactly, it is that wide. Its height is its content's at that width.
751
874
  */
752
- function measureBox(box, ctx, inner) {
753
- ctx.layoutSubtree(box, inner + box.horizontalExtra);
875
+ function measureBox(box, ctx, inner,
876
+ /** The flex box's content width, the item's containing block's. */
877
+ containing, laid) {
878
+ // its padding as Yoga has it, a percentage of the flex box's width: a
879
+ // probe of its content's width leaves a percentage at none
880
+ resolveEdges(box, containing);
881
+ const width = inner + box.horizontalExtra;
882
+ // its height at this width is known, where a layout since has left it
883
+ // another: the answer is the height, and the layout `layoutItemAt`'s
884
+ const kept = keptNatural(box, ctx, width, containing);
885
+ if (kept !== null && kept.serial !== box.layoutSerial) {
886
+ return {
887
+ width: inner,
888
+ height: Math.max(0, kept.height - box.verticalExtra),
889
+ };
890
+ }
891
+ layoutNaturally(box, ctx, width, containing, laid);
754
892
  return {
755
893
  width: inner,
756
894
  height: Math.max(0, box.height - box.verticalExtra),
757
895
  };
758
896
  }
897
+ /** An item's height as its content's own makes it at a border-box width,
898
+ * where this pass has laid it out so (`layoutNaturally`), and else NaN. */
899
+ function naturalHeight(box, ctx, width, containing, laid) {
900
+ if (Math.abs(laid.width - width) <= 0.01 && Number.isNaN(laid.tall)) {
901
+ return laid.height;
902
+ }
903
+ return keptNatural(box, ctx, width, containing)?.height ?? NaN;
904
+ }
905
+ /**
906
+ * Give an item the layout its content's own height makes of it at a
907
+ * border-box width, and note it (`Laid`): the one this pass has made of it
908
+ * there already, where it still has it.
909
+ *
910
+ * A flex box laid out a second time — at the height a stretch gave it, its
911
+ * own flex layout made anew (`layoutItemAt`) — asks of each item what it
912
+ * asked the first time, and the item's layout at a width is what it was.
913
+ * Kept for the pass, a card laid out again for its height lays nothing in
914
+ * it out that is where it was; made again at every layout of the box
915
+ * around it, a card in a card in a card laid its innermost out twice a
916
+ * level.
917
+ */
918
+ function layoutNaturally(box, ctx, width,
919
+ /** The flex box's content width, the item's containing block's. */
920
+ containing, laid) {
921
+ const kept = keptNatural(box, ctx, width, containing);
922
+ if (kept !== null && kept.serial === box.layoutSerial) {
923
+ // put back, where the flex layout stretched it
924
+ box.height = kept.height;
925
+ }
926
+ else {
927
+ const at = ctx.positioned.length;
928
+ ctx.layoutSubtree(box, width, containing);
929
+ // a replaced box is sized, not laid out, and has no layout to count
930
+ if (box.kind !== 'replaced') {
931
+ NATURAL.set(box, {
932
+ ctx,
933
+ serial: box.layoutSerial,
934
+ width,
935
+ containing: readsContaining(box) ? containing : NaN,
936
+ base: box.percentHeightBase,
937
+ edges: edgesOf(box),
938
+ height: box.height,
939
+ queued: ctx.positioned.length > at ? { at, first: ctx.positioned[at] } : null,
940
+ drawn: NaN,
941
+ content: NaN,
942
+ });
943
+ }
944
+ }
945
+ laid.width = box.width;
946
+ laid.height = box.height;
947
+ laid.tall = NaN;
948
+ }
949
+ /**
950
+ * The layout this pass made of an item at its content's own height
951
+ * (`layoutNaturally`), where one at `width` now would be the same: the same
952
+ * width, the same edges — a percentage among them resolved, as the layout
953
+ * leaves them — the same containing block's width where a limit on its own
954
+ * is a percentage of it, and the same height for its percentages, where its
955
+ * own style reads that at all, which the flex box around it coming to a
956
+ * definite height changes for every item in it. Its `height` is the item's
957
+ * there whatever has laid it out since, and the layout is the one the box
958
+ * has where nothing has (`Box.layoutSerial`).
959
+ */
960
+ function keptNatural(box, ctx, width, containing) {
961
+ const kept = NATURAL.get(box);
962
+ if (kept === undefined || kept.ctx !== ctx || kept.width !== width) {
963
+ return null;
964
+ }
965
+ if (!Number.isNaN(kept.containing) && kept.containing !== containing) {
966
+ return null;
967
+ }
968
+ if (!Object.is(kept.base, box.percentHeightBase) && readsPercentBase(box)) {
969
+ return null;
970
+ }
971
+ // the boxes in it that are positioned last are still waiting to be: a
972
+ // `line-clamp: auto` box laid out a second time forgets the ones its
973
+ // first layout found
974
+ if (kept.queued && ctx.positioned[kept.queued.at] !== kept.queued.first) {
975
+ return null;
976
+ }
977
+ const edges = kept.edges;
978
+ return edges[0] === box.marginTop &&
979
+ edges[1] === box.marginRight &&
980
+ edges[2] === box.marginBottom &&
981
+ edges[3] === box.marginLeft &&
982
+ edges[4] === box.borderTop &&
983
+ edges[5] === box.borderRight &&
984
+ edges[6] === box.borderBottom &&
985
+ edges[7] === box.borderLeft &&
986
+ edges[8] === box.padTop &&
987
+ edges[9] === box.padRight &&
988
+ edges[10] === box.padBottom &&
989
+ edges[11] === box.padLeft
990
+ ? kept
991
+ : null;
992
+ }
993
+ const NATURAL = new WeakMap();
994
+ /** Note what was read from the layout an item has, where that is the one
995
+ * its content's own height makes of it at `width`. */
996
+ function remember(box, ctx, width, containing, field, value) {
997
+ const made = keptNatural(box, ctx, width, containing);
998
+ if (made !== null && made.serial === box.layoutSerial)
999
+ made[field] = value;
1000
+ }
1001
+ /** A box's edges, which a layout resolves again against the width it is
1002
+ * given: margins, borders, padding, each from the top round. */
1003
+ function edgesOf(box) {
1004
+ return [
1005
+ box.marginTop,
1006
+ box.marginRight,
1007
+ box.marginBottom,
1008
+ box.marginLeft,
1009
+ box.borderTop,
1010
+ box.borderRight,
1011
+ box.borderBottom,
1012
+ box.borderLeft,
1013
+ box.padTop,
1014
+ box.padRight,
1015
+ box.padBottom,
1016
+ box.padLeft,
1017
+ ];
1018
+ }
1019
+ /** Whether the width a box is laid out at is held to a percentage of its
1020
+ * containing block's: a least or greatest width that is one. */
1021
+ function readsContaining(box) {
1022
+ const style = box.style;
1023
+ return (isPct(style.minWidth) ||
1024
+ (style.maxWidth !== 'none' && isPct(style.maxWidth)));
1025
+ }
1026
+ /** Whether a box's own layout reads the height its percentages are of: a
1027
+ * height, a least or greatest one, or an offset that is a percentage, a
1028
+ * height that is a keyword — `stretch` fills it — or an anonymous box,
1029
+ * which hands it on to what is in it (`percentBaseInside`). */
1030
+ function readsPercentBase(box) {
1031
+ if (!box.el && !box.pseudo)
1032
+ return true;
1033
+ const style = box.style;
1034
+ return (isPct(style.height) ||
1035
+ isPct(style.minHeight) ||
1036
+ (style.maxHeight !== 'none' && isPct(style.maxHeight)) ||
1037
+ isPct(style.top) ||
1038
+ isPct(style.bottom) ||
1039
+ style.heightKeyword !== null ||
1040
+ style.minHeightKeyword !== null ||
1041
+ style.maxHeightKeyword !== null);
1042
+ }
759
1043
  /** The width an item's content is measured at: the width Yoga gives it
760
1044
  * exactly, or its max-content width, no wider than any it may take up to
761
1045
  * (CSS Flexbox 9.2). */
@@ -786,7 +1070,7 @@ function innerWidth(width, mode, maxContent) {
786
1070
  * (`Laid`), so a row with room pays for nothing. True where some item
787
1071
  * was given a minimum and the row has to be laid out again.
788
1072
  */
789
- function autoMinimums(items, row, ctx, containingWidth) {
1073
+ function autoMinimums(items, row, ctx, containingWidth, hold) {
790
1074
  let changed = false;
791
1075
  for (const { box, node, laid } of items) {
792
1076
  if (box.kind === 'text' || box.kind === 'break')
@@ -798,7 +1082,7 @@ function autoMinimums(items, row, ctx, containingWidth) {
798
1082
  if (!asked && scrolls(style)) {
799
1083
  continue;
800
1084
  }
801
- const width = meant(node.getComputedWidth(), laid.set, laid.stretch, laid.width);
1085
+ const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
802
1086
  // a replaced item's content along a row is its natural width
803
1087
  if (row && box.kind === 'replaced') {
804
1088
  if (style.minWidth !== AUTO || style.minWidthKeyword)
@@ -806,7 +1090,7 @@ function autoMinimums(items, row, ctx, containingWidth) {
806
1090
  const least = replacedMinimum(box, containingWidth);
807
1091
  if (least === null || width >= least - 0.01)
808
1092
  continue;
809
- node.setMinWidth(least);
1093
+ holdAt(node, laid, row, least, hold);
810
1094
  changed = true;
811
1095
  continue;
812
1096
  }
@@ -833,7 +1117,7 @@ function autoMinimums(items, row, ctx, containingWidth) {
833
1117
  }
834
1118
  if (width >= least - 0.01)
835
1119
  continue;
836
- node.setMinWidth(least);
1120
+ holdAt(node, laid, row, least, hold);
837
1121
  changed = true;
838
1122
  continue;
839
1123
  }
@@ -862,9 +1146,9 @@ function autoMinimums(items, row, ctx, containingWidth) {
862
1146
  if (width >= least - 0.01)
863
1147
  continue;
864
1148
  least = Math.min(least, minContentOf(box, ctx, laid));
865
- if (node.getComputedWidth() >= least - 0.01)
1149
+ if (laid.main >= least - 0.01)
866
1150
  continue;
867
- node.setMinWidth(least);
1151
+ holdAt(node, laid, row, least, hold);
868
1152
  changed = true;
869
1153
  continue;
870
1154
  }
@@ -876,17 +1160,37 @@ function autoMinimums(items, row, ctx, containingWidth) {
876
1160
  transferredWidth(box, widest + box.horizontalExtra) <= width + 0.5) {
877
1161
  continue;
878
1162
  }
879
- if (!(Math.abs(laid.width - width) <= 0.01)) {
880
- ctx.layoutSubtree(box, width);
881
- laid.width = box.width;
882
- laid.height = box.height;
1163
+ // What its content's own height makes of it at this width — how wide
1164
+ // that drew, along a row, and how far down, along a column — is read
1165
+ // from that layout, which the item is given where it has another: or
1166
+ // is what this pass read from it before, where the flex box is laid
1167
+ // out a second time (`layoutNaturally`)
1168
+ const kept = keptNatural(box, ctx, width, containingWidth);
1169
+ const read = kept === null ? NaN : row ? kept.drawn : kept.content;
1170
+ if (Number.isNaN(read) && !(Math.abs(laid.width - width) <= 0.01)) {
1171
+ layoutNaturally(box, ctx, width, containingWidth, laid);
883
1172
  }
1173
+ const height = Number.isNaN(read) ? laid.height : kept.height;
884
1174
  let least;
885
1175
  if (row) {
886
- // or than what it drew at the width it has: nothing overflows it
887
- const drawn = intrinsicWidth(box) + box.horizontalExtra;
888
- if (transferredWidth(box, drawn) <= width + 0.5)
1176
+ // or than what it drew at the width it has: nothing overflows it —
1177
+ // unless a line in it fits for being cut short by `text-overflow`,
1178
+ // which is how the line is drawn and no part of how wide it is: a
1179
+ // button whose label is a `truncate` span is no narrower than the
1180
+ // label, where it was shrunk and the label cut. No width, then, that
1181
+ // it drew at, which is what is kept of it
1182
+ let drawn = read;
1183
+ if (Number.isNaN(drawn)) {
1184
+ const seen = { cut: false };
1185
+ drawn = intrinsicWidth(box, seen) + box.horizontalExtra;
1186
+ if (seen.cut)
1187
+ drawn = Infinity;
1188
+ remember(box, ctx, width, containingWidth, 'drawn', drawn);
1189
+ }
1190
+ if (Number.isFinite(drawn) &&
1191
+ transferredWidth(box, drawn) <= width + 0.5) {
889
1192
  continue;
1193
+ }
890
1194
  // within what a ratio makes of its least and greatest heights (4.5's
891
1195
  // content size suggestion)
892
1196
  least = transferredWidth(box, minContentOf(box, ctx, laid));
@@ -896,15 +1200,18 @@ function autoMinimums(items, row, ctx, containingWidth) {
896
1200
  if (most !== null)
897
1201
  least = Math.min(least, most + extra);
898
1202
  }
899
- if (node.getComputedWidth() >= least - 0.01)
1203
+ if (laid.main >= least - 0.01)
900
1204
  continue;
901
- node.setMinWidth(least);
1205
+ holdAt(node, laid, row, least, hold);
902
1206
  }
903
1207
  else {
904
1208
  // its height, or where it has one of its own and its content comes to
905
1209
  // less, its content's: the lesser of the two (4.5)
906
- let content;
907
- if (box.kind === 'replaced')
1210
+ let content = read;
1211
+ if (!Number.isNaN(content)) {
1212
+ // read before
1213
+ }
1214
+ else if (box.kind === 'replaced')
908
1215
  content = laid.height;
909
1216
  else if (style.height !== AUTO && percentHeightsIn(box)) {
910
1217
  // what its content comes to where it has no height to take
@@ -912,43 +1219,226 @@ function autoMinimums(items, row, ctx, containingWidth) {
912
1219
  // apart, and the final pass lays it out again
913
1220
  FLEXED_HEIGHT.set(box, NaN);
914
1221
  try {
915
- ctx.layoutSubtree(box, width);
1222
+ ctx.layoutSubtree(box, width, containingWidth);
916
1223
  }
917
1224
  finally {
918
1225
  FLEXED_HEIGHT.delete(box);
919
1226
  }
920
- content = contentBottom(box) + box.verticalExtra;
1227
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
921
1228
  laid.width = NaN;
922
1229
  }
923
- else
924
- content = contentBottom(box) + box.verticalExtra;
925
- // and a ratio's content is at least its width through the ratio,
926
- // height of its own or not (CSS Sizing 4, 5.1)
927
- const aspect = style.aspectRatio;
928
- if (aspect && box.kind !== 'replaced') {
929
- content = Math.max(content, style.boxSizing === 'border-box'
930
- ? box.width / aspect.ratio
931
- : box.contentWidth / aspect.ratio + box.verticalExtra);
1230
+ else {
1231
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1232
+ remember(box, ctx, width, containingWidth, 'content', content);
932
1233
  }
933
1234
  // within what the ratio makes of its least and greatest widths
934
1235
  if (box.kind !== 'replaced') {
935
1236
  content = transferredHeight(box, content, containingWidth);
936
1237
  }
937
- least = Math.min(laid.height, content);
1238
+ least = Math.min(height, content);
938
1239
  const extra = style.boxSizing === 'border-box' ? 0 : box.verticalExtra;
939
1240
  if (style.maxHeight !== 'none') {
940
1241
  const most = resolveOrNull(style.maxHeight, NaN);
941
1242
  if (most !== null)
942
1243
  least = Math.min(least, most + extra);
943
1244
  }
944
- if (node.getComputedHeight() >= least - 0.01)
1245
+ if (laid.main >= least - 0.01)
945
1246
  continue;
946
- node.setMinHeight(least);
1247
+ holdAt(node, laid, row, least, hold);
947
1248
  }
948
1249
  changed = true;
949
1250
  }
950
1251
  return changed;
951
1252
  }
1253
+ /** A laid-out item's content height, its border box's: no less than its
1254
+ * width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
1255
+ function ratioContent(box, content) {
1256
+ const aspect = box.style.aspectRatio;
1257
+ if (!aspect || box.kind === 'replaced')
1258
+ return content;
1259
+ return Math.max(content, box.style.boxSizing === 'border-box'
1260
+ ? box.width / aspect.ratio
1261
+ : box.contentWidth / aspect.ratio + box.verticalExtra);
1262
+ }
1263
+ /**
1264
+ * Hold an item to its least size along the main axis, the size Yoga gave
1265
+ * it being under that. It is frozen there, as CSS Flexbox freezes an item
1266
+ * its minimum stops (9.7, step 4): inflexible, at that size, the rest of
1267
+ * the line sharing what is left. Handed the minimum as a `min-width`
1268
+ * alone, Yoga makes it the item's flex base size, which is two things the
1269
+ * specification does not do:
1270
+ *
1271
+ * - where the line has room to share out, the item takes its share on top
1272
+ * of the minimum: of three `flex: 1` items in 300 pixels, the one with a
1273
+ * word 200 wide was 233, where it is 200 and the others 50 each;
1274
+ * - where every item of a line is held, Yoga divides what the line is
1275
+ * short of by what is left of the shrink factors once it has taken each
1276
+ * item's away — nothing, or a float's rounding of it. Two items 110.992
1277
+ * and 62.706 pixels wide in a row too narrow for them came out billions
1278
+ * of pixels wide, and 31.3 and 17.7 came out as they are: by the digits
1279
+ * of their sizes, which are a text's widths in whatever font it is set.
1280
+ *
1281
+ * On the one line of a box that does not wrap, an item Yoga had under its
1282
+ * minimum has no more room when another is held too, so frozen is where it
1283
+ * ends. In a box that wraps it may have: the minimums move the line breaks
1284
+ * (9.3), and an item held on a crowded line may be on one with room to
1285
+ * grow in once they have. There the minimum is left to Yoga, as a minimum;
1286
+ * only a line of one item is short of room there, and what Yoga takes away
1287
+ * from its shrink factors is exactly what it added.
1288
+ */
1289
+ function holdAt(node, laid, row, least, hold) {
1290
+ if (row)
1291
+ node.setMinWidth(least);
1292
+ else
1293
+ node.setMinHeight(least);
1294
+ if (!hold.frozen)
1295
+ return;
1296
+ laid.auto = true;
1297
+ freezeAt(node, laid, row, least, hold);
1298
+ }
1299
+ /** Freeze an item at a size along the main axis: inflexible, and that size
1300
+ * its flex base size. */
1301
+ function freezeAt(node, laid, row, size, hold) {
1302
+ laid.held = size;
1303
+ node.setFlexGrow(0);
1304
+ node.setFlexShrink(0);
1305
+ if (hold.basis)
1306
+ node.setFlexBasis(size);
1307
+ else if (row)
1308
+ node.setWidth(size);
1309
+ else
1310
+ node.setHeight(size);
1311
+ }
1312
+ /**
1313
+ * Put right the one line of a box that does not wrap, where every item of
1314
+ * it that may shrink is stopped by its minimum and Yoga made them wider for
1315
+ * it; whether Yoga was asked again. The line is laid out with each of them
1316
+ * frozen at its minimum, which is where CSS Flexbox leaves them (9.7, step
1317
+ * 4): the line is short of room with every one of them as small as it
1318
+ * goes.
1319
+ *
1320
+ * Yoga shares what a line is short of in two passes. The first takes each
1321
+ * item a minimum stops out of the sum of the scaled shrink factors, one
1322
+ * subtraction an item, and the second divides what the line is still short
1323
+ * of by what is left of the sum. With every item stopped that is nothing:
1324
+ * `(a + b) - a - b`, in float32s, which is 0 for some sizes and a rounding
1325
+ * either side of it for others, 2^-18 for sizes about a hundred. Yoga
1326
+ * 3.2.1 tests for 0, and the guard it has had since, a sum under 1e-6, is
1327
+ * under that rounding (react/yoga#1665, #1974). A negative rounding sends
1328
+ * every item under its minimum, which stops it, and a positive one makes
1329
+ * each of them wider by billions of pixels: `width: 110.992px; min-width:
1330
+ * 110.992px` beside `62.705625px` of the same, in a row 50 wide, were
1331
+ * 3599091712 and 2033329408 wide, where 100 and 60 were as they are. So
1332
+ * were two items with no minimum but their padding, and two with
1333
+ * `min-width: 0` beside one that does not shrink and is wider than the row
1334
+ * alone.
1335
+ *
1336
+ * A minimum of the item's own is Yoga's to hold it to, so this looks at
1337
+ * its answer, for a line that is two things at once. It is short of room
1338
+ * with every item that may shrink at its minimum — where 9.7 leaves each
1339
+ * of them at that, whatever their sizes. And one of them is wider than its
1340
+ * hypothetical size (9.7, step 2), which no sharing out of what a line is
1341
+ * short of makes an item: Yoga is asked for the line with nothing
1342
+ * shrinking, where each item is that size. Any other line is put back as
1343
+ * it was, the two layouts that took being the cost of one that is short of
1344
+ * room at its minimums and was not shared out to them; a line with room
1345
+ * for its minimums, or one whose every item sits at its own, has cost the
1346
+ * sum of its sizes.
1347
+ *
1348
+ * An automatic minimum is no part of it: an item held at one is frozen
1349
+ * already (`holdAt`), and one not yet held has no minimum to Yoga but its
1350
+ * padding and border, where this leaves it for `autoMinimums` to find
1351
+ * short of its content.
1352
+ */
1353
+ function stoppedLine(items, row,
1354
+ /** The flex box's size along the main axis. */
1355
+ room,
1356
+ /** The gap between two items. */
1357
+ gap, hold,
1358
+ /** Lay the flex box out, as its nodes now are. */
1359
+ ask) {
1360
+ // what is between the items' border boxes: the gaps, and their margins
1361
+ let between = gap * (items.length - 1);
1362
+ // the line's size with each item that may shrink at its minimum
1363
+ let least = 0;
1364
+ let shrinking = 0;
1365
+ let over = false;
1366
+ for (const { box, laid } of items) {
1367
+ const style = box.style;
1368
+ // an `auto` margin is none on a line with no room (8.1)
1369
+ if (row) {
1370
+ if (style.marginLeft !== AUTO)
1371
+ between += box.marginLeft;
1372
+ if (style.marginRight !== AUTO)
1373
+ between += box.marginRight;
1374
+ }
1375
+ else {
1376
+ if (style.marginTop !== AUTO)
1377
+ between += box.marginTop;
1378
+ if (style.marginBottom !== AUTO)
1379
+ between += box.marginBottom;
1380
+ }
1381
+ if (!shrinks(box, laid))
1382
+ least += laid.main;
1383
+ else {
1384
+ const min = leastOf(box, laid, row);
1385
+ shrinking += 1;
1386
+ least += min;
1387
+ if (laid.main > min + 0.01)
1388
+ over = true;
1389
+ }
1390
+ }
1391
+ // two at the least: what is left of a sum of one, less it, is 0 exactly.
1392
+ // A line within a thousandth of a pixel of room is short of it: what
1393
+ // Yoga makes of that is as far out, and the minimums are that near
1394
+ if (shrinking < 2 || !over || !(between + least > room - SHORT)) {
1395
+ return false;
1396
+ }
1397
+ const stopped = items.filter(({ box, laid }) => shrinks(box, laid));
1398
+ for (const { node } of stopped)
1399
+ node.setFlexShrink(0);
1400
+ ask();
1401
+ // each item its hypothetical size, which one that does not shrink is on
1402
+ // a line short of room — where Yoga may have grown it, on a line it took
1403
+ // for having some
1404
+ let wider = false;
1405
+ least = 0;
1406
+ for (const { box, node, laid } of items) {
1407
+ const hypothetical = row
1408
+ ? node.getComputedWidth()
1409
+ : node.getComputedHeight();
1410
+ if (!shrinks(box, laid))
1411
+ least += hypothetical;
1412
+ else {
1413
+ least += leastOf(box, laid, row);
1414
+ if (laid.main > hypothetical && !nearly(laid.main, hypothetical)) {
1415
+ wider = true;
1416
+ }
1417
+ }
1418
+ }
1419
+ const freeze = wider && between + least > room - SHORT;
1420
+ for (const { box, node, laid } of stopped) {
1421
+ if (freeze)
1422
+ freezeAt(node, laid, row, leastOf(box, laid, row), hold);
1423
+ else
1424
+ node.setFlexShrink(box.style.flexShrink);
1425
+ }
1426
+ ask();
1427
+ return true;
1428
+ }
1429
+ /** Whether Yoga may shrink an item: it has a shrink factor, and is not
1430
+ * frozen (`freezeAt`). */
1431
+ function shrinks(box, laid) {
1432
+ return box.style.flexShrink > 0 && Number.isNaN(laid.held);
1433
+ }
1434
+ /** How near to its room a line may be and be short of it (`stoppedLine`). */
1435
+ const SHORT = 0.001;
1436
+ /** The least an item may be along the main axis, its border box: the
1437
+ * minimum of its own, and no less than its padding and borders. */
1438
+ function leastOf(box, laid, row) {
1439
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
1440
+ return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
1441
+ }
952
1442
  /** An item's min-content width, its border box's, taken the once: its
953
1443
  * content's, whatever width it has of its own (`keywordWidth`'s too). */
954
1444
  function minContentOf(box, ctx, laid) {