@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,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,63 @@ 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.
594
+ const given = definite ?? (column ? height : null);
595
+ const shares =
596
+ box.kind === 'flex' &&
597
+ given !== null &&
598
+ !(
599
+ Math.abs(naturalHeight(box, ctx, width, containing, laid) - given) <= 0.01
600
+ );
601
+ if (given !== null) {
602
+ const inner = Math.max(0, given - box.verticalExtra);
603
+ if (Object.is(inner, percentBaseInside(box))) {
604
+ // the height it has of its own
605
+ } else if (shares || (definite !== null && percentHeightsIn(box))) {
606
+ tall = inner;
522
607
  }
523
608
  }
524
609
  // `measureBox` lays the box out at (0, 0); re-running it at the final width
525
610
  // 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);
611
+ // one whose result is kept — or no pass, where the last layout was at
612
+ // this width and this height. An item Yoga never measured has none, NaN,
613
+ // and is laid out.
614
+ if (sameWidth && Object.is(laid.tall, tall)) box.height = laid.height;
615
+ else if (Number.isNaN(tall)) {
616
+ layoutNaturally(box, ctx, width, containing, laid);
617
+ } else {
618
+ // what is in it takes its percentages of a definite height alone (9.8)
619
+ const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
620
+ heights.set(box, tall);
621
+ try {
622
+ ctx.layoutSubtree(box, width, containing);
623
+ } finally {
624
+ heights.delete(box);
625
+ }
626
+ laid.width = box.width;
627
+ laid.height = box.height;
628
+ laid.tall = tall;
629
+ }
530
630
  // A stretched item is taller than its content, and the box has to say so
531
631
  // or its background stops short of the row — and it is no taller than its
532
632
  // line where its content is, or than what its ratio makes of its width:
533
633
  // its height is the line's (CSS Flexbox 9.4, step 11).
534
634
  if (height > box.height || column || definite !== null) box.height = height;
635
+ // and a button's content is centred in the height it came to
636
+ centreButton(box);
535
637
  moveTo(box, x, y);
536
638
  }
537
639
 
@@ -561,8 +663,11 @@ function applyItem(
561
663
  ctx: LayoutContext,
562
664
  containingWidth: number,
563
665
  laid: Laid,
564
- /** Whether the flex box has a height of its own. */
666
+ /** Whether the flex box has a height its items are laid out in. */
565
667
  tall: boolean,
668
+ /** Whether that height is definite, and a percentage may be of it: one a
669
+ * column flexed the box to is not (`USED_HEIGHT`). */
670
+ own: boolean,
566
671
  ): void {
567
672
  const style = box.style;
568
673
  laid.stretch = containingWidth - box.marginLeft - box.marginRight;
@@ -590,9 +695,20 @@ function applyItem(
590
695
  if (style.width !== AUTO) {
591
696
  laid.set = setLength(node, true, style.width, containingWidth, across);
592
697
  }
593
- if (style.height !== AUTO) setLength(node, false, style.height, NaN, down);
698
+ // a percentage of a height that is not definite is `auto` (CSS 2.1 10.5),
699
+ // which Yoga makes of one where the flex box has no height at all
700
+ if (style.height !== AUTO && (own || !tall || !isPct(style.height))) {
701
+ setLength(node, false, style.height, NaN, down);
702
+ }
703
+ // the least it may be along each axis, its border box's, as Yoga is
704
+ // told it
705
+ let leastWidth = NaN;
706
+ let leastHeight = NaN;
594
707
  const minWidth = resolveOrNull(style.minWidth, containingWidth);
595
- if (minWidth !== null) node.setMinWidth(minWidth + across);
708
+ if (minWidth !== null) {
709
+ leastWidth = minWidth + across;
710
+ node.setMinWidth(leastWidth);
711
+ }
596
712
  if (style.maxWidth !== 'none') {
597
713
  const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
598
714
  if (maxWidth !== null) node.setMaxWidth(maxWidth + across);
@@ -611,17 +727,24 @@ function applyItem(
611
727
  laid.set = size(style.widthKeyword);
612
728
  node.setWidth(laid.set);
613
729
  }
614
- if (style.minWidthKeyword) node.setMinWidth(size(style.minWidthKeyword));
730
+ if (style.minWidthKeyword) {
731
+ leastWidth = size(style.minWidthKeyword);
732
+ node.setMinWidth(leastWidth);
733
+ }
615
734
  if (style.maxWidthKeyword) node.setMaxWidth(size(style.maxWidthKeyword));
616
735
  }
617
736
  const minHeight = resolveOrNull(style.minHeight, NaN);
618
- if (minHeight !== null) node.setMinHeight(minHeight + down);
737
+ if (minHeight !== null) {
738
+ leastHeight = minHeight + down;
739
+ node.setMinHeight(leastHeight);
740
+ }
619
741
  if (style.maxHeight !== 'none') {
620
742
  const maxHeight = resolveOrNull(style.maxHeight, NaN);
621
743
  if (maxHeight !== null) node.setMaxHeight(maxHeight + down);
622
744
  }
623
745
 
624
746
  const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
747
+ laid.least = row ? leastWidth : leastHeight;
625
748
  // Yoga takes a length for a basis only where the flex box's main size is
626
749
  // definite, and else the item's own size along it: down a column of no
627
750
  // height of its own it read `flex: 0 0 3rem` as the item's height, or
@@ -653,6 +776,9 @@ function applyItem(
653
776
  setBasis(columnBasis);
654
777
  } else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
655
778
  // Yoga's own
779
+ } else if (isPct(style.flexBasis) && !row && tall && !own) {
780
+ // a percentage of a main size that is not definite is `content` (CSS
781
+ // Flexbox 7.2.3): Yoga's own, down a column
656
782
  } else if (isPct(style.flexBasis)) {
657
783
  // a percentage of the main size, which is known across a row
658
784
  const basis = style.flexBasis;
@@ -738,7 +864,13 @@ function applyItem(
738
864
  const width = meant(w, laid.set - extra, laid.stretch - extra);
739
865
  const given = hm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(h);
740
866
  const exact = wm === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width);
741
- const through = widthThrough && given && !exact ? widthThrough(h) : null;
867
+ // the height it was flexed to, that is: one its automatic minimum
868
+ // holds it at (`holdAt`) is its content's, which a replaced element's
869
+ // width follows through its ratio and no other's does (CSS Sizing 4,
870
+ // 5.1)
871
+ const flexed = box.kind === 'replaced' || !laid.auto;
872
+ const through =
873
+ widthThrough && given && !exact && flexed ? widthThrough(h) : null;
742
874
  // along a row, a replaced element is its own width wherever there is
743
875
  // room for less — its flex base size is not fitted to the room (9.2)
744
876
  // — and across a column it is fitted, as any item is
@@ -758,10 +890,8 @@ function applyItem(
758
890
  }
759
891
  let answer = answers.get(inner);
760
892
  if (answer === undefined) {
761
- answer = measureBox(box, ctx, inner);
893
+ answer = measureBox(box, ctx, inner, containingWidth, laid);
762
894
  answers.set(inner, answer);
763
- laid.width = box.width;
764
- laid.height = box.height;
765
895
  }
766
896
  return answer;
767
897
  });
@@ -777,12 +907,28 @@ const MAX_CONTENT = new WeakMap<Box, number>();
777
907
  interface Laid {
778
908
  width: number;
779
909
  height: number;
910
+ /** The content height that layout was made at, where the flex layout gave
911
+ * the item one (`FLEXED_HEIGHT`, `USED_HEIGHT`), and NaN where its
912
+ * content's own. */
913
+ tall: number;
780
914
  /** The border-box width this engine set on the item's node — a length,
781
915
  * or the content's for `fit-content` and its kin — or NaN. */
782
916
  set: number;
783
917
  /** Its border-box width stretched across its container: the container's
784
918
  * content width, less its margins. */
785
919
  stretch: number;
920
+ /** Its border box's size along the main axis in the layout Yoga last
921
+ * made of the flex box, as Yoga has it. */
922
+ main: number;
923
+ /** The least it may be along the main axis, where its own style says:
924
+ * the minimum this engine set on its node, or NaN. */
925
+ least: number;
926
+ /** The size along the main axis it is frozen at, inflexible (`freezeAt`),
927
+ * or NaN. */
928
+ held: number;
929
+ /** Whether that is its automatic minimum (`holdAt`), its content's size,
930
+ * and not a minimum the line stopped at (`stoppedLine`). */
931
+ auto: boolean;
786
932
  }
787
933
 
788
934
  /**
@@ -855,14 +1001,236 @@ function measureBox(
855
1001
  box: Box,
856
1002
  ctx: LayoutContext,
857
1003
  inner: number,
1004
+ /** The flex box's content width, the item's containing block's. */
1005
+ containing: number,
1006
+ laid: Laid,
858
1007
  ): { width: number; height: number } {
859
- ctx.layoutSubtree(box, inner + box.horizontalExtra);
1008
+ // its padding as Yoga has it, a percentage of the flex box's width: a
1009
+ // probe of its content's width leaves a percentage at none
1010
+ resolveEdges(box, containing);
1011
+ const width = inner + box.horizontalExtra;
1012
+ // its height at this width is known, where a layout since has left it
1013
+ // another: the answer is the height, and the layout `layoutItemAt`'s
1014
+ const kept = keptNatural(box, ctx, width, containing);
1015
+ if (kept !== null && kept.serial !== box.layoutSerial) {
1016
+ return {
1017
+ width: inner,
1018
+ height: Math.max(0, kept.height - box.verticalExtra),
1019
+ };
1020
+ }
1021
+ layoutNaturally(box, ctx, width, containing, laid);
860
1022
  return {
861
1023
  width: inner,
862
1024
  height: Math.max(0, box.height - box.verticalExtra),
863
1025
  };
864
1026
  }
865
1027
 
1028
+ /** An item's height as its content's own makes it at a border-box width,
1029
+ * where this pass has laid it out so (`layoutNaturally`), and else NaN. */
1030
+ function naturalHeight(
1031
+ box: Box,
1032
+ ctx: LayoutContext,
1033
+ width: number,
1034
+ containing: number,
1035
+ laid: Laid,
1036
+ ): number {
1037
+ if (Math.abs(laid.width - width) <= 0.01 && Number.isNaN(laid.tall)) {
1038
+ return laid.height;
1039
+ }
1040
+ return keptNatural(box, ctx, width, containing)?.height ?? NaN;
1041
+ }
1042
+
1043
+ /**
1044
+ * Give an item the layout its content's own height makes of it at a
1045
+ * border-box width, and note it (`Laid`): the one this pass has made of it
1046
+ * there already, where it still has it.
1047
+ *
1048
+ * A flex box laid out a second time — at the height a stretch gave it, its
1049
+ * own flex layout made anew (`layoutItemAt`) — asks of each item what it
1050
+ * asked the first time, and the item's layout at a width is what it was.
1051
+ * Kept for the pass, a card laid out again for its height lays nothing in
1052
+ * it out that is where it was; made again at every layout of the box
1053
+ * around it, a card in a card in a card laid its innermost out twice a
1054
+ * level.
1055
+ */
1056
+ function layoutNaturally(
1057
+ box: Box,
1058
+ ctx: LayoutContext,
1059
+ width: number,
1060
+ /** The flex box's content width, the item's containing block's. */
1061
+ containing: number,
1062
+ laid: Laid,
1063
+ ): void {
1064
+ const kept = keptNatural(box, ctx, width, containing);
1065
+ if (kept !== null && kept.serial === box.layoutSerial) {
1066
+ // put back, where the flex layout stretched it
1067
+ box.height = kept.height;
1068
+ } else {
1069
+ const at = ctx.positioned.length;
1070
+ ctx.layoutSubtree(box, width, containing);
1071
+ // a replaced box is sized, not laid out, and has no layout to count
1072
+ if (box.kind !== 'replaced') {
1073
+ NATURAL.set(box, {
1074
+ ctx,
1075
+ serial: box.layoutSerial,
1076
+ width,
1077
+ containing: readsContaining(box) ? containing : NaN,
1078
+ base: box.percentHeightBase,
1079
+ edges: edgesOf(box),
1080
+ height: box.height,
1081
+ queued:
1082
+ ctx.positioned.length > at ? { at, first: ctx.positioned[at] } : null,
1083
+ drawn: NaN,
1084
+ content: NaN,
1085
+ });
1086
+ }
1087
+ }
1088
+ laid.width = box.width;
1089
+ laid.height = box.height;
1090
+ laid.tall = NaN;
1091
+ }
1092
+
1093
+ /**
1094
+ * The layout this pass made of an item at its content's own height
1095
+ * (`layoutNaturally`), where one at `width` now would be the same: the same
1096
+ * width, the same edges — a percentage among them resolved, as the layout
1097
+ * leaves them — the same containing block's width where a limit on its own
1098
+ * is a percentage of it, and the same height for its percentages, where its
1099
+ * own style reads that at all, which the flex box around it coming to a
1100
+ * definite height changes for every item in it. Its `height` is the item's
1101
+ * there whatever has laid it out since, and the layout is the one the box
1102
+ * has where nothing has (`Box.layoutSerial`).
1103
+ */
1104
+ function keptNatural(
1105
+ box: Box,
1106
+ ctx: LayoutContext,
1107
+ width: number,
1108
+ containing: number,
1109
+ ): Natural | null {
1110
+ const kept = NATURAL.get(box);
1111
+ if (kept === undefined || kept.ctx !== ctx || kept.width !== width) {
1112
+ return null;
1113
+ }
1114
+ if (!Number.isNaN(kept.containing) && kept.containing !== containing) {
1115
+ return null;
1116
+ }
1117
+ if (!Object.is(kept.base, box.percentHeightBase) && readsPercentBase(box)) {
1118
+ return null;
1119
+ }
1120
+ // the boxes in it that are positioned last are still waiting to be: a
1121
+ // `line-clamp: auto` box laid out a second time forgets the ones its
1122
+ // first layout found
1123
+ if (kept.queued && ctx.positioned[kept.queued.at] !== kept.queued.first) {
1124
+ return null;
1125
+ }
1126
+ const edges = kept.edges;
1127
+ return edges[0] === box.marginTop &&
1128
+ edges[1] === box.marginRight &&
1129
+ edges[2] === box.marginBottom &&
1130
+ edges[3] === box.marginLeft &&
1131
+ edges[4] === box.borderTop &&
1132
+ edges[5] === box.borderRight &&
1133
+ edges[6] === box.borderBottom &&
1134
+ edges[7] === box.borderLeft &&
1135
+ edges[8] === box.padTop &&
1136
+ edges[9] === box.padRight &&
1137
+ edges[10] === box.padBottom &&
1138
+ edges[11] === box.padLeft
1139
+ ? kept
1140
+ : null;
1141
+ }
1142
+
1143
+ /** A layout of an item at its content's own height (`layoutNaturally`): in
1144
+ * which pass, which of the box's layouts it was, and what it was made
1145
+ * from. */
1146
+ interface Natural {
1147
+ ctx: LayoutContext;
1148
+ serial: number;
1149
+ /** The border-box width it was asked for at. */
1150
+ width: number;
1151
+ /** Its containing block's width, where a limit on its own width is a
1152
+ * percentage of that (`readsContaining`), and else NaN: a nest of flex
1153
+ * boxes is laid out in rooms of many widths, and an item with no such
1154
+ * limit is laid out the same in each. */
1155
+ containing: number;
1156
+ /** The height its percentages were of. */
1157
+ base: number;
1158
+ /** Its margins, borders and padding once laid out (`edgesOf`). */
1159
+ edges: number[];
1160
+ height: number;
1161
+ /** The first of the out-of-flow boxes that layout queued to be
1162
+ * positioned (`LayoutContext.positioned`) and where; null for none. */
1163
+ queued: { at: number; first: LayoutContext['positioned'][number] } | null;
1164
+ /** What `autoMinimums` read from that layout, NaN until it has: how wide
1165
+ * it drew, and how far down its content came, each its border box's. */
1166
+ drawn: number;
1167
+ content: number;
1168
+ }
1169
+
1170
+ const NATURAL = new WeakMap<Box, Natural>();
1171
+
1172
+ /** Note what was read from the layout an item has, where that is the one
1173
+ * its content's own height makes of it at `width`. */
1174
+ function remember(
1175
+ box: Box,
1176
+ ctx: LayoutContext,
1177
+ width: number,
1178
+ containing: number,
1179
+ field: 'drawn' | 'content',
1180
+ value: number,
1181
+ ): void {
1182
+ const made = keptNatural(box, ctx, width, containing);
1183
+ if (made !== null && made.serial === box.layoutSerial) made[field] = value;
1184
+ }
1185
+
1186
+ /** A box's edges, which a layout resolves again against the width it is
1187
+ * given: margins, borders, padding, each from the top round. */
1188
+ function edgesOf(box: Box): number[] {
1189
+ return [
1190
+ box.marginTop,
1191
+ box.marginRight,
1192
+ box.marginBottom,
1193
+ box.marginLeft,
1194
+ box.borderTop,
1195
+ box.borderRight,
1196
+ box.borderBottom,
1197
+ box.borderLeft,
1198
+ box.padTop,
1199
+ box.padRight,
1200
+ box.padBottom,
1201
+ box.padLeft,
1202
+ ];
1203
+ }
1204
+
1205
+ /** Whether the width a box is laid out at is held to a percentage of its
1206
+ * containing block's: a least or greatest width that is one. */
1207
+ function readsContaining(box: Box): boolean {
1208
+ const style = box.style;
1209
+ return (
1210
+ isPct(style.minWidth) ||
1211
+ (style.maxWidth !== 'none' && isPct(style.maxWidth))
1212
+ );
1213
+ }
1214
+
1215
+ /** Whether a box's own layout reads the height its percentages are of: a
1216
+ * height, a least or greatest one, or an offset that is a percentage, a
1217
+ * height that is a keyword — `stretch` fills it — or an anonymous box,
1218
+ * which hands it on to what is in it (`percentBaseInside`). */
1219
+ function readsPercentBase(box: Box): boolean {
1220
+ if (!box.el && !box.pseudo) return true;
1221
+ const style = box.style;
1222
+ return (
1223
+ isPct(style.height) ||
1224
+ isPct(style.minHeight) ||
1225
+ (style.maxHeight !== 'none' && isPct(style.maxHeight)) ||
1226
+ isPct(style.top) ||
1227
+ isPct(style.bottom) ||
1228
+ style.heightKeyword !== null ||
1229
+ style.minHeightKeyword !== null ||
1230
+ style.maxHeightKeyword !== null
1231
+ );
1232
+ }
1233
+
866
1234
  /** The width an item's content is measured at: the width Yoga gives it
867
1235
  * exactly, or its max-content width, no wider than any it may take up to
868
1236
  * (CSS Flexbox 9.2). */
@@ -903,6 +1271,7 @@ function autoMinimums(
903
1271
  row: boolean,
904
1272
  ctx: LayoutContext,
905
1273
  containingWidth: number,
1274
+ hold: Hold,
906
1275
  ): boolean {
907
1276
  let changed = false;
908
1277
  for (const { box, node, laid } of items) {
@@ -915,17 +1284,18 @@ function autoMinimums(
915
1284
  continue;
916
1285
  }
917
1286
  const width = meant(
918
- node.getComputedWidth(),
1287
+ row ? laid.main : node.getComputedWidth(),
919
1288
  laid.set,
920
1289
  laid.stretch,
921
1290
  laid.width,
1291
+ row ? laid.held : NaN,
922
1292
  );
923
1293
  // a replaced item's content along a row is its natural width
924
1294
  if (row && box.kind === 'replaced') {
925
1295
  if (style.minWidth !== AUTO || style.minWidthKeyword) continue;
926
1296
  const least = replacedMinimum(box, containingWidth);
927
1297
  if (least === null || width >= least - 0.01) continue;
928
- node.setMinWidth(least);
1298
+ holdAt(node, laid, row, least, hold);
929
1299
  changed = true;
930
1300
  continue;
931
1301
  }
@@ -952,7 +1322,7 @@ function autoMinimums(
952
1322
  if (most !== null) least = Math.min(least, most + extra);
953
1323
  }
954
1324
  if (width >= least - 0.01) continue;
955
- node.setMinWidth(least);
1325
+ holdAt(node, laid, row, least, hold);
956
1326
  changed = true;
957
1327
  continue;
958
1328
  }
@@ -980,8 +1350,8 @@ function autoMinimums(
980
1350
  }
981
1351
  if (width >= least - 0.01) continue;
982
1352
  least = Math.min(least, minContentOf(box, ctx, laid));
983
- if (node.getComputedWidth() >= least - 0.01) continue;
984
- node.setMinWidth(least);
1353
+ if (laid.main >= least - 0.01) continue;
1354
+ holdAt(node, laid, row, least, hold);
985
1355
  changed = true;
986
1356
  continue;
987
1357
  }
@@ -995,16 +1365,38 @@ function autoMinimums(
995
1365
  ) {
996
1366
  continue;
997
1367
  }
998
- if (!(Math.abs(laid.width - width) <= 0.01)) {
999
- ctx.layoutSubtree(box, width);
1000
- laid.width = box.width;
1001
- laid.height = box.height;
1368
+ // What its content's own height makes of it at this width — how wide
1369
+ // that drew, along a row, and how far down, along a column — is read
1370
+ // from that layout, which the item is given where it has another: or
1371
+ // is what this pass read from it before, where the flex box is laid
1372
+ // out a second time (`layoutNaturally`)
1373
+ const kept = keptNatural(box, ctx, width, containingWidth);
1374
+ const read = kept === null ? NaN : row ? kept.drawn : kept.content;
1375
+ if (Number.isNaN(read) && !(Math.abs(laid.width - width) <= 0.01)) {
1376
+ layoutNaturally(box, ctx, width, containingWidth, laid);
1002
1377
  }
1378
+ const height = Number.isNaN(read) ? laid.height : kept!.height;
1003
1379
  let least: number;
1004
1380
  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;
1381
+ // or than what it drew at the width it has: nothing overflows it —
1382
+ // unless a line in it fits for being cut short by `text-overflow`,
1383
+ // which is how the line is drawn and no part of how wide it is: a
1384
+ // button whose label is a `truncate` span is no narrower than the
1385
+ // label, where it was shrunk and the label cut. No width, then, that
1386
+ // it drew at, which is what is kept of it
1387
+ let drawn = read;
1388
+ if (Number.isNaN(drawn)) {
1389
+ const seen = { cut: false };
1390
+ drawn = intrinsicWidth(box, seen) + box.horizontalExtra;
1391
+ if (seen.cut) drawn = Infinity;
1392
+ remember(box, ctx, width, containingWidth, 'drawn', drawn);
1393
+ }
1394
+ if (
1395
+ Number.isFinite(drawn) &&
1396
+ transferredWidth(box, drawn) <= width + 0.5
1397
+ ) {
1398
+ continue;
1399
+ }
1008
1400
  // within what a ratio makes of its least and greatest heights (4.5's
1009
1401
  // content size suggestion)
1010
1402
  least = transferredWidth(box, minContentOf(box, ctx, laid));
@@ -1013,55 +1405,257 @@ function autoMinimums(
1013
1405
  const most = resolveOrNull(style.maxWidth, containingWidth);
1014
1406
  if (most !== null) least = Math.min(least, most + extra);
1015
1407
  }
1016
- if (node.getComputedWidth() >= least - 0.01) continue;
1017
- node.setMinWidth(least);
1408
+ if (laid.main >= least - 0.01) continue;
1409
+ holdAt(node, laid, row, least, hold);
1018
1410
  } else {
1019
1411
  // its height, or where it has one of its own and its content comes to
1020
1412
  // less, its content's: the lesser of the two (4.5)
1021
- let content: number;
1022
- if (box.kind === 'replaced') content = laid.height;
1413
+ let content = read;
1414
+ if (!Number.isNaN(content)) {
1415
+ // read before
1416
+ } else if (box.kind === 'replaced') content = laid.height;
1023
1417
  else if (style.height !== AUTO && percentHeightsIn(box)) {
1024
1418
  // what its content comes to where it has no height to take
1025
1419
  // percentages of, as an intrinsic size is measured: laid out so
1026
1420
  // apart, and the final pass lays it out again
1027
1421
  FLEXED_HEIGHT.set(box, NaN);
1028
1422
  try {
1029
- ctx.layoutSubtree(box, width);
1423
+ ctx.layoutSubtree(box, width, containingWidth);
1030
1424
  } finally {
1031
1425
  FLEXED_HEIGHT.delete(box);
1032
1426
  }
1033
- content = contentBottom(box) + box.verticalExtra;
1427
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1034
1428
  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
- );
1429
+ } else {
1430
+ content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1431
+ remember(box, ctx, width, containingWidth, 'content', content);
1046
1432
  }
1047
1433
  // within what the ratio makes of its least and greatest widths
1048
1434
  if (box.kind !== 'replaced') {
1049
1435
  content = transferredHeight(box, content, containingWidth);
1050
1436
  }
1051
- least = Math.min(laid.height, content);
1437
+ least = Math.min(height, content);
1052
1438
  const extra = style.boxSizing === 'border-box' ? 0 : box.verticalExtra;
1053
1439
  if (style.maxHeight !== 'none') {
1054
1440
  const most = resolveOrNull(style.maxHeight, NaN);
1055
1441
  if (most !== null) least = Math.min(least, most + extra);
1056
1442
  }
1057
- if (node.getComputedHeight() >= least - 0.01) continue;
1058
- node.setMinHeight(least);
1443
+ if (laid.main >= least - 0.01) continue;
1444
+ holdAt(node, laid, row, least, hold);
1059
1445
  }
1060
1446
  changed = true;
1061
1447
  }
1062
1448
  return changed;
1063
1449
  }
1064
1450
 
1451
+ /** A laid-out item's content height, its border box's: no less than its
1452
+ * width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
1453
+ function ratioContent(box: Box, content: number): number {
1454
+ const aspect = box.style.aspectRatio;
1455
+ if (!aspect || box.kind === 'replaced') return content;
1456
+ return Math.max(
1457
+ content,
1458
+ box.style.boxSizing === 'border-box'
1459
+ ? box.width / aspect.ratio
1460
+ : box.contentWidth / aspect.ratio + box.verticalExtra,
1461
+ );
1462
+ }
1463
+
1464
+ /** How a flex box holds its items to their least sizes (`holdAt`). */
1465
+ interface Hold {
1466
+ /** Whether an item is frozen at its least size: on the one line of a
1467
+ * box that does not wrap. */
1468
+ frozen: boolean;
1469
+ /** Whether Yoga takes a length for a basis along the main axis
1470
+ * (`applyItem`'s `setBasis`), and else the item's own size. */
1471
+ basis: boolean;
1472
+ }
1473
+
1474
+ /**
1475
+ * Hold an item to its least size along the main axis, the size Yoga gave
1476
+ * it being under that. It is frozen there, as CSS Flexbox freezes an item
1477
+ * its minimum stops (9.7, step 4): inflexible, at that size, the rest of
1478
+ * the line sharing what is left. Handed the minimum as a `min-width`
1479
+ * alone, Yoga makes it the item's flex base size, which is two things the
1480
+ * specification does not do:
1481
+ *
1482
+ * - where the line has room to share out, the item takes its share on top
1483
+ * of the minimum: of three `flex: 1` items in 300 pixels, the one with a
1484
+ * word 200 wide was 233, where it is 200 and the others 50 each;
1485
+ * - where every item of a line is held, Yoga divides what the line is
1486
+ * short of by what is left of the shrink factors once it has taken each
1487
+ * item's away — nothing, or a float's rounding of it. Two items 110.992
1488
+ * and 62.706 pixels wide in a row too narrow for them came out billions
1489
+ * of pixels wide, and 31.3 and 17.7 came out as they are: by the digits
1490
+ * of their sizes, which are a text's widths in whatever font it is set.
1491
+ *
1492
+ * On the one line of a box that does not wrap, an item Yoga had under its
1493
+ * minimum has no more room when another is held too, so frozen is where it
1494
+ * ends. In a box that wraps it may have: the minimums move the line breaks
1495
+ * (9.3), and an item held on a crowded line may be on one with room to
1496
+ * grow in once they have. There the minimum is left to Yoga, as a minimum;
1497
+ * only a line of one item is short of room there, and what Yoga takes away
1498
+ * from its shrink factors is exactly what it added.
1499
+ */
1500
+ function holdAt(
1501
+ node: YogaNode,
1502
+ laid: Laid,
1503
+ row: boolean,
1504
+ least: number,
1505
+ hold: Hold,
1506
+ ): void {
1507
+ if (row) node.setMinWidth(least);
1508
+ else node.setMinHeight(least);
1509
+ if (!hold.frozen) return;
1510
+ laid.auto = true;
1511
+ freezeAt(node, laid, row, least, hold);
1512
+ }
1513
+
1514
+ /** Freeze an item at a size along the main axis: inflexible, and that size
1515
+ * its flex base size. */
1516
+ function freezeAt(
1517
+ node: YogaNode,
1518
+ laid: Laid,
1519
+ row: boolean,
1520
+ size: number,
1521
+ hold: Hold,
1522
+ ): void {
1523
+ laid.held = size;
1524
+ node.setFlexGrow(0);
1525
+ node.setFlexShrink(0);
1526
+ if (hold.basis) node.setFlexBasis(size);
1527
+ else if (row) node.setWidth(size);
1528
+ else node.setHeight(size);
1529
+ }
1530
+
1531
+ /**
1532
+ * Put right the one line of a box that does not wrap, where every item of
1533
+ * it that may shrink is stopped by its minimum and Yoga made them wider for
1534
+ * it; whether Yoga was asked again. The line is laid out with each of them
1535
+ * frozen at its minimum, which is where CSS Flexbox leaves them (9.7, step
1536
+ * 4): the line is short of room with every one of them as small as it
1537
+ * goes.
1538
+ *
1539
+ * Yoga shares what a line is short of in two passes. The first takes each
1540
+ * item a minimum stops out of the sum of the scaled shrink factors, one
1541
+ * subtraction an item, and the second divides what the line is still short
1542
+ * of by what is left of the sum. With every item stopped that is nothing:
1543
+ * `(a + b) - a - b`, in float32s, which is 0 for some sizes and a rounding
1544
+ * either side of it for others, 2^-18 for sizes about a hundred. Yoga
1545
+ * 3.2.1 tests for 0, and the guard it has had since, a sum under 1e-6, is
1546
+ * under that rounding (react/yoga#1665, #1974). A negative rounding sends
1547
+ * every item under its minimum, which stops it, and a positive one makes
1548
+ * each of them wider by billions of pixels: `width: 110.992px; min-width:
1549
+ * 110.992px` beside `62.705625px` of the same, in a row 50 wide, were
1550
+ * 3599091712 and 2033329408 wide, where 100 and 60 were as they are. So
1551
+ * were two items with no minimum but their padding, and two with
1552
+ * `min-width: 0` beside one that does not shrink and is wider than the row
1553
+ * alone.
1554
+ *
1555
+ * A minimum of the item's own is Yoga's to hold it to, so this looks at
1556
+ * its answer, for a line that is two things at once. It is short of room
1557
+ * with every item that may shrink at its minimum — where 9.7 leaves each
1558
+ * of them at that, whatever their sizes. And one of them is wider than its
1559
+ * hypothetical size (9.7, step 2), which no sharing out of what a line is
1560
+ * short of makes an item: Yoga is asked for the line with nothing
1561
+ * shrinking, where each item is that size. Any other line is put back as
1562
+ * it was, the two layouts that took being the cost of one that is short of
1563
+ * room at its minimums and was not shared out to them; a line with room
1564
+ * for its minimums, or one whose every item sits at its own, has cost the
1565
+ * sum of its sizes.
1566
+ *
1567
+ * An automatic minimum is no part of it: an item held at one is frozen
1568
+ * already (`holdAt`), and one not yet held has no minimum to Yoga but its
1569
+ * padding and border, where this leaves it for `autoMinimums` to find
1570
+ * short of its content.
1571
+ */
1572
+ function stoppedLine(
1573
+ items: readonly { box: Box; node: YogaNode; laid: Laid }[],
1574
+ row: boolean,
1575
+ /** The flex box's size along the main axis. */
1576
+ room: number,
1577
+ /** The gap between two items. */
1578
+ gap: number,
1579
+ hold: Hold,
1580
+ /** Lay the flex box out, as its nodes now are. */
1581
+ ask: () => void,
1582
+ ): boolean {
1583
+ // what is between the items' border boxes: the gaps, and their margins
1584
+ let between = gap * (items.length - 1);
1585
+ // the line's size with each item that may shrink at its minimum
1586
+ let least = 0;
1587
+ let shrinking = 0;
1588
+ let over = false;
1589
+ for (const { box, laid } of items) {
1590
+ const style = box.style;
1591
+ // an `auto` margin is none on a line with no room (8.1)
1592
+ if (row) {
1593
+ if (style.marginLeft !== AUTO) between += box.marginLeft;
1594
+ if (style.marginRight !== AUTO) between += box.marginRight;
1595
+ } else {
1596
+ if (style.marginTop !== AUTO) between += box.marginTop;
1597
+ if (style.marginBottom !== AUTO) between += box.marginBottom;
1598
+ }
1599
+ if (!shrinks(box, laid)) least += laid.main;
1600
+ else {
1601
+ const min = leastOf(box, laid, row);
1602
+ shrinking += 1;
1603
+ least += min;
1604
+ if (laid.main > min + 0.01) over = true;
1605
+ }
1606
+ }
1607
+ // two at the least: what is left of a sum of one, less it, is 0 exactly.
1608
+ // A line within a thousandth of a pixel of room is short of it: what
1609
+ // Yoga makes of that is as far out, and the minimums are that near
1610
+ if (shrinking < 2 || !over || !(between + least > room - SHORT)) {
1611
+ return false;
1612
+ }
1613
+
1614
+ const stopped = items.filter(({ box, laid }) => shrinks(box, laid));
1615
+ for (const { node } of stopped) node.setFlexShrink(0);
1616
+ ask();
1617
+ // each item its hypothetical size, which one that does not shrink is on
1618
+ // a line short of room — where Yoga may have grown it, on a line it took
1619
+ // for having some
1620
+ let wider = false;
1621
+ least = 0;
1622
+ for (const { box, node, laid } of items) {
1623
+ const hypothetical = row
1624
+ ? node.getComputedWidth()
1625
+ : node.getComputedHeight();
1626
+ if (!shrinks(box, laid)) least += hypothetical;
1627
+ else {
1628
+ least += leastOf(box, laid, row);
1629
+ if (laid.main > hypothetical && !nearly(laid.main, hypothetical)) {
1630
+ wider = true;
1631
+ }
1632
+ }
1633
+ }
1634
+ const freeze = wider && between + least > room - SHORT;
1635
+ for (const { box, node, laid } of stopped) {
1636
+ if (freeze) freezeAt(node, laid, row, leastOf(box, laid, row), hold);
1637
+ else node.setFlexShrink(box.style.flexShrink);
1638
+ }
1639
+ ask();
1640
+ return true;
1641
+ }
1642
+
1643
+ /** Whether Yoga may shrink an item: it has a shrink factor, and is not
1644
+ * frozen (`freezeAt`). */
1645
+ function shrinks(box: Box, laid: Laid): boolean {
1646
+ return box.style.flexShrink > 0 && Number.isNaN(laid.held);
1647
+ }
1648
+
1649
+ /** How near to its room a line may be and be short of it (`stoppedLine`). */
1650
+ const SHORT = 0.001;
1651
+
1652
+ /** The least an item may be along the main axis, its border box: the
1653
+ * minimum of its own, and no less than its padding and borders. */
1654
+ function leastOf(box: Box, laid: Laid, row: boolean): number {
1655
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
1656
+ return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
1657
+ }
1658
+
1065
1659
  /** An item's min-content width, its border box's, taken the once: its
1066
1660
  * content's, whatever width it has of its own (`keywordWidth`'s too). */
1067
1661
  function minContentOf(box: Box, ctx: LayoutContext, laid: Laid): number {