@react-x11/components 0.13.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 (128) hide show
  1. package/dist/html/controls.d.ts +29 -2
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +70 -2
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts.map +1 -1
  6. package/dist/html/css/cascade.js +26 -4
  7. package/dist/html/css/cascade.js.map +1 -1
  8. package/dist/html/css/color.d.ts +18 -0
  9. package/dist/html/css/color.d.ts.map +1 -1
  10. package/dist/html/css/color.js +27 -0
  11. package/dist/html/css/color.js.map +1 -1
  12. package/dist/html/css/parse.d.ts +18 -4
  13. package/dist/html/css/parse.d.ts.map +1 -1
  14. package/dist/html/css/parse.js +66 -28
  15. package/dist/html/css/parse.js.map +1 -1
  16. package/dist/html/css/style.d.ts +128 -15
  17. package/dist/html/css/style.d.ts.map +1 -1
  18. package/dist/html/css/style.js +561 -144
  19. package/dist/html/css/style.js.map +1 -1
  20. package/dist/html/css/transform.d.ts +85 -0
  21. package/dist/html/css/transform.d.ts.map +1 -0
  22. package/dist/html/css/transform.js +425 -0
  23. package/dist/html/css/transform.js.map +1 -0
  24. package/dist/html/css/ua.d.ts.map +1 -1
  25. package/dist/html/css/ua.js +14 -2
  26. package/dist/html/css/ua.js.map +1 -1
  27. package/dist/html/css/values.d.ts +4 -0
  28. package/dist/html/css/values.d.ts.map +1 -1
  29. package/dist/html/css/values.js.map +1 -1
  30. package/dist/html/fonts.d.ts +55 -5
  31. package/dist/html/fonts.d.ts.map +1 -1
  32. package/dist/html/fonts.js +284 -23
  33. package/dist/html/fonts.js.map +1 -1
  34. package/dist/html/layout/axes.d.ts +14 -0
  35. package/dist/html/layout/axes.d.ts.map +1 -0
  36. package/dist/html/layout/axes.js +28 -0
  37. package/dist/html/layout/axes.js.map +1 -0
  38. package/dist/html/layout/block.d.ts +93 -12
  39. package/dist/html/layout/block.d.ts.map +1 -1
  40. package/dist/html/layout/block.js +389 -39
  41. package/dist/html/layout/block.js.map +1 -1
  42. package/dist/html/layout/boxes.d.ts +59 -1
  43. package/dist/html/layout/boxes.d.ts.map +1 -1
  44. package/dist/html/layout/boxes.js +22 -2
  45. package/dist/html/layout/boxes.js.map +1 -1
  46. package/dist/html/layout/cache.d.ts +1 -1
  47. package/dist/html/layout/cache.d.ts.map +1 -1
  48. package/dist/html/layout/cache.js +6 -0
  49. package/dist/html/layout/cache.js.map +1 -1
  50. package/dist/html/layout/css-grid.d.ts.map +1 -1
  51. package/dist/html/layout/css-grid.js +90 -18
  52. package/dist/html/layout/css-grid.js.map +1 -1
  53. package/dist/html/layout/flex.d.ts.map +1 -1
  54. package/dist/html/layout/flex.js +570 -83
  55. package/dist/html/layout/flex.js.map +1 -1
  56. package/dist/html/layout/inline.d.ts +3 -0
  57. package/dist/html/layout/inline.d.ts.map +1 -1
  58. package/dist/html/layout/inline.js +144 -54
  59. package/dist/html/layout/inline.js.map +1 -1
  60. package/dist/html/layout/multicol.d.ts +42 -0
  61. package/dist/html/layout/multicol.d.ts.map +1 -0
  62. package/dist/html/layout/multicol.js +897 -0
  63. package/dist/html/layout/multicol.js.map +1 -0
  64. package/dist/html/layout/shaping.d.ts +7 -0
  65. package/dist/html/layout/shaping.d.ts.map +1 -1
  66. package/dist/html/layout/shaping.js +25 -20
  67. package/dist/html/layout/shaping.js.map +1 -1
  68. package/dist/html/layout/table.d.ts.map +1 -1
  69. package/dist/html/layout/table.js +62 -18
  70. package/dist/html/layout/table.js.map +1 -1
  71. package/dist/html/node.d.ts +3 -0
  72. package/dist/html/node.d.ts.map +1 -1
  73. package/dist/html/node.js +263 -36
  74. package/dist/html/node.js.map +1 -1
  75. package/dist/html/paint.d.ts +79 -4
  76. package/dist/html/paint.d.ts.map +1 -1
  77. package/dist/html/paint.js +1213 -141
  78. package/dist/html/paint.js.map +1 -1
  79. package/dist/html/svg.d.ts +15 -2
  80. package/dist/html/svg.d.ts.map +1 -1
  81. package/dist/html/svg.js +249 -17
  82. package/dist/html/svg.js.map +1 -1
  83. package/dist/html/widgets.js +87 -7
  84. package/dist/html/widgets.js.map +1 -1
  85. package/dist/html/woff2.d.ts +14 -0
  86. package/dist/html/woff2.d.ts.map +1 -0
  87. package/dist/html/woff2.js +608 -0
  88. package/dist/html/woff2.js.map +1 -0
  89. package/dist/maps/controller.d.ts +17 -1
  90. package/dist/maps/controller.d.ts.map +1 -1
  91. package/dist/maps/controller.js +27 -7
  92. package/dist/maps/controller.js.map +1 -1
  93. package/dist/richtext/node.d.ts +8 -1
  94. package/dist/richtext/node.d.ts.map +1 -1
  95. package/dist/richtext/node.js +5 -1
  96. package/dist/richtext/node.js.map +1 -1
  97. package/dist/richtext/runs.d.ts +25 -6
  98. package/dist/richtext/runs.d.ts.map +1 -1
  99. package/dist/richtext/runs.js +240 -24
  100. package/dist/richtext/runs.js.map +1 -1
  101. package/package.json +1 -1
  102. package/src/html/controls.ts +85 -2
  103. package/src/html/css/cascade.ts +23 -0
  104. package/src/html/css/color.ts +29 -0
  105. package/src/html/css/parse.ts +71 -31
  106. package/src/html/css/style.ts +664 -147
  107. package/src/html/css/transform.ts +459 -0
  108. package/src/html/css/ua.ts +15 -2
  109. package/src/html/css/values.ts +4 -0
  110. package/src/html/fonts.ts +312 -29
  111. package/src/html/layout/axes.ts +62 -0
  112. package/src/html/layout/block.ts +444 -37
  113. package/src/html/layout/boxes.ts +65 -3
  114. package/src/html/layout/cache.ts +6 -0
  115. package/src/html/layout/css-grid.ts +95 -22
  116. package/src/html/layout/flex.ts +671 -77
  117. package/src/html/layout/inline.ts +149 -55
  118. package/src/html/layout/multicol.ts +977 -0
  119. package/src/html/layout/shaping.ts +27 -18
  120. package/src/html/layout/table.ts +62 -19
  121. package/src/html/node.ts +300 -37
  122. package/src/html/paint.ts +1406 -206
  123. package/src/html/svg.ts +277 -19
  124. package/src/html/widgets.ts +95 -9
  125. package/src/html/woff2.ts +612 -0
  126. package/src/maps/controller.ts +28 -7
  127. package/src/richtext/node.ts +13 -2
  128. 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,15 +436,18 @@ 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);
450
+ layoutItemAt(child, ctx, left, top, width, itemHeight, laid, definite, contentWidth, !row);
390
451
  // to the end of its margin box, as Yoga measured the box: a negative
391
452
  // margin takes the item's end back in, which Codex's search field
392
453
  // hangs a pixel over its form's border with, top and bottom
@@ -397,6 +458,8 @@ lined, started) {
397
458
  function layoutItemAt(box, ctx, x, y, width, height, laid,
398
459
  /** Its border box's height where the flex layout made it definite. */
399
460
  definite,
461
+ /** The flex box's content width, the item's containing block's. */
462
+ containing,
400
463
  /** Whether its height is the one the flex layout gave it, in a column,
401
464
  * whatever its own says: flexed, it is shrunk as well as grown. */
402
465
  column = false) {
@@ -409,32 +472,65 @@ column = false) {
409
472
  box.height = height;
410
473
  return;
411
474
  }
412
- const inner = definite === null || !percentHeightsIn(box)
413
- ? null
414
- : Math.max(0, definite - box.verticalExtra);
415
- if (inner !== null && !Object.is(inner, percentBaseInside(box))) {
416
- FLEXED_HEIGHT.set(box, inner);
417
- try {
418
- 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
419
497
  }
420
- finally {
421
- FLEXED_HEIGHT.delete(box);
498
+ else if (shares || (definite !== null && percentHeightsIn(box))) {
499
+ tall = inner;
422
500
  }
423
501
  }
424
502
  // `measureBox` lays the box out at (0, 0); re-running it at the final width
425
503
  // and then moving it is one pass, not two, because the second call is the
426
- // one whose result is kept — or no pass, where the last measure was at
427
- // this width. An item Yoga never measured has none, NaN, and is laid out.
428
- 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))
429
508
  box.height = laid.height;
430
- else
431
- 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
+ }
432
526
  // A stretched item is taller than its content, and the box has to say so
433
527
  // or its background stops short of the row — and it is no taller than its
434
528
  // line where its content is, or than what its ratio makes of its width:
435
529
  // its height is the line's (CSS Flexbox 9.4, step 11).
436
530
  if (height > box.height || column || definite !== null)
437
531
  box.height = height;
532
+ // and a button's content is centred in the height it came to
533
+ centreButton(box);
438
534
  moveTo(box, x, y);
439
535
  }
440
536
  // What a new Yoga node already is is not said again. A call into the
@@ -461,8 +557,11 @@ function applyContainer(node, style) {
461
557
  node.setAlignContent(lines);
462
558
  }
463
559
  function applyItem(node, box, ctx, containingWidth, laid,
464
- /** Whether the flex box has a height of its own. */
465
- 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) {
466
565
  const style = box.style;
467
566
  laid.stretch = containingWidth - box.marginLeft - box.marginRight;
468
567
  // an `auto` margin takes the free space on its side, which is how
@@ -492,11 +591,20 @@ tall) {
492
591
  if (style.width !== AUTO) {
493
592
  laid.set = setLength(node, true, style.width, containingWidth, across);
494
593
  }
495
- 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))) {
496
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;
497
603
  const minWidth = resolveOrNull(style.minWidth, containingWidth);
498
- if (minWidth !== null)
499
- node.setMinWidth(minWidth + across);
604
+ if (minWidth !== null) {
605
+ leastWidth = minWidth + across;
606
+ node.setMinWidth(leastWidth);
607
+ }
500
608
  if (style.maxWidth !== 'none') {
501
609
  const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
502
610
  if (maxWidth !== null)
@@ -512,20 +620,25 @@ tall) {
512
620
  laid.set = size(style.widthKeyword);
513
621
  node.setWidth(laid.set);
514
622
  }
515
- if (style.minWidthKeyword)
516
- node.setMinWidth(size(style.minWidthKeyword));
623
+ if (style.minWidthKeyword) {
624
+ leastWidth = size(style.minWidthKeyword);
625
+ node.setMinWidth(leastWidth);
626
+ }
517
627
  if (style.maxWidthKeyword)
518
628
  node.setMaxWidth(size(style.maxWidthKeyword));
519
629
  }
520
630
  const minHeight = resolveOrNull(style.minHeight, NaN);
521
- if (minHeight !== null)
522
- node.setMinHeight(minHeight + down);
631
+ if (minHeight !== null) {
632
+ leastHeight = minHeight + down;
633
+ node.setMinHeight(leastHeight);
634
+ }
523
635
  if (style.maxHeight !== 'none') {
524
636
  const maxHeight = resolveOrNull(style.maxHeight, NaN);
525
637
  if (maxHeight !== null)
526
638
  node.setMaxHeight(maxHeight + down);
527
639
  }
528
640
  const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
641
+ laid.least = row ? leastWidth : leastHeight;
529
642
  // Yoga takes a length for a basis only where the flex box's main size is
530
643
  // definite, and else the item's own size along it: down a column of no
531
644
  // height of its own it read `flex: 0 0 3rem` as the item's height, or
@@ -561,6 +674,10 @@ tall) {
561
674
  else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
562
675
  // Yoga's own
563
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
+ }
564
681
  else if (isPct(style.flexBasis)) {
565
682
  // a percentage of the main size, which is known across a row
566
683
  const basis = style.flexBasis;
@@ -656,7 +773,12 @@ tall) {
656
773
  const width = meant(w, laid.set - extra, laid.stretch - extra);
657
774
  const given = hm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(h);
658
775
  const exact = wm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width);
659
- 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;
660
782
  // along a row, a replaced element is its own width wherever there is
661
783
  // room for less — its flex base size is not fitted to the room (9.2)
662
784
  // — and across a column it is fitted, as any item is
@@ -673,10 +795,8 @@ tall) {
673
795
  }
674
796
  let answer = answers.get(inner);
675
797
  if (answer === undefined) {
676
- answer = measureBox(box, ctx, inner);
798
+ answer = measureBox(box, ctx, inner, containingWidth, laid);
677
799
  answers.set(inner, answer);
678
- laid.width = box.width;
679
- laid.height = box.height;
680
800
  }
681
801
  return answer;
682
802
  });
@@ -752,13 +872,174 @@ extra) {
752
872
  * 9.2, `flex-basis: auto`), rather than as wide as the row; asked for one
753
873
  * exactly, it is that wide. Its height is its content's at that width.
754
874
  */
755
- function measureBox(box, ctx, inner) {
756
- 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);
757
892
  return {
758
893
  width: inner,
759
894
  height: Math.max(0, box.height - box.verticalExtra),
760
895
  };
761
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
+ }
762
1043
  /** The width an item's content is measured at: the width Yoga gives it
763
1044
  * exactly, or its max-content width, no wider than any it may take up to
764
1045
  * (CSS Flexbox 9.2). */
@@ -789,7 +1070,7 @@ function innerWidth(width, mode, maxContent) {
789
1070
  * (`Laid`), so a row with room pays for nothing. True where some item
790
1071
  * was given a minimum and the row has to be laid out again.
791
1072
  */
792
- function autoMinimums(items, row, ctx, containingWidth) {
1073
+ function autoMinimums(items, row, ctx, containingWidth, hold) {
793
1074
  let changed = false;
794
1075
  for (const { box, node, laid } of items) {
795
1076
  if (box.kind === 'text' || box.kind === 'break')
@@ -801,7 +1082,7 @@ function autoMinimums(items, row, ctx, containingWidth) {
801
1082
  if (!asked && scrolls(style)) {
802
1083
  continue;
803
1084
  }
804
- 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);
805
1086
  // a replaced item's content along a row is its natural width
806
1087
  if (row && box.kind === 'replaced') {
807
1088
  if (style.minWidth !== AUTO || style.minWidthKeyword)
@@ -809,7 +1090,7 @@ function autoMinimums(items, row, ctx, containingWidth) {
809
1090
  const least = replacedMinimum(box, containingWidth);
810
1091
  if (least === null || width >= least - 0.01)
811
1092
  continue;
812
- node.setMinWidth(least);
1093
+ holdAt(node, laid, row, least, hold);
813
1094
  changed = true;
814
1095
  continue;
815
1096
  }
@@ -836,7 +1117,7 @@ function autoMinimums(items, row, ctx, containingWidth) {
836
1117
  }
837
1118
  if (width >= least - 0.01)
838
1119
  continue;
839
- node.setMinWidth(least);
1120
+ holdAt(node, laid, row, least, hold);
840
1121
  changed = true;
841
1122
  continue;
842
1123
  }
@@ -865,9 +1146,9 @@ function autoMinimums(items, row, ctx, containingWidth) {
865
1146
  if (width >= least - 0.01)
866
1147
  continue;
867
1148
  least = Math.min(least, minContentOf(box, ctx, laid));
868
- if (node.getComputedWidth() >= least - 0.01)
1149
+ if (laid.main >= least - 0.01)
869
1150
  continue;
870
- node.setMinWidth(least);
1151
+ holdAt(node, laid, row, least, hold);
871
1152
  changed = true;
872
1153
  continue;
873
1154
  }
@@ -879,17 +1160,37 @@ function autoMinimums(items, row, ctx, containingWidth) {
879
1160
  transferredWidth(box, widest + box.horizontalExtra) <= width + 0.5) {
880
1161
  continue;
881
1162
  }
882
- if (!(Math.abs(laid.width - width) <= 0.01)) {
883
- ctx.layoutSubtree(box, width);
884
- laid.width = box.width;
885
- 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);
886
1172
  }
1173
+ const height = Number.isNaN(read) ? laid.height : kept.height;
887
1174
  let least;
888
1175
  if (row) {
889
- // or than what it drew at the width it has: nothing overflows it
890
- const drawn = intrinsicWidth(box) + box.horizontalExtra;
891
- 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) {
892
1192
  continue;
1193
+ }
893
1194
  // within what a ratio makes of its least and greatest heights (4.5's
894
1195
  // content size suggestion)
895
1196
  least = transferredWidth(box, minContentOf(box, ctx, laid));
@@ -899,15 +1200,18 @@ function autoMinimums(items, row, ctx, containingWidth) {
899
1200
  if (most !== null)
900
1201
  least = Math.min(least, most + extra);
901
1202
  }
902
- if (node.getComputedWidth() >= least - 0.01)
1203
+ if (laid.main >= least - 0.01)
903
1204
  continue;
904
- node.setMinWidth(least);
1205
+ holdAt(node, laid, row, least, hold);
905
1206
  }
906
1207
  else {
907
1208
  // its height, or where it has one of its own and its content comes to
908
1209
  // less, its content's: the lesser of the two (4.5)
909
- let content;
910
- if (box.kind === 'replaced')
1210
+ let content = read;
1211
+ if (!Number.isNaN(content)) {
1212
+ // read before
1213
+ }
1214
+ else if (box.kind === 'replaced')
911
1215
  content = laid.height;
912
1216
  else if (style.height !== AUTO && percentHeightsIn(box)) {
913
1217
  // what its content comes to where it has no height to take
@@ -915,43 +1219,226 @@ function autoMinimums(items, row, ctx, containingWidth) {
915
1219
  // apart, and the final pass lays it out again
916
1220
  FLEXED_HEIGHT.set(box, NaN);
917
1221
  try {
918
- ctx.layoutSubtree(box, width);
1222
+ ctx.layoutSubtree(box, width, containingWidth);
919
1223
  }
920
1224
  finally {
921
1225
  FLEXED_HEIGHT.delete(box);
922
1226
  }
923
- content = contentBottom(box) + box.verticalExtra;
1227
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
924
1228
  laid.width = NaN;
925
1229
  }
926
- else
927
- content = contentBottom(box) + box.verticalExtra;
928
- // and a ratio's content is at least its width through the ratio,
929
- // height of its own or not (CSS Sizing 4, 5.1)
930
- const aspect = style.aspectRatio;
931
- if (aspect && box.kind !== 'replaced') {
932
- content = Math.max(content, style.boxSizing === 'border-box'
933
- ? box.width / aspect.ratio
934
- : box.contentWidth / aspect.ratio + box.verticalExtra);
1230
+ else {
1231
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1232
+ remember(box, ctx, width, containingWidth, 'content', content);
935
1233
  }
936
1234
  // within what the ratio makes of its least and greatest widths
937
1235
  if (box.kind !== 'replaced') {
938
1236
  content = transferredHeight(box, content, containingWidth);
939
1237
  }
940
- least = Math.min(laid.height, content);
1238
+ least = Math.min(height, content);
941
1239
  const extra = style.boxSizing === 'border-box' ? 0 : box.verticalExtra;
942
1240
  if (style.maxHeight !== 'none') {
943
1241
  const most = resolveOrNull(style.maxHeight, NaN);
944
1242
  if (most !== null)
945
1243
  least = Math.min(least, most + extra);
946
1244
  }
947
- if (node.getComputedHeight() >= least - 0.01)
1245
+ if (laid.main >= least - 0.01)
948
1246
  continue;
949
- node.setMinHeight(least);
1247
+ holdAt(node, laid, row, least, hold);
950
1248
  }
951
1249
  changed = true;
952
1250
  }
953
1251
  return changed;
954
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
+ }
955
1442
  /** An item's min-content width, its border box's, taken the once: its
956
1443
  * content's, whatever width it has of its own (`keywordWidth`'s too). */
957
1444
  function minContentOf(box, ctx, laid) {