@react-x11/components 0.13.0 → 0.14.1

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 (134) 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 +42 -1
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +336 -16
  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 +18 -4
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +66 -28
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/shapes.d.ts +44 -0
  18. package/dist/html/css/shapes.d.ts.map +1 -0
  19. package/dist/html/css/shapes.js +194 -0
  20. package/dist/html/css/shapes.js.map +1 -0
  21. package/dist/html/css/style.d.ts +128 -15
  22. package/dist/html/css/style.d.ts.map +1 -1
  23. package/dist/html/css/style.js +547 -144
  24. package/dist/html/css/style.js.map +1 -1
  25. package/dist/html/css/transform.d.ts +85 -0
  26. package/dist/html/css/transform.d.ts.map +1 -0
  27. package/dist/html/css/transform.js +425 -0
  28. package/dist/html/css/transform.js.map +1 -0
  29. package/dist/html/css/ua.d.ts.map +1 -1
  30. package/dist/html/css/ua.js +14 -2
  31. package/dist/html/css/ua.js.map +1 -1
  32. package/dist/html/css/values.d.ts +4 -0
  33. package/dist/html/css/values.d.ts.map +1 -1
  34. package/dist/html/css/values.js.map +1 -1
  35. package/dist/html/fonts.d.ts +55 -5
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +284 -23
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/layout/axes.d.ts +14 -0
  40. package/dist/html/layout/axes.d.ts.map +1 -0
  41. package/dist/html/layout/axes.js +28 -0
  42. package/dist/html/layout/axes.js.map +1 -0
  43. package/dist/html/layout/block.d.ts +104 -12
  44. package/dist/html/layout/block.d.ts.map +1 -1
  45. package/dist/html/layout/block.js +410 -39
  46. package/dist/html/layout/block.js.map +1 -1
  47. package/dist/html/layout/boxes.d.ts +72 -1
  48. package/dist/html/layout/boxes.d.ts.map +1 -1
  49. package/dist/html/layout/boxes.js +40 -2
  50. package/dist/html/layout/boxes.js.map +1 -1
  51. package/dist/html/layout/cache.d.ts +1 -1
  52. package/dist/html/layout/cache.d.ts.map +1 -1
  53. package/dist/html/layout/cache.js +6 -0
  54. package/dist/html/layout/cache.js.map +1 -1
  55. package/dist/html/layout/css-grid.d.ts.map +1 -1
  56. package/dist/html/layout/css-grid.js +144 -24
  57. package/dist/html/layout/css-grid.js.map +1 -1
  58. package/dist/html/layout/flex.d.ts.map +1 -1
  59. package/dist/html/layout/flex.js +585 -85
  60. package/dist/html/layout/flex.js.map +1 -1
  61. package/dist/html/layout/inline.d.ts +3 -0
  62. package/dist/html/layout/inline.d.ts.map +1 -1
  63. package/dist/html/layout/inline.js +144 -54
  64. package/dist/html/layout/inline.js.map +1 -1
  65. package/dist/html/layout/multicol.d.ts +42 -0
  66. package/dist/html/layout/multicol.d.ts.map +1 -0
  67. package/dist/html/layout/multicol.js +897 -0
  68. package/dist/html/layout/multicol.js.map +1 -0
  69. package/dist/html/layout/shaping.d.ts +7 -0
  70. package/dist/html/layout/shaping.d.ts.map +1 -1
  71. package/dist/html/layout/shaping.js +25 -20
  72. package/dist/html/layout/shaping.js.map +1 -1
  73. package/dist/html/layout/table.d.ts.map +1 -1
  74. package/dist/html/layout/table.js +97 -31
  75. package/dist/html/layout/table.js.map +1 -1
  76. package/dist/html/node.d.ts +4 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +319 -47
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +83 -5
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +1474 -217
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/svg.d.ts +39 -9
  85. package/dist/html/svg.d.ts.map +1 -1
  86. package/dist/html/svg.js +482 -37
  87. package/dist/html/svg.js.map +1 -1
  88. package/dist/html/widgets.js +87 -7
  89. package/dist/html/widgets.js.map +1 -1
  90. package/dist/html/woff2.d.ts +14 -0
  91. package/dist/html/woff2.d.ts.map +1 -0
  92. package/dist/html/woff2.js +608 -0
  93. package/dist/html/woff2.js.map +1 -0
  94. package/dist/maps/controller.d.ts +17 -1
  95. package/dist/maps/controller.d.ts.map +1 -1
  96. package/dist/maps/controller.js +27 -7
  97. package/dist/maps/controller.js.map +1 -1
  98. package/dist/richtext/node.d.ts +8 -1
  99. package/dist/richtext/node.d.ts.map +1 -1
  100. package/dist/richtext/node.js +5 -1
  101. package/dist/richtext/node.js.map +1 -1
  102. package/dist/richtext/runs.d.ts +25 -6
  103. package/dist/richtext/runs.d.ts.map +1 -1
  104. package/dist/richtext/runs.js +240 -24
  105. package/dist/richtext/runs.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/html/controls.ts +85 -2
  108. package/src/html/css/cascade.ts +366 -8
  109. package/src/html/css/color.ts +29 -0
  110. package/src/html/css/parse.ts +71 -31
  111. package/src/html/css/shapes.ts +239 -0
  112. package/src/html/css/style.ts +652 -147
  113. package/src/html/css/transform.ts +459 -0
  114. package/src/html/css/ua.ts +15 -2
  115. package/src/html/css/values.ts +4 -0
  116. package/src/html/fonts.ts +312 -29
  117. package/src/html/layout/axes.ts +62 -0
  118. package/src/html/layout/block.ts +466 -37
  119. package/src/html/layout/boxes.ts +95 -3
  120. package/src/html/layout/cache.ts +6 -0
  121. package/src/html/layout/css-grid.ts +169 -32
  122. package/src/html/layout/flex.ts +686 -79
  123. package/src/html/layout/inline.ts +149 -55
  124. package/src/html/layout/multicol.ts +977 -0
  125. package/src/html/layout/shaping.ts +27 -18
  126. package/src/html/layout/table.ts +105 -35
  127. package/src/html/node.ts +354 -48
  128. package/src/html/paint.ts +1804 -315
  129. package/src/html/svg.ts +573 -36
  130. package/src/html/widgets.ts +95 -9
  131. package/src/html/woff2.ts +612 -0
  132. package/src/maps/controller.ts +28 -7
  133. package/src/richtext/node.ts +13 -2
  134. package/src/richtext/runs.ts +313 -35
@@ -20,8 +20,10 @@
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 type { Config as YogaConfig, Node as YogaNode } from 'react-x11/yoga';
27
29
 
@@ -32,6 +34,8 @@ import { Box, PAINT_ORDER, isBlank } from './boxes.js';
32
34
  import {
33
35
  FLEXED_HEIGHT,
34
36
  MIN_CONTENT_PROBE,
37
+ USED_HEIGHT,
38
+ centreButton,
35
39
  clampHeight,
36
40
  clampWidth,
37
41
  contentSizedWidth,
@@ -122,9 +126,14 @@ export function layoutFlex(
122
126
  // percentage that resolves, or what `aspect-ratio` makes of the width —
123
127
  // less the padding and borders a `border-box` height holds, which put
124
128
  // `h-16 py-2 items-center` eight pixels low. Where it is not, the limits
125
- // on it, so a column `min-h-screen` gives its `flex-1` the rest.
129
+ // on it, so a column `min-h-screen` gives its `flex-1` the rest — or the
130
+ // height the column it is an item of flexed it to (`USED_HEIGHT`), which
131
+ // its items are laid out in and take no percentage of.
126
132
  const definite = percentBaseInside(box);
127
- const height = Number.isFinite(definite) ? definite : null;
133
+ const own = Number.isFinite(definite);
134
+ const height = USED_HEIGHT.get(box) ?? (own ? definite : null);
135
+ // whether its content may come to more than it is tall
136
+ let capped = height !== null;
128
137
  if (height !== null) root.setHeight(height);
129
138
  else {
130
139
  root.setHeightAuto();
@@ -132,7 +141,10 @@ export function layoutFlex(
132
141
  const min = clampHeight(box, extra) - extra;
133
142
  if (min > 0) root.setMinHeight(min);
134
143
  const max = clampHeight(box, Infinity) - extra;
135
- if (Number.isFinite(max)) root.setMaxHeight(Math.max(0, max));
144
+ if (Number.isFinite(max)) {
145
+ root.setMaxHeight(Math.max(0, max));
146
+ capped = true;
147
+ }
136
148
  }
137
149
 
138
150
  const flowing: Box[] = [];
@@ -161,8 +173,18 @@ export function layoutFlex(
161
173
  for (const child of flowing) {
162
174
  const node = Y.Node.create(flexConfig());
163
175
  resolveEdges(child, contentWidth);
164
- const laid: Laid = { width: NaN, height: NaN, set: NaN, stretch: NaN };
165
- applyItem(node, child, ctx, contentWidth, laid, height !== null);
176
+ const laid: Laid = {
177
+ width: NaN,
178
+ height: NaN,
179
+ tall: NaN,
180
+ set: NaN,
181
+ stretch: NaN,
182
+ main: NaN,
183
+ least: NaN,
184
+ held: NaN,
185
+ auto: false,
186
+ };
187
+ applyItem(node, child, ctx, contentWidth, laid, height !== null, own);
166
188
  root.insertChild(node, items.length);
167
189
  items.push({ box: child, node, laid });
168
190
  }
@@ -170,12 +192,38 @@ export function layoutFlex(
170
192
  const direction =
171
193
  box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
172
194
  const row = box.style.flexDirection.startsWith('row');
173
- const calculate = (): void =>
195
+ // frozen at its least size on the one line of a box that does not
196
+ // wrap, by a basis where Yoga takes one along the main axis
197
+ // (`applyItem`'s `setBasis`)
198
+ const hold: Hold = {
199
+ frozen: box.style.flexWrap === 'nowrap',
200
+ basis: row ? bounded : height !== null,
201
+ };
202
+ const ask = (): void =>
174
203
  root.calculateLayout(
175
204
  bounded ? contentWidth : Number.NaN,
176
205
  height ?? Number.NaN,
177
206
  direction,
178
207
  );
208
+ // the one line of a box that does not wrap may be short of room, where
209
+ // the box has a size along it that its items do not make: and Yoga's
210
+ // answer for a line every item of which its minimum stops is put right
211
+ // (`stoppedLine`)
212
+ const short = hold.frozen && (row ? bounded : capped);
213
+ // each item's size along the main axis, read the once a layout (`Laid`)
214
+ const read = (): void => {
215
+ for (const { node, laid } of items) {
216
+ laid.main = row ? node.getComputedWidth() : node.getComputedHeight();
217
+ }
218
+ };
219
+ const calculate = (): void => {
220
+ ask();
221
+ read();
222
+ if (!short) return;
223
+ const room = row ? contentWidth : (height ?? root.getComputedHeight());
224
+ const gap = row ? columnGap : rowGap;
225
+ if (stoppedLine(items, row, room, gap, hold, ask)) read();
226
+ };
179
227
  ctx.flexDepth = depth + 1;
180
228
  try {
181
229
  calculate();
@@ -184,7 +232,7 @@ export function layoutFlex(
184
232
  // another under its own, a few times over at most
185
233
  for (
186
234
  let pass = 0;
187
- pass < 4 && autoMinimums(items, row, ctx, contentWidth);
235
+ pass < 4 && autoMinimums(items, row, ctx, contentWidth, hold);
188
236
  pass += 1
189
237
  ) {
190
238
  calculate();
@@ -203,8 +251,9 @@ export function layoutFlex(
203
251
  ctx,
204
252
  items,
205
253
  row,
206
- height,
254
+ own,
207
255
  box.style.flexWrap === 'wrap' || baselines,
256
+ contentWidth,
208
257
  );
209
258
  // Items aligned by their baselines, which Yoga cannot see, are aligned
210
259
  // with their baselines known (`baselineLines`)
@@ -237,8 +286,9 @@ export function layoutFlex(
237
286
  ctx,
238
287
  items,
239
288
  row,
240
- height,
289
+ own,
241
290
  box.style.flexWrap === 'wrap',
291
+ contentWidth,
242
292
  started,
243
293
  );
244
294
  }
@@ -264,8 +314,9 @@ export function layoutFlex(
264
314
  ctx,
265
315
  items,
266
316
  row,
267
- height,
317
+ own,
268
318
  box.style.flexWrap === 'wrap' || baselines,
319
+ contentWidth,
269
320
  );
270
321
  }
271
322
 
@@ -278,11 +329,11 @@ export function layoutFlex(
278
329
  * base size the ratio makes of the stretched size; whether any changed. */
279
330
  function keepRatios(
280
331
  container: Box,
281
- items: readonly { box: Box; node: YogaNode }[],
332
+ items: readonly { box: Box; node: YogaNode; laid: Laid }[],
282
333
  row: boolean,
283
334
  ): boolean {
284
335
  let changed = false;
285
- for (const { box, node } of items) {
336
+ for (const { box, node, laid } of items) {
286
337
  if (box.kind !== 'replaced' || !stretches(box, container.style, row)) {
287
338
  continue;
288
339
  }
@@ -295,9 +346,16 @@ function keepRatios(
295
346
  box.horizontalExtra
296
347
  : Math.max(0, node.getComputedWidth() - box.horizontalExtra) / ratio +
297
348
  box.verticalExtra;
298
- const main = row ? node.getComputedWidth() : node.getComputedHeight();
299
- if (Math.abs(basis - main) < 0.5) continue;
349
+ if (Math.abs(basis - laid.main) < 0.5) continue;
300
350
  node.setFlexBasis(basis);
351
+ // one held at its least size (`holdAt`) flexes from the new basis, no
352
+ // further down than that
353
+ if (!Number.isNaN(laid.held)) {
354
+ laid.held = NaN;
355
+ laid.auto = false;
356
+ node.setFlexGrow(style.flexGrow);
357
+ node.setFlexShrink(style.flexShrink);
358
+ }
301
359
  changed = true;
302
360
  }
303
361
  return changed;
@@ -431,9 +489,12 @@ function placeItems(
431
489
  ctx: LayoutContext,
432
490
  items: readonly { box: Box; node: YogaNode; laid: Laid }[],
433
491
  row: boolean,
434
- height: number | null,
492
+ /** Whether the flex box has a definite height. */
493
+ own: boolean,
435
494
  /** Whether Yoga aligned the lines in its pass that drops a margin. */
436
495
  lined: boolean,
496
+ /** The flex box's content width: its items' containing block's. */
497
+ contentWidth: number,
437
498
  started?: ReadonlySet<Box>,
438
499
  ): number {
439
500
  let bottom = 0;
@@ -444,12 +505,13 @@ function placeItems(
444
505
  const left = box.contentX + node.getComputedLeft() + (row ? 0 : across);
445
506
  const top = box.contentY + node.getComputedTop() + (row ? across : 0);
446
507
  const width = meant(
447
- node.getComputedWidth(),
508
+ row ? laid.main : node.getComputedWidth(),
448
509
  laid.set,
449
510
  laid.stretch,
450
511
  laid.width,
512
+ row ? laid.held : NaN,
451
513
  );
452
- const itemHeight = node.getComputedHeight();
514
+ const itemHeight = row ? node.getComputedHeight() : laid.main;
453
515
  // The item is laid out again at the width the flex pass settled on: the
454
516
  // measure function answered a question, and the answer is not a layout —
455
517
  // its line breaks were computed against a width that may have changed
@@ -459,12 +521,15 @@ function placeItems(
459
521
  // 9.8): stretched across a row's line, or flexed in a column of a
460
522
  // height of its own. What is in it takes its percentages of that —
461
523
  // the `h-full` in a stretched sidebar — where they had nothing to be
462
- // of, and the item is laid out again for them.
524
+ // of, and the item is laid out again for them; and so is one that is a
525
+ // flex or grid container itself, whose own items are laid out in that
526
+ // height (9.4, step 11: "redo layout for its contents, treating this
527
+ // used size as its definite cross size").
463
528
  const definite = row
464
529
  ? stretches(child, box.style)
465
530
  ? itemHeight
466
531
  : null
467
- : height !== null
532
+ : own
468
533
  ? itemHeight
469
534
  : null;
470
535
  layoutItemAt(
@@ -476,6 +541,7 @@ function placeItems(
476
541
  itemHeight,
477
542
  laid,
478
543
  definite,
544
+ contentWidth,
479
545
  !row,
480
546
  );
481
547
  // to the end of its margin box, as Yoga measured the box: a negative
@@ -496,6 +562,8 @@ function layoutItemAt(
496
562
  laid: Laid,
497
563
  /** Its border box's height where the flex layout made it definite. */
498
564
  definite: number | null,
565
+ /** The flex box's content width, the item's containing block's. */
566
+ containing: number,
499
567
  /** Whether its height is the one the flex layout gave it, in a column,
500
568
  * whatever its own says: flexed, it is shrunk as well as grown. */
501
569
  column = false,
@@ -509,29 +577,76 @@ function layoutItemAt(
509
577
  box.height = height;
510
578
  return;
511
579
  }
512
- const inner =
513
- definite === null || !percentHeightsIn(box)
514
- ? null
515
- : Math.max(0, definite - box.verticalExtra);
516
- if (inner !== null && !Object.is(inner, percentBaseInside(box))) {
517
- FLEXED_HEIGHT.set(box, inner);
518
- try {
519
- ctx.layoutSubtree(box, width);
520
- } finally {
521
- FLEXED_HEIGHT.delete(box);
580
+ const sameWidth = Math.abs(laid.width - width) <= 0.01;
581
+ // The content height the item is laid out at, where the flex layout gave
582
+ // it one that is not its own already: NaN where its content says how
583
+ // tall it is.
584
+ let tall = NaN;
585
+ // A flex or grid container shares its height out among its items: an
586
+ // `auto` margin takes what is free of it (8.1), a `flex: 1` grows into it
587
+ // (9.7), its lines are as tall as it (9.4), its `fr` rows fill it. So it
588
+ // is laid out in the height the flex layout gave it, definite or not —
589
+ // the one a column with no height of its own flexed it to is its used
590
+ // size all the same (9.7), and `min-h-screen flex flex-col` around a
591
+ // `flex-1 flex items-center` centres what is in that. At its content's
592
+ // own height there is nothing to share, and the layout it has is that
593
+ // one: the tallest card of a row is laid out the once. A table shares
594
+ // what it is given past its rows among them (`layoutTable`), and a
595
+ // height short of them is none it can have: it is laid out again where
596
+ // it was given more.
597
+ const given = definite ?? (column ? height : null);
598
+ let shares = false;
599
+ if (given !== null && (box.kind === 'flex' || box.kind === 'table')) {
600
+ const natural = naturalHeight(box, ctx, width, containing, laid);
601
+ shares =
602
+ box.kind === 'flex'
603
+ ? !(Math.abs(natural - given) <= 0.01)
604
+ : !(natural >= given - 0.01);
605
+ }
606
+ if (given !== null) {
607
+ const inner = Math.max(0, given - box.verticalExtra);
608
+ if (Object.is(inner, percentBaseInside(box))) {
609
+ // the height it has of its own
610
+ } else if (shares || (definite !== null && percentHeightsIn(box))) {
611
+ tall = inner;
522
612
  }
523
613
  }
524
614
  // `measureBox` lays the box out at (0, 0); re-running it at the final width
525
615
  // and then moving it is one pass, not two, because the second call is the
526
- // one whose result is kept — or no pass, where the last measure was at
527
- // this width. An item Yoga never measured has none, NaN, and is laid out.
528
- else if (Math.abs(laid.width - width) <= 0.01) box.height = laid.height;
529
- else ctx.layoutSubtree(box, width);
616
+ // one whose result is kept — or no pass, where the last layout was at
617
+ // this width and this height. An item Yoga never measured has none, NaN,
618
+ // and is laid out.
619
+ if (sameWidth && Object.is(laid.tall, tall)) box.height = laid.height;
620
+ else if (Number.isNaN(tall)) {
621
+ layoutNaturally(box, ctx, width, containing, laid);
622
+ } else {
623
+ // what is in it takes its percentages of a definite height alone (9.8)
624
+ const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
625
+ heights.set(box, tall);
626
+ try {
627
+ ctx.layoutSubtree(box, width, containing);
628
+ } finally {
629
+ heights.delete(box);
630
+ }
631
+ laid.width = box.width;
632
+ laid.height = box.height;
633
+ laid.tall = tall;
634
+ }
530
635
  // A stretched item is taller than its content, and the box has to say so
531
636
  // or its background stops short of the row — and it is no taller than its
532
637
  // line where its content is, or than what its ratio makes of its width:
533
- // its height is the line's (CSS Flexbox 9.4, step 11).
534
- if (height > box.height || column || definite !== null) box.height = height;
638
+ // its height is the line's (CSS Flexbox 9.4, step 11). But for a table,
639
+ // whose rows are the least it can be, however short its line (CSS 2.1
640
+ // 17.5.3, as a height of its own is; Blink: "Tables can't shrink below
641
+ // their min-intrinsic size").
642
+ if (
643
+ height > box.height ||
644
+ ((column || definite !== null) && box.kind !== 'table')
645
+ ) {
646
+ box.height = height;
647
+ }
648
+ // and a button's content is centred in the height it came to
649
+ centreButton(box);
535
650
  moveTo(box, x, y);
536
651
  }
537
652
 
@@ -561,8 +676,11 @@ function applyItem(
561
676
  ctx: LayoutContext,
562
677
  containingWidth: number,
563
678
  laid: Laid,
564
- /** Whether the flex box has a height of its own. */
679
+ /** Whether the flex box has a height its items are laid out in. */
565
680
  tall: boolean,
681
+ /** Whether that height is definite, and a percentage may be of it: one a
682
+ * column flexed the box to is not (`USED_HEIGHT`). */
683
+ own: boolean,
566
684
  ): void {
567
685
  const style = box.style;
568
686
  laid.stretch = containingWidth - box.marginLeft - box.marginRight;
@@ -590,9 +708,20 @@ function applyItem(
590
708
  if (style.width !== AUTO) {
591
709
  laid.set = setLength(node, true, style.width, containingWidth, across);
592
710
  }
593
- if (style.height !== AUTO) setLength(node, false, style.height, NaN, down);
711
+ // a percentage of a height that is not definite is `auto` (CSS 2.1 10.5),
712
+ // which Yoga makes of one where the flex box has no height at all
713
+ if (style.height !== AUTO && (own || !tall || !isPct(style.height))) {
714
+ setLength(node, false, style.height, NaN, down);
715
+ }
716
+ // the least it may be along each axis, its border box's, as Yoga is
717
+ // told it
718
+ let leastWidth = NaN;
719
+ let leastHeight = NaN;
594
720
  const minWidth = resolveOrNull(style.minWidth, containingWidth);
595
- if (minWidth !== null) node.setMinWidth(minWidth + across);
721
+ if (minWidth !== null) {
722
+ leastWidth = minWidth + across;
723
+ node.setMinWidth(leastWidth);
724
+ }
596
725
  if (style.maxWidth !== 'none') {
597
726
  const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
598
727
  if (maxWidth !== null) node.setMaxWidth(maxWidth + across);
@@ -611,17 +740,24 @@ function applyItem(
611
740
  laid.set = size(style.widthKeyword);
612
741
  node.setWidth(laid.set);
613
742
  }
614
- if (style.minWidthKeyword) node.setMinWidth(size(style.minWidthKeyword));
743
+ if (style.minWidthKeyword) {
744
+ leastWidth = size(style.minWidthKeyword);
745
+ node.setMinWidth(leastWidth);
746
+ }
615
747
  if (style.maxWidthKeyword) node.setMaxWidth(size(style.maxWidthKeyword));
616
748
  }
617
749
  const minHeight = resolveOrNull(style.minHeight, NaN);
618
- if (minHeight !== null) node.setMinHeight(minHeight + down);
750
+ if (minHeight !== null) {
751
+ leastHeight = minHeight + down;
752
+ node.setMinHeight(leastHeight);
753
+ }
619
754
  if (style.maxHeight !== 'none') {
620
755
  const maxHeight = resolveOrNull(style.maxHeight, NaN);
621
756
  if (maxHeight !== null) node.setMaxHeight(maxHeight + down);
622
757
  }
623
758
 
624
759
  const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
760
+ laid.least = row ? leastWidth : leastHeight;
625
761
  // Yoga takes a length for a basis only where the flex box's main size is
626
762
  // definite, and else the item's own size along it: down a column of no
627
763
  // height of its own it read `flex: 0 0 3rem` as the item's height, or
@@ -653,6 +789,9 @@ function applyItem(
653
789
  setBasis(columnBasis);
654
790
  } else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
655
791
  // Yoga's own
792
+ } else if (isPct(style.flexBasis) && !row && tall && !own) {
793
+ // a percentage of a main size that is not definite is `content` (CSS
794
+ // Flexbox 7.2.3): Yoga's own, down a column
656
795
  } else if (isPct(style.flexBasis)) {
657
796
  // a percentage of the main size, which is known across a row
658
797
  const basis = style.flexBasis;
@@ -738,7 +877,13 @@ function applyItem(
738
877
  const width = meant(w, laid.set - extra, laid.stretch - extra);
739
878
  const given = hm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(h);
740
879
  const exact = wm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width);
741
- const through = widthThrough && given && !exact ? widthThrough(h) : null;
880
+ // the height it was flexed to, that is: one its automatic minimum
881
+ // holds it at (`holdAt`) is its content's, which a replaced element's
882
+ // width follows through its ratio and no other's does (CSS Sizing 4,
883
+ // 5.1)
884
+ const flexed = box.kind === 'replaced' || !laid.auto;
885
+ const through =
886
+ widthThrough && given && !exact && flexed ? widthThrough(h) : null;
742
887
  // along a row, a replaced element is its own width wherever there is
743
888
  // room for less — its flex base size is not fitted to the room (9.2)
744
889
  // — and across a column it is fitted, as any item is
@@ -758,10 +903,8 @@ function applyItem(
758
903
  }
759
904
  let answer = answers.get(inner);
760
905
  if (answer === undefined) {
761
- answer = measureBox(box, ctx, inner);
906
+ answer = measureBox(box, ctx, inner, containingWidth, laid);
762
907
  answers.set(inner, answer);
763
- laid.width = box.width;
764
- laid.height = box.height;
765
908
  }
766
909
  return answer;
767
910
  });
@@ -777,12 +920,28 @@ const MAX_CONTENT = new WeakMap<Box, number>();
777
920
  interface Laid {
778
921
  width: number;
779
922
  height: number;
923
+ /** The content height that layout was made at, where the flex layout gave
924
+ * the item one (`FLEXED_HEIGHT`, `USED_HEIGHT`), and NaN where its
925
+ * content's own. */
926
+ tall: number;
780
927
  /** The border-box width this engine set on the item's node — a length,
781
928
  * or the content's for `fit-content` and its kin — or NaN. */
782
929
  set: number;
783
930
  /** Its border-box width stretched across its container: the container's
784
931
  * content width, less its margins. */
785
932
  stretch: number;
933
+ /** Its border box's size along the main axis in the layout Yoga last
934
+ * made of the flex box, as Yoga has it. */
935
+ main: number;
936
+ /** The least it may be along the main axis, where its own style says:
937
+ * the minimum this engine set on its node, or NaN. */
938
+ least: number;
939
+ /** The size along the main axis it is frozen at, inflexible (`freezeAt`),
940
+ * or NaN. */
941
+ held: number;
942
+ /** Whether that is its automatic minimum (`holdAt`), its content's size,
943
+ * and not a minimum the line stopped at (`stoppedLine`). */
944
+ auto: boolean;
786
945
  }
787
946
 
788
947
  /**
@@ -855,14 +1014,236 @@ function measureBox(
855
1014
  box: Box,
856
1015
  ctx: LayoutContext,
857
1016
  inner: number,
1017
+ /** The flex box's content width, the item's containing block's. */
1018
+ containing: number,
1019
+ laid: Laid,
858
1020
  ): { width: number; height: number } {
859
- ctx.layoutSubtree(box, inner + box.horizontalExtra);
1021
+ // its padding as Yoga has it, a percentage of the flex box's width: a
1022
+ // probe of its content's width leaves a percentage at none
1023
+ resolveEdges(box, containing);
1024
+ const width = inner + box.horizontalExtra;
1025
+ // its height at this width is known, where a layout since has left it
1026
+ // another: the answer is the height, and the layout `layoutItemAt`'s
1027
+ const kept = keptNatural(box, ctx, width, containing);
1028
+ if (kept !== null && kept.serial !== box.layoutSerial) {
1029
+ return {
1030
+ width: inner,
1031
+ height: Math.max(0, kept.height - box.verticalExtra),
1032
+ };
1033
+ }
1034
+ layoutNaturally(box, ctx, width, containing, laid);
860
1035
  return {
861
1036
  width: inner,
862
1037
  height: Math.max(0, box.height - box.verticalExtra),
863
1038
  };
864
1039
  }
865
1040
 
1041
+ /** An item's height as its content's own makes it at a border-box width,
1042
+ * where this pass has laid it out so (`layoutNaturally`), and else NaN. */
1043
+ function naturalHeight(
1044
+ box: Box,
1045
+ ctx: LayoutContext,
1046
+ width: number,
1047
+ containing: number,
1048
+ laid: Laid,
1049
+ ): number {
1050
+ if (Math.abs(laid.width - width) <= 0.01 && Number.isNaN(laid.tall)) {
1051
+ return laid.height;
1052
+ }
1053
+ return keptNatural(box, ctx, width, containing)?.height ?? NaN;
1054
+ }
1055
+
1056
+ /**
1057
+ * Give an item the layout its content's own height makes of it at a
1058
+ * border-box width, and note it (`Laid`): the one this pass has made of it
1059
+ * there already, where it still has it.
1060
+ *
1061
+ * A flex box laid out a second time — at the height a stretch gave it, its
1062
+ * own flex layout made anew (`layoutItemAt`) — asks of each item what it
1063
+ * asked the first time, and the item's layout at a width is what it was.
1064
+ * Kept for the pass, a card laid out again for its height lays nothing in
1065
+ * it out that is where it was; made again at every layout of the box
1066
+ * around it, a card in a card in a card laid its innermost out twice a
1067
+ * level.
1068
+ */
1069
+ function layoutNaturally(
1070
+ box: Box,
1071
+ ctx: LayoutContext,
1072
+ width: number,
1073
+ /** The flex box's content width, the item's containing block's. */
1074
+ containing: number,
1075
+ laid: Laid,
1076
+ ): void {
1077
+ const kept = keptNatural(box, ctx, width, containing);
1078
+ if (kept !== null && kept.serial === box.layoutSerial) {
1079
+ // put back, where the flex layout stretched it
1080
+ box.height = kept.height;
1081
+ } else {
1082
+ const at = ctx.positioned.length;
1083
+ ctx.layoutSubtree(box, width, containing);
1084
+ // a replaced box is sized, not laid out, and has no layout to count
1085
+ if (box.kind !== 'replaced') {
1086
+ NATURAL.set(box, {
1087
+ ctx,
1088
+ serial: box.layoutSerial,
1089
+ width,
1090
+ containing: readsContaining(box) ? containing : NaN,
1091
+ base: box.percentHeightBase,
1092
+ edges: edgesOf(box),
1093
+ height: box.height,
1094
+ queued:
1095
+ ctx.positioned.length > at ? { at, first: ctx.positioned[at] } : null,
1096
+ drawn: NaN,
1097
+ content: NaN,
1098
+ });
1099
+ }
1100
+ }
1101
+ laid.width = box.width;
1102
+ laid.height = box.height;
1103
+ laid.tall = NaN;
1104
+ }
1105
+
1106
+ /**
1107
+ * The layout this pass made of an item at its content's own height
1108
+ * (`layoutNaturally`), where one at `width` now would be the same: the same
1109
+ * width, the same edges — a percentage among them resolved, as the layout
1110
+ * leaves them — the same containing block's width where a limit on its own
1111
+ * is a percentage of it, and the same height for its percentages, where its
1112
+ * own style reads that at all, which the flex box around it coming to a
1113
+ * definite height changes for every item in it. Its `height` is the item's
1114
+ * there whatever has laid it out since, and the layout is the one the box
1115
+ * has where nothing has (`Box.layoutSerial`).
1116
+ */
1117
+ function keptNatural(
1118
+ box: Box,
1119
+ ctx: LayoutContext,
1120
+ width: number,
1121
+ containing: number,
1122
+ ): Natural | null {
1123
+ const kept = NATURAL.get(box);
1124
+ if (kept === undefined || kept.ctx !== ctx || kept.width !== width) {
1125
+ return null;
1126
+ }
1127
+ if (!Number.isNaN(kept.containing) && kept.containing !== containing) {
1128
+ return null;
1129
+ }
1130
+ if (!Object.is(kept.base, box.percentHeightBase) && readsPercentBase(box)) {
1131
+ return null;
1132
+ }
1133
+ // the boxes in it that are positioned last are still waiting to be: a
1134
+ // `line-clamp: auto` box laid out a second time forgets the ones its
1135
+ // first layout found
1136
+ if (kept.queued && ctx.positioned[kept.queued.at] !== kept.queued.first) {
1137
+ return null;
1138
+ }
1139
+ const edges = kept.edges;
1140
+ return edges[0] === box.marginTop &&
1141
+ edges[1] === box.marginRight &&
1142
+ edges[2] === box.marginBottom &&
1143
+ edges[3] === box.marginLeft &&
1144
+ edges[4] === box.borderTop &&
1145
+ edges[5] === box.borderRight &&
1146
+ edges[6] === box.borderBottom &&
1147
+ edges[7] === box.borderLeft &&
1148
+ edges[8] === box.padTop &&
1149
+ edges[9] === box.padRight &&
1150
+ edges[10] === box.padBottom &&
1151
+ edges[11] === box.padLeft
1152
+ ? kept
1153
+ : null;
1154
+ }
1155
+
1156
+ /** A layout of an item at its content's own height (`layoutNaturally`): in
1157
+ * which pass, which of the box's layouts it was, and what it was made
1158
+ * from. */
1159
+ interface Natural {
1160
+ ctx: LayoutContext;
1161
+ serial: number;
1162
+ /** The border-box width it was asked for at. */
1163
+ width: number;
1164
+ /** Its containing block's width, where a limit on its own width is a
1165
+ * percentage of that (`readsContaining`), and else NaN: a nest of flex
1166
+ * boxes is laid out in rooms of many widths, and an item with no such
1167
+ * limit is laid out the same in each. */
1168
+ containing: number;
1169
+ /** The height its percentages were of. */
1170
+ base: number;
1171
+ /** Its margins, borders and padding once laid out (`edgesOf`). */
1172
+ edges: number[];
1173
+ height: number;
1174
+ /** The first of the out-of-flow boxes that layout queued to be
1175
+ * positioned (`LayoutContext.positioned`) and where; null for none. */
1176
+ queued: { at: number; first: LayoutContext['positioned'][number] } | null;
1177
+ /** What `autoMinimums` read from that layout, NaN until it has: how wide
1178
+ * it drew, and how far down its content came, each its border box's. */
1179
+ drawn: number;
1180
+ content: number;
1181
+ }
1182
+
1183
+ const NATURAL = new WeakMap<Box, Natural>();
1184
+
1185
+ /** Note what was read from the layout an item has, where that is the one
1186
+ * its content's own height makes of it at `width`. */
1187
+ function remember(
1188
+ box: Box,
1189
+ ctx: LayoutContext,
1190
+ width: number,
1191
+ containing: number,
1192
+ field: 'drawn' | 'content',
1193
+ value: number,
1194
+ ): void {
1195
+ const made = keptNatural(box, ctx, width, containing);
1196
+ if (made !== null && made.serial === box.layoutSerial) made[field] = value;
1197
+ }
1198
+
1199
+ /** A box's edges, which a layout resolves again against the width it is
1200
+ * given: margins, borders, padding, each from the top round. */
1201
+ function edgesOf(box: Box): number[] {
1202
+ return [
1203
+ box.marginTop,
1204
+ box.marginRight,
1205
+ box.marginBottom,
1206
+ box.marginLeft,
1207
+ box.borderTop,
1208
+ box.borderRight,
1209
+ box.borderBottom,
1210
+ box.borderLeft,
1211
+ box.padTop,
1212
+ box.padRight,
1213
+ box.padBottom,
1214
+ box.padLeft,
1215
+ ];
1216
+ }
1217
+
1218
+ /** Whether the width a box is laid out at is held to a percentage of its
1219
+ * containing block's: a least or greatest width that is one. */
1220
+ function readsContaining(box: Box): boolean {
1221
+ const style = box.style;
1222
+ return (
1223
+ isPct(style.minWidth) ||
1224
+ (style.maxWidth !== 'none' && isPct(style.maxWidth))
1225
+ );
1226
+ }
1227
+
1228
+ /** Whether a box's own layout reads the height its percentages are of: a
1229
+ * height, a least or greatest one, or an offset that is a percentage, a
1230
+ * height that is a keyword — `stretch` fills it — or an anonymous box,
1231
+ * which hands it on to what is in it (`percentBaseInside`). */
1232
+ function readsPercentBase(box: Box): boolean {
1233
+ if (!box.el && !box.pseudo) return true;
1234
+ const style = box.style;
1235
+ return (
1236
+ isPct(style.height) ||
1237
+ isPct(style.minHeight) ||
1238
+ (style.maxHeight !== 'none' && isPct(style.maxHeight)) ||
1239
+ isPct(style.top) ||
1240
+ isPct(style.bottom) ||
1241
+ style.heightKeyword !== null ||
1242
+ style.minHeightKeyword !== null ||
1243
+ style.maxHeightKeyword !== null
1244
+ );
1245
+ }
1246
+
866
1247
  /** The width an item's content is measured at: the width Yoga gives it
867
1248
  * exactly, or its max-content width, no wider than any it may take up to
868
1249
  * (CSS Flexbox 9.2). */
@@ -903,6 +1284,7 @@ function autoMinimums(
903
1284
  row: boolean,
904
1285
  ctx: LayoutContext,
905
1286
  containingWidth: number,
1287
+ hold: Hold,
906
1288
  ): boolean {
907
1289
  let changed = false;
908
1290
  for (const { box, node, laid } of items) {
@@ -915,17 +1297,18 @@ function autoMinimums(
915
1297
  continue;
916
1298
  }
917
1299
  const width = meant(
918
- node.getComputedWidth(),
1300
+ row ? laid.main : node.getComputedWidth(),
919
1301
  laid.set,
920
1302
  laid.stretch,
921
1303
  laid.width,
1304
+ row ? laid.held : NaN,
922
1305
  );
923
1306
  // a replaced item's content along a row is its natural width
924
1307
  if (row && box.kind === 'replaced') {
925
1308
  if (style.minWidth !== AUTO || style.minWidthKeyword) continue;
926
1309
  const least = replacedMinimum(box, containingWidth);
927
1310
  if (least === null || width >= least - 0.01) continue;
928
- node.setMinWidth(least);
1311
+ holdAt(node, laid, row, least, hold);
929
1312
  changed = true;
930
1313
  continue;
931
1314
  }
@@ -952,7 +1335,7 @@ function autoMinimums(
952
1335
  if (most !== null) least = Math.min(least, most + extra);
953
1336
  }
954
1337
  if (width >= least - 0.01) continue;
955
- node.setMinWidth(least);
1338
+ holdAt(node, laid, row, least, hold);
956
1339
  changed = true;
957
1340
  continue;
958
1341
  }
@@ -980,8 +1363,8 @@ function autoMinimums(
980
1363
  }
981
1364
  if (width >= least - 0.01) continue;
982
1365
  least = Math.min(least, minContentOf(box, ctx, laid));
983
- if (node.getComputedWidth() >= least - 0.01) continue;
984
- node.setMinWidth(least);
1366
+ if (laid.main >= least - 0.01) continue;
1367
+ holdAt(node, laid, row, least, hold);
985
1368
  changed = true;
986
1369
  continue;
987
1370
  }
@@ -995,16 +1378,38 @@ function autoMinimums(
995
1378
  ) {
996
1379
  continue;
997
1380
  }
998
- if (!(Math.abs(laid.width - width) <= 0.01)) {
999
- ctx.layoutSubtree(box, width);
1000
- laid.width = box.width;
1001
- laid.height = box.height;
1381
+ // What its content's own height makes of it at this width — how wide
1382
+ // that drew, along a row, and how far down, along a column — is read
1383
+ // from that layout, which the item is given where it has another: or
1384
+ // is what this pass read from it before, where the flex box is laid
1385
+ // out a second time (`layoutNaturally`)
1386
+ const kept = keptNatural(box, ctx, width, containingWidth);
1387
+ const read = kept === null ? NaN : row ? kept.drawn : kept.content;
1388
+ if (Number.isNaN(read) && !(Math.abs(laid.width - width) <= 0.01)) {
1389
+ layoutNaturally(box, ctx, width, containingWidth, laid);
1002
1390
  }
1391
+ const height = Number.isNaN(read) ? laid.height : kept!.height;
1003
1392
  let least: number;
1004
1393
  if (row) {
1005
- // or than what it drew at the width it has: nothing overflows it
1006
- const drawn = intrinsicWidth(box) + box.horizontalExtra;
1007
- if (transferredWidth(box, drawn) <= width + 0.5) continue;
1394
+ // or than what it drew at the width it has: nothing overflows it —
1395
+ // unless a line in it fits for being cut short by `text-overflow`,
1396
+ // which is how the line is drawn and no part of how wide it is: a
1397
+ // button whose label is a `truncate` span is no narrower than the
1398
+ // label, where it was shrunk and the label cut. No width, then, that
1399
+ // it drew at, which is what is kept of it
1400
+ let drawn = read;
1401
+ if (Number.isNaN(drawn)) {
1402
+ const seen = { cut: false };
1403
+ drawn = intrinsicWidth(box, seen) + box.horizontalExtra;
1404
+ if (seen.cut) drawn = Infinity;
1405
+ remember(box, ctx, width, containingWidth, 'drawn', drawn);
1406
+ }
1407
+ if (
1408
+ Number.isFinite(drawn) &&
1409
+ transferredWidth(box, drawn) <= width + 0.5
1410
+ ) {
1411
+ continue;
1412
+ }
1008
1413
  // within what a ratio makes of its least and greatest heights (4.5's
1009
1414
  // content size suggestion)
1010
1415
  least = transferredWidth(box, minContentOf(box, ctx, laid));
@@ -1013,55 +1418,257 @@ function autoMinimums(
1013
1418
  const most = resolveOrNull(style.maxWidth, containingWidth);
1014
1419
  if (most !== null) least = Math.min(least, most + extra);
1015
1420
  }
1016
- if (node.getComputedWidth() >= least - 0.01) continue;
1017
- node.setMinWidth(least);
1421
+ if (laid.main >= least - 0.01) continue;
1422
+ holdAt(node, laid, row, least, hold);
1018
1423
  } else {
1019
1424
  // its height, or where it has one of its own and its content comes to
1020
1425
  // less, its content's: the lesser of the two (4.5)
1021
- let content: number;
1022
- if (box.kind === 'replaced') content = laid.height;
1426
+ let content = read;
1427
+ if (!Number.isNaN(content)) {
1428
+ // read before
1429
+ } else if (box.kind === 'replaced') content = laid.height;
1023
1430
  else if (style.height !== AUTO && percentHeightsIn(box)) {
1024
1431
  // what its content comes to where it has no height to take
1025
1432
  // percentages of, as an intrinsic size is measured: laid out so
1026
1433
  // apart, and the final pass lays it out again
1027
1434
  FLEXED_HEIGHT.set(box, NaN);
1028
1435
  try {
1029
- ctx.layoutSubtree(box, width);
1436
+ ctx.layoutSubtree(box, width, containingWidth);
1030
1437
  } finally {
1031
1438
  FLEXED_HEIGHT.delete(box);
1032
1439
  }
1033
- content = contentBottom(box) + box.verticalExtra;
1440
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1034
1441
  laid.width = NaN;
1035
- } else content = contentBottom(box) + box.verticalExtra;
1036
- // and a ratio's content is at least its width through the ratio,
1037
- // height of its own or not (CSS Sizing 4, 5.1)
1038
- const aspect = style.aspectRatio;
1039
- if (aspect && box.kind !== 'replaced') {
1040
- content = Math.max(
1041
- content,
1042
- style.boxSizing === 'border-box'
1043
- ? box.width / aspect.ratio
1044
- : box.contentWidth / aspect.ratio + box.verticalExtra,
1045
- );
1442
+ } else {
1443
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1444
+ remember(box, ctx, width, containingWidth, 'content', content);
1046
1445
  }
1047
1446
  // within what the ratio makes of its least and greatest widths
1048
1447
  if (box.kind !== 'replaced') {
1049
1448
  content = transferredHeight(box, content, containingWidth);
1050
1449
  }
1051
- least = Math.min(laid.height, content);
1450
+ least = Math.min(height, content);
1052
1451
  const extra = style.boxSizing === 'border-box' ? 0 : box.verticalExtra;
1053
1452
  if (style.maxHeight !== 'none') {
1054
1453
  const most = resolveOrNull(style.maxHeight, NaN);
1055
1454
  if (most !== null) least = Math.min(least, most + extra);
1056
1455
  }
1057
- if (node.getComputedHeight() >= least - 0.01) continue;
1058
- node.setMinHeight(least);
1456
+ if (laid.main >= least - 0.01) continue;
1457
+ holdAt(node, laid, row, least, hold);
1059
1458
  }
1060
1459
  changed = true;
1061
1460
  }
1062
1461
  return changed;
1063
1462
  }
1064
1463
 
1464
+ /** A laid-out item's content height, its border box's: no less than its
1465
+ * width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
1466
+ function ratioContent(box: Box, content: number): number {
1467
+ const aspect = box.style.aspectRatio;
1468
+ if (!aspect || box.kind === 'replaced') return content;
1469
+ return Math.max(
1470
+ content,
1471
+ box.style.boxSizing === 'border-box'
1472
+ ? box.width / aspect.ratio
1473
+ : box.contentWidth / aspect.ratio + box.verticalExtra,
1474
+ );
1475
+ }
1476
+
1477
+ /** How a flex box holds its items to their least sizes (`holdAt`). */
1478
+ interface Hold {
1479
+ /** Whether an item is frozen at its least size: on the one line of a
1480
+ * box that does not wrap. */
1481
+ frozen: boolean;
1482
+ /** Whether Yoga takes a length for a basis along the main axis
1483
+ * (`applyItem`'s `setBasis`), and else the item's own size. */
1484
+ basis: boolean;
1485
+ }
1486
+
1487
+ /**
1488
+ * Hold an item to its least size along the main axis, the size Yoga gave
1489
+ * it being under that. It is frozen there, as CSS Flexbox freezes an item
1490
+ * its minimum stops (9.7, step 4): inflexible, at that size, the rest of
1491
+ * the line sharing what is left. Handed the minimum as a `min-width`
1492
+ * alone, Yoga makes it the item's flex base size, which is two things the
1493
+ * specification does not do:
1494
+ *
1495
+ * - where the line has room to share out, the item takes its share on top
1496
+ * of the minimum: of three `flex: 1` items in 300 pixels, the one with a
1497
+ * word 200 wide was 233, where it is 200 and the others 50 each;
1498
+ * - where every item of a line is held, Yoga divides what the line is
1499
+ * short of by what is left of the shrink factors once it has taken each
1500
+ * item's away — nothing, or a float's rounding of it. Two items 110.992
1501
+ * and 62.706 pixels wide in a row too narrow for them came out billions
1502
+ * of pixels wide, and 31.3 and 17.7 came out as they are: by the digits
1503
+ * of their sizes, which are a text's widths in whatever font it is set.
1504
+ *
1505
+ * On the one line of a box that does not wrap, an item Yoga had under its
1506
+ * minimum has no more room when another is held too, so frozen is where it
1507
+ * ends. In a box that wraps it may have: the minimums move the line breaks
1508
+ * (9.3), and an item held on a crowded line may be on one with room to
1509
+ * grow in once they have. There the minimum is left to Yoga, as a minimum;
1510
+ * only a line of one item is short of room there, and what Yoga takes away
1511
+ * from its shrink factors is exactly what it added.
1512
+ */
1513
+ function holdAt(
1514
+ node: YogaNode,
1515
+ laid: Laid,
1516
+ row: boolean,
1517
+ least: number,
1518
+ hold: Hold,
1519
+ ): void {
1520
+ if (row) node.setMinWidth(least);
1521
+ else node.setMinHeight(least);
1522
+ if (!hold.frozen) return;
1523
+ laid.auto = true;
1524
+ freezeAt(node, laid, row, least, hold);
1525
+ }
1526
+
1527
+ /** Freeze an item at a size along the main axis: inflexible, and that size
1528
+ * its flex base size. */
1529
+ function freezeAt(
1530
+ node: YogaNode,
1531
+ laid: Laid,
1532
+ row: boolean,
1533
+ size: number,
1534
+ hold: Hold,
1535
+ ): void {
1536
+ laid.held = size;
1537
+ node.setFlexGrow(0);
1538
+ node.setFlexShrink(0);
1539
+ if (hold.basis) node.setFlexBasis(size);
1540
+ else if (row) node.setWidth(size);
1541
+ else node.setHeight(size);
1542
+ }
1543
+
1544
+ /**
1545
+ * Put right the one line of a box that does not wrap, where every item of
1546
+ * it that may shrink is stopped by its minimum and Yoga made them wider for
1547
+ * it; whether Yoga was asked again. The line is laid out with each of them
1548
+ * frozen at its minimum, which is where CSS Flexbox leaves them (9.7, step
1549
+ * 4): the line is short of room with every one of them as small as it
1550
+ * goes.
1551
+ *
1552
+ * Yoga shares what a line is short of in two passes. The first takes each
1553
+ * item a minimum stops out of the sum of the scaled shrink factors, one
1554
+ * subtraction an item, and the second divides what the line is still short
1555
+ * of by what is left of the sum. With every item stopped that is nothing:
1556
+ * `(a + b) - a - b`, in float32s, which is 0 for some sizes and a rounding
1557
+ * either side of it for others, 2^-18 for sizes about a hundred. Yoga
1558
+ * 3.2.1 tests for 0, and the guard it has had since, a sum under 1e-6, is
1559
+ * under that rounding (react/yoga#1665, #1974). A negative rounding sends
1560
+ * every item under its minimum, which stops it, and a positive one makes
1561
+ * each of them wider by billions of pixels: `width: 110.992px; min-width:
1562
+ * 110.992px` beside `62.705625px` of the same, in a row 50 wide, were
1563
+ * 3599091712 and 2033329408 wide, where 100 and 60 were as they are. So
1564
+ * were two items with no minimum but their padding, and two with
1565
+ * `min-width: 0` beside one that does not shrink and is wider than the row
1566
+ * alone.
1567
+ *
1568
+ * A minimum of the item's own is Yoga's to hold it to, so this looks at
1569
+ * its answer, for a line that is two things at once. It is short of room
1570
+ * with every item that may shrink at its minimum — where 9.7 leaves each
1571
+ * of them at that, whatever their sizes. And one of them is wider than its
1572
+ * hypothetical size (9.7, step 2), which no sharing out of what a line is
1573
+ * short of makes an item: Yoga is asked for the line with nothing
1574
+ * shrinking, where each item is that size. Any other line is put back as
1575
+ * it was, the two layouts that took being the cost of one that is short of
1576
+ * room at its minimums and was not shared out to them; a line with room
1577
+ * for its minimums, or one whose every item sits at its own, has cost the
1578
+ * sum of its sizes.
1579
+ *
1580
+ * An automatic minimum is no part of it: an item held at one is frozen
1581
+ * already (`holdAt`), and one not yet held has no minimum to Yoga but its
1582
+ * padding and border, where this leaves it for `autoMinimums` to find
1583
+ * short of its content.
1584
+ */
1585
+ function stoppedLine(
1586
+ items: readonly { box: Box; node: YogaNode; laid: Laid }[],
1587
+ row: boolean,
1588
+ /** The flex box's size along the main axis. */
1589
+ room: number,
1590
+ /** The gap between two items. */
1591
+ gap: number,
1592
+ hold: Hold,
1593
+ /** Lay the flex box out, as its nodes now are. */
1594
+ ask: () => void,
1595
+ ): boolean {
1596
+ // what is between the items' border boxes: the gaps, and their margins
1597
+ let between = gap * (items.length - 1);
1598
+ // the line's size with each item that may shrink at its minimum
1599
+ let least = 0;
1600
+ let shrinking = 0;
1601
+ let over = false;
1602
+ for (const { box, laid } of items) {
1603
+ const style = box.style;
1604
+ // an `auto` margin is none on a line with no room (8.1)
1605
+ if (row) {
1606
+ if (style.marginLeft !== AUTO) between += box.marginLeft;
1607
+ if (style.marginRight !== AUTO) between += box.marginRight;
1608
+ } else {
1609
+ if (style.marginTop !== AUTO) between += box.marginTop;
1610
+ if (style.marginBottom !== AUTO) between += box.marginBottom;
1611
+ }
1612
+ if (!shrinks(box, laid)) least += laid.main;
1613
+ else {
1614
+ const min = leastOf(box, laid, row);
1615
+ shrinking += 1;
1616
+ least += min;
1617
+ if (laid.main > min + 0.01) over = true;
1618
+ }
1619
+ }
1620
+ // two at the least: what is left of a sum of one, less it, is 0 exactly.
1621
+ // A line within a thousandth of a pixel of room is short of it: what
1622
+ // Yoga makes of that is as far out, and the minimums are that near
1623
+ if (shrinking < 2 || !over || !(between + least > room - SHORT)) {
1624
+ return false;
1625
+ }
1626
+
1627
+ const stopped = items.filter(({ box, laid }) => shrinks(box, laid));
1628
+ for (const { node } of stopped) node.setFlexShrink(0);
1629
+ ask();
1630
+ // each item its hypothetical size, which one that does not shrink is on
1631
+ // a line short of room — where Yoga may have grown it, on a line it took
1632
+ // for having some
1633
+ let wider = false;
1634
+ least = 0;
1635
+ for (const { box, node, laid } of items) {
1636
+ const hypothetical = row
1637
+ ? node.getComputedWidth()
1638
+ : node.getComputedHeight();
1639
+ if (!shrinks(box, laid)) least += hypothetical;
1640
+ else {
1641
+ least += leastOf(box, laid, row);
1642
+ if (laid.main > hypothetical && !nearly(laid.main, hypothetical)) {
1643
+ wider = true;
1644
+ }
1645
+ }
1646
+ }
1647
+ const freeze = wider && between + least > room - SHORT;
1648
+ for (const { box, node, laid } of stopped) {
1649
+ if (freeze) freezeAt(node, laid, row, leastOf(box, laid, row), hold);
1650
+ else node.setFlexShrink(box.style.flexShrink);
1651
+ }
1652
+ ask();
1653
+ return true;
1654
+ }
1655
+
1656
+ /** Whether Yoga may shrink an item: it has a shrink factor, and is not
1657
+ * frozen (`freezeAt`). */
1658
+ function shrinks(box: Box, laid: Laid): boolean {
1659
+ return box.style.flexShrink > 0 && Number.isNaN(laid.held);
1660
+ }
1661
+
1662
+ /** How near to its room a line may be and be short of it (`stoppedLine`). */
1663
+ const SHORT = 0.001;
1664
+
1665
+ /** The least an item may be along the main axis, its border box: the
1666
+ * minimum of its own, and no less than its padding and borders. */
1667
+ function leastOf(box: Box, laid: Laid, row: boolean): number {
1668
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
1669
+ return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
1670
+ }
1671
+
1065
1672
  /** An item's min-content width, its border box's, taken the once: its
1066
1673
  * content's, whatever width it has of its own (`keywordWidth`'s too). */
1067
1674
  function minContentOf(box: Box, ctx: LayoutContext, laid: Laid): number {