@react-x11/components 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +171 -26
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +616 -216
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +18 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -1
  22. package/dist/html/css/shapes.js +21 -1
  23. package/dist/html/css/shapes.js.map +1 -1
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +74 -12
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +15 -0
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +29 -9
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +141 -52
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +16 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +144 -23
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +11 -9
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1631 -221
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +23 -8
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +886 -324
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +35 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +118 -26
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts.map +1 -1
  64. package/dist/html/paint.js +558 -178
  65. package/dist/html/paint.js.map +1 -1
  66. package/dist/html/resources.js +14 -1
  67. package/dist/html/resources.js.map +1 -1
  68. package/dist/html/svg.d.ts +23 -7
  69. package/dist/html/svg.d.ts.map +1 -1
  70. package/dist/html/svg.js +264 -59
  71. package/dist/html/svg.js.map +1 -1
  72. package/dist/html/widgets.d.ts.map +1 -1
  73. package/dist/html/widgets.js +57 -4
  74. package/dist/html/widgets.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/code-editor/node.ts +24 -5
  77. package/src/html/css/cascade.ts +697 -221
  78. package/src/html/css/color.ts +345 -18
  79. package/src/html/css/content.ts +173 -15
  80. package/src/html/css/parse.ts +60 -8
  81. package/src/html/css/shapes.ts +35 -1
  82. package/src/html/css/style.ts +122 -17
  83. package/src/html/css/ua.ts +15 -0
  84. package/src/html/css/values.ts +23 -9
  85. package/src/html/index.ts +7 -0
  86. package/src/html/layout/block.ts +149 -58
  87. package/src/html/layout/boxes.ts +171 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1843 -212
  90. package/src/html/layout/inline.ts +27 -7
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +151 -27
  93. package/src/html/paint.ts +692 -234
  94. package/src/html/resources.ts +13 -1
  95. package/src/html/svg.ts +299 -56
  96. package/src/html/widgets.ts +61 -4
@@ -28,9 +28,10 @@ import { Yoga, layoutLoaded } from 'react-x11/yoga';
28
28
  import { AUTO, gapOf, isPct, resolve, resolveOrNull } from '../css/values.js';
29
29
  import { scrolls } from '../css/style.js';
30
30
  import { Box, PAINT_ORDER, isBlank } from './boxes.js';
31
- import { FLEXED_HEIGHT, MIN_CONTENT_PROBE, USED_HEIGHT, centreButton, clampHeight, clampWidth, contentSizedWidth, exactMinContent, intrinsicWidth, measureIntrinsicWidth, moveTo, percentBaseInside, percentHeightsIn, positionOutOfFlow, replacedRatio, transferredHeight, transferredWidth, heightFromWidth, widthFromHeight, resolveEdges, } from './block.js';
31
+ import { FLEXED_HEIGHT, MIN_CONTENT_PROBE, STRETCHED_ACROSS, USED_HEIGHT, centreButton, clampHeight, clampWidth, contentSizedWidth, exactMinContent, intrinsicWidth, measureIntrinsicWidth, moveTo, percentBaseInside, percentHeightsIn, positionOutOfFlow, replacedRatio, transferredHeight, transferredWidth, heightFromWidth, heightThroughRatio, widthFromHeight, resolveEdges, } from './block.js';
32
32
  import { layoutGrid } from './css-grid.js';
33
33
  import { firstBaselineIn } from './inline.js';
34
+ import { TABLE_CONTENT } from './table.js';
34
35
  // `react-x11/yoga` re-exports yoga's own declarations, so the node shape and
35
36
  // the enum constants are typed rather than written out here and cast at every
36
37
  // call, which is what this had to do while the engine was reached through
@@ -93,8 +94,6 @@ export function layoutFlex(box, ctx, contentWidth) {
93
94
  // and the measure that asked came back vast: a row beside such an item
94
95
  // was squeezed to wrap every word.
95
96
  const bounded = Number.isFinite(contentWidth);
96
- if (bounded)
97
- root.setWidth(contentWidth);
98
97
  // The content box's height, where it is definite: a length or a
99
98
  // percentage that resolves, or what `aspect-ratio` makes of the width —
100
99
  // less the padding and borders a `border-box` height holds, which put
@@ -105,20 +104,23 @@ export function layoutFlex(box, ctx, contentWidth) {
105
104
  const definite = percentBaseInside(box);
106
105
  const own = Number.isFinite(definite);
107
106
  const height = USED_HEIGHT.get(box) ?? (own ? definite : null);
108
- // whether its content may come to more than it is tall
109
- let capped = height !== null;
107
+ // the limits on its content box's height where it has none of its own
108
+ let floor = 0;
109
+ let ceiling = Infinity;
110
110
  if (height !== null)
111
111
  root.setHeight(height);
112
112
  else {
113
113
  root.setHeightAuto();
114
114
  const extra = box.verticalExtra;
115
115
  const min = clampHeight(box, extra) - extra;
116
- if (min > 0)
116
+ if (min > 0) {
117
+ floor = min;
117
118
  root.setMinHeight(min);
119
+ }
118
120
  const max = clampHeight(box, Infinity) - extra;
119
121
  if (Number.isFinite(max)) {
120
- root.setMaxHeight(Math.max(0, max));
121
- capped = true;
122
+ ceiling = Math.max(0, max);
123
+ root.setMaxHeight(ceiling);
122
124
  }
123
125
  }
124
126
  const flowing = [];
@@ -143,6 +145,15 @@ export function layoutFlex(box, ctx, contentWidth) {
143
145
  }
144
146
  else
145
147
  PAINT_ORDER.delete(box);
148
+ // Whether a line weighs its items one against another as it shrinks
149
+ // them, each by its flex base size (CSS Flexbox 9.7, step 4c): the one
150
+ // line of a row that does not wrap, two or more of whose items may
151
+ // shrink. One that shrinks alone comes to the room the rest leave it,
152
+ // whatever its base, and a line of a row that wraps is too narrow for
153
+ // an item only where that is alone on it.
154
+ const weighs = box.style.flexWrap === 'nowrap' &&
155
+ box.style.flexDirection.startsWith('row') &&
156
+ flowing.filter((child) => child.style.flexShrink > 0).length > 1;
146
157
  const items = [];
147
158
  for (const child of flowing) {
148
159
  const node = Y.Node.create(flexConfig());
@@ -155,15 +166,64 @@ export function layoutFlex(box, ctx, contentWidth) {
155
166
  stretch: NaN,
156
167
  main: NaN,
157
168
  least: NaN,
169
+ most: NaN,
170
+ basis: NaN,
171
+ sized: false,
172
+ content: NaN,
173
+ fitted: NaN,
174
+ base: NaN,
175
+ narrowest: NaN,
158
176
  held: NaN,
177
+ minimum: NaN,
159
178
  auto: false,
179
+ across: false,
180
+ unstretched: NaN,
160
181
  };
161
- applyItem(node, child, ctx, contentWidth, laid, height !== null, own);
182
+ applyItem(node, child, ctx, contentWidth, laid, height !== null, own ? definite : NaN, weighs);
162
183
  root.insertChild(node, items.length);
163
184
  items.push({ box: child, node, laid });
164
185
  }
186
+ // A column that wraps is as wide as its content box, but each of its
187
+ // lines is as wide as its widest item, every item `fit-content` across
188
+ // (CSS Flexbox 9.4, steps 7 and 8), and so is placed across here and not
189
+ // by Yoga (`crossLines`). Yoga stretched an item to the box wherever the
190
+ // column had room for every item down it — with no height, always — so a
191
+ // line was the box's width, an item wider at its narrowest ran out of
192
+ // it, and `align-content` had no room to centre or end a line in. And
193
+ // stretching a line, it laid each stretched item out again at its height
194
+ // less its top and bottom margins and plus its side ones (its multi-line
195
+ // pass adds the margins across a column where it means the ones down
196
+ // it), and added what `space-around` puts between lines to its width. So
197
+ // Yoga lays the box out with every item at its line's start, where it
198
+ // measures each `fit-content` (`innerWidth`) and down the column as wide
199
+ // as that, as the specification measures a line's items (9.2, step 3),
200
+ // and the lines one more pixel apart than they are, so that each line
201
+ // starts where the last does not and says which items are on it.
202
+ const crossed = wrapsColumn(box.style, contentWidth);
203
+ if (crossed) {
204
+ root.setAlignContent(Y.ALIGN_FLEX_START);
205
+ root.setGap(Y.GUTTER_COLUMN, columnGap + 1);
206
+ for (const { node } of items)
207
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
208
+ }
209
+ if (bounded)
210
+ root.setWidth(contentWidth);
165
211
  const direction = box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
166
212
  const row = box.style.flexDirection.startsWith('row');
213
+ // An `auto` margin across a row that wraps takes the room its line has
214
+ // past the item (CSS Flexbox 8.1, 9.6), where Yoga's pass that sets the
215
+ // items across their lines reads an item's alignment and not its
216
+ // margins: it stretched one with `margin-top: auto` to its line, and set
217
+ // one with a height at its line's start. So Yoga is told the alignment
218
+ // the margins come to (`autoAcross`), which it then places it by.
219
+ if (row && box.style.flexWrap !== 'nowrap') {
220
+ const reverse = box.style.flexWrap === 'wrap-reverse';
221
+ for (const { box: child, node } of items) {
222
+ const aligned = autoAcross(child.style, reverse);
223
+ if (aligned)
224
+ node.setAlignSelf(ALIGN[aligned]);
225
+ }
226
+ }
167
227
  // frozen at its least size on the one line of a box that does not
168
228
  // wrap, by a basis where Yoga takes one along the main axis
169
229
  // (`applyItem`'s `setBasis`)
@@ -171,27 +231,75 @@ export function layoutFlex(box, ctx, contentWidth) {
171
231
  frozen: box.style.flexWrap === 'nowrap',
172
232
  basis: row ? bounded : height !== null,
173
233
  };
174
- const ask = () => root.calculateLayout(bounded ? contentWidth : Number.NaN, height ?? Number.NaN, direction);
175
- // the one line of a box that does not wrap may be short of room, where
176
- // the box has a size along it that its items do not make: and Yoga's
177
- // answer for a line every item of which its minimum stops is put right
178
- // (`stoppedLine`)
179
- const short = hold.frozen && (row ? bounded : capped);
234
+ const layout = () => root.calculateLayout(bounded ? contentWidth : Number.NaN, height ?? Number.NaN, direction);
235
+ // Down a column that wraps, the items Yoga stretches across their lines
236
+ // come out of its layout at another height than it sized them at, where
237
+ // their side margins are not their top and bottom ones (`unstretch`):
238
+ // laid out so that each has the height it was sized at
239
+ const restretched = row || hold.frozen ? [] : stretchedDown(root, items);
240
+ const sized = bounded ? contentWidth : null;
241
+ // and across a row that wraps, the items Yoga stretches across lines
242
+ // `align-content` spaces out come out of its layout as tall as their
243
+ // line and the room after it as well (`unspace`): laid out so that each
244
+ // is as tall as its line, where the box has room to space them out in —
245
+ // a height, or a minimum one
246
+ const spaced = row &&
247
+ bounded &&
248
+ !hold.frozen &&
249
+ (height !== null || floor > 0) &&
250
+ spacesLines(box.style);
251
+ const ask = restretched.length > 0
252
+ ? () => unstretch(items, restretched, sized, layout)
253
+ : spaced
254
+ ? () => unspace(root, items, box.style.flexWrap === 'wrap-reverse', layout)
255
+ : layout;
256
+ // The one line of a box that does not wrap is shared out by CSS
257
+ // Flexbox 9.7 where Yoga's answer for it may not be what that makes of
258
+ // it (`resolveLine`): along a row of a width, or down a column of a
259
+ // height or of limits on one, where a line may have room to share out
260
+ const line = hold.frozen &&
261
+ (row ? bounded : height !== null || floor > 0 || ceiling < Infinity)
262
+ ? {
263
+ resolved: false,
264
+ size: row ? contentWidth : (height ?? NaN),
265
+ floor,
266
+ ceiling,
267
+ between: betweenOf(items, row, row ? columnGap : rowGap),
268
+ probe: row && contentWidth === MIN_CONTENT_PROBE,
269
+ }
270
+ : null;
271
+ // and the lines of a box that wraps may have room left over, where a
272
+ // row has a width to have it in: Yoga's answer for one every item of
273
+ // which that may grow its maximum stops is put right (`cappedLines`) —
274
+ // broken into lines as Yoga breaks them, at the box's size along them
275
+ const roomy = !hold.frozen && (row ? bounded : true);
276
+ const breaks = row ? contentWidth : (height ?? Infinity);
180
277
  // each item's size along the main axis, read the once a layout (`Laid`)
181
278
  const read = () => {
182
279
  for (const { node, laid } of items) {
183
- laid.main = row ? node.getComputedWidth() : node.getComputedHeight();
280
+ laid.main = row ? node.getComputedWidth() : heightOf(node, laid);
184
281
  }
185
282
  };
186
283
  const calculate = () => {
284
+ // a line shared out here is again, before Yoga places it: an item
285
+ // since held at its content's size is frozen there (`autoMinimums`)
286
+ if (line?.resolved)
287
+ shareOut(items, row, line, hold);
187
288
  ask();
188
289
  read();
189
- if (!short)
190
- return;
191
- const room = row ? contentWidth : (height ?? root.getComputedHeight());
192
- const gap = row ? columnGap : rowGap;
193
- if (stoppedLine(items, row, room, gap, hold, ask))
194
- read();
290
+ if (line && !line.resolved) {
291
+ const room = Number.isNaN(line.size)
292
+ ? root.getComputedHeight()
293
+ : line.size;
294
+ if (resolveLine(items, row, line, room, hold, ask))
295
+ read();
296
+ }
297
+ else if (roomy) {
298
+ const room = row ? contentWidth : (height ?? root.getComputedHeight());
299
+ const gap = row ? columnGap : rowGap;
300
+ if (cappedLines(items, row, room, breaks, gap, hold, ask))
301
+ read();
302
+ }
195
303
  };
196
304
  ctx.flexDepth = depth + 1;
197
305
  try {
@@ -206,11 +314,54 @@ export function layoutFlex(box, ctx, contentWidth) {
206
314
  finally {
207
315
  ctx.flexDepth = depth;
208
316
  }
317
+ // The lines of a row that wraps are as tall as their tallest items, each
318
+ // as tall as its content across (CSS Flexbox 9.4, steps 7 and 8), and
319
+ // `align-content` stretches lines short of the box to it, or centres
320
+ // them, or ends them, past its edges where they are taller (step 9).
321
+ // Handed the box's height as a size, Yoga measures a stretched item at it
322
+ // wherever the row has room along it for every item, and so its line is
323
+ // the box's height: an item taller than the box was squashed to it, and a
324
+ // line `align-content` centres stayed at the box's top. Where that is so
325
+ // (`heldLine`), the box is held to its height from both sides instead,
326
+ // and laid out again: Yoga measures each item as at most that tall, which
327
+ // its content passes where it is taller — the maximum a percentage, as a
328
+ // minimum and a maximum that are the same are a size to Yoga. Only there,
329
+ // which takes in the one line `space-around` and `space-evenly` centre,
330
+ // and not where the row is measured for its widest, which is laid out
331
+ // for its width alone.
332
+ if (row &&
333
+ bounded &&
334
+ height !== null &&
335
+ box.style.flexWrap !== 'nowrap' &&
336
+ heldLine(box, ctx, items, height, contentWidth, columnGap)) {
337
+ root.setHeightAuto();
338
+ root.setMinHeight(height);
339
+ root.setMaxHeightPercent(100);
340
+ // and a percentage height resolved: Yoga takes one inside a box it
341
+ // does not hold to a size for none, and measured `h-1/2` as tall as
342
+ // its content
343
+ if (own) {
344
+ for (const { box: child, node } of items) {
345
+ const set = child.style.height;
346
+ if (!isPct(set))
347
+ continue;
348
+ const down = child.style.boxSizing === 'border-box' ? 0 : child.verticalExtra;
349
+ node.setHeight(resolve(set, definite) + down);
350
+ }
351
+ }
352
+ ctx.flexDepth = depth + 1;
353
+ try {
354
+ calculate();
355
+ }
356
+ finally {
357
+ ctx.flexDepth = depth;
358
+ }
359
+ }
209
360
  // Yoga aligns the lines of a box that wraps, or aligns by baselines, in
210
361
  // one pass of its own, which drops a margin (`lineMarginFix`)
211
362
  const baselines = box.style.alignItems === 'baseline' ||
212
363
  items.some(({ box: child }) => child.style.alignSelf === 'baseline');
213
- let bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap' || baselines, contentWidth);
364
+ let bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap' || baselines, contentWidth, undefined, crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined);
214
365
  // Items aligned by their baselines, which Yoga cannot see, are aligned
215
366
  // with their baselines known (`baselineLines`)
216
367
  // by the flex layout again, set at their lines' starts with the margins
@@ -238,7 +389,7 @@ export function layoutFlex(box, ctx, contentWidth) {
238
389
  finally {
239
390
  ctx.flexDepth = depth;
240
391
  }
241
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap', contentWidth, started);
392
+ bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap', contentWidth, started);
242
393
  }
243
394
  // A replaced item with a ratio that is stretched across a line of a
244
395
  // definite size takes its flex base size from the stretched one through
@@ -256,7 +407,7 @@ export function layoutFlex(box, ctx, contentWidth) {
256
407
  finally {
257
408
  ctx.flexDepth = depth;
258
409
  }
259
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap' || baselines, contentWidth);
410
+ bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap' || baselines, contentWidth);
260
411
  }
261
412
  const contentHeight = root.getComputedHeight();
262
413
  root.freeRecursive();
@@ -284,6 +435,10 @@ function keepRatios(container, items, row) {
284
435
  if (Math.abs(basis - laid.main) < 0.5)
285
436
  continue;
286
437
  node.setFlexBasis(basis);
438
+ // and is shared out from it where its line is here (`shareOut`)
439
+ laid.basis = basis;
440
+ laid.sized = false;
441
+ laid.base = basis;
287
442
  // one held at its least size (`holdAt`) flexes from the new basis, no
288
443
  // further down than that
289
444
  if (!Number.isNaN(laid.held)) {
@@ -417,16 +572,29 @@ own,
417
572
  /** Whether Yoga aligned the lines in its pass that drops a margin. */
418
573
  lined,
419
574
  /** The flex box's content width: its items' containing block's. */
420
- contentWidth, started) {
575
+ contentWidth, started,
576
+ /** Where each item is across a column that wraps (`crossLines`). */
577
+ crossed) {
421
578
  let bottom = 0;
579
+ // the lines of a row that wraps in reverse are turned round to run up it
580
+ const reverse = row && box.style.flexWrap === 'wrap-reverse';
422
581
  for (const { box: child, node, laid } of items) {
423
- const across = lined
582
+ const placed = crossed?.get(child);
583
+ const across = lined && !placed
424
584
  ? lineMarginFix(box.style, child, node, row, started?.has(child))
425
585
  : 0;
426
- const left = box.contentX + node.getComputedLeft() + (row ? 0 : across);
427
- const top = box.contentY + node.getComputedTop() + (row ? across : 0);
428
- const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
429
- const itemHeight = row ? node.getComputedHeight() : laid.main;
586
+ const itemHeight = row ? heightOf(node, laid) : laid.main;
587
+ const left = box.contentX +
588
+ (placed ? placed.x : node.getComputedLeft() + (row ? 0 : across));
589
+ // which Yoga turns an item round in by the height it laid it out at, and
590
+ // not the one it is (`unspace`)
591
+ const top = box.contentY +
592
+ node.getComputedTop() +
593
+ (row ? across : 0) +
594
+ (reverse ? node.getComputedHeight() - itemHeight : 0);
595
+ const width = placed
596
+ ? placed.width
597
+ : meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
430
598
  // The item is laid out again at the width the flex pass settled on: the
431
599
  // measure function answered a question, and the answer is not a layout —
432
600
  // its line breaks were computed against a width that may have changed
@@ -488,7 +656,8 @@ column = false) {
488
656
  // one: the tallest card of a row is laid out the once. A table shares
489
657
  // what it is given past its rows among them (`layoutTable`), and a
490
658
  // height short of them is none it can have: it is laid out again where
491
- // it was given more.
659
+ // it was given more — or less than a height of its own, which it takes
660
+ // in place of that one, down to its rows.
492
661
  const given = definite ?? (column ? height : null);
493
662
  let shares = false;
494
663
  if (given !== null && (box.kind === 'flex' || box.kind === 'table')) {
@@ -496,7 +665,8 @@ column = false) {
496
665
  shares =
497
666
  box.kind === 'flex'
498
667
  ? !(Math.abs(natural - given) <= 0.01)
499
- : !(natural >= given - 0.01);
668
+ : !(natural >= given - 0.01) ||
669
+ (box.style.height !== AUTO && natural > given + 0.01);
500
670
  }
501
671
  if (given !== null) {
502
672
  const inner = Math.max(0, given - box.verticalExtra);
@@ -522,7 +692,7 @@ column = false) {
522
692
  const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
523
693
  heights.set(box, tall);
524
694
  try {
525
- ctx.layoutSubtree(box, width, containing);
695
+ layoutAt(box, ctx, width, containing, laid);
526
696
  }
527
697
  finally {
528
698
  heights.delete(box);
@@ -565,17 +735,22 @@ function applyContainer(node, style) {
565
735
  const items = ALIGN[style.alignItems] ?? Y.ALIGN_STRETCH;
566
736
  if (items !== Y.ALIGN_STRETCH)
567
737
  node.setAlignItems(items);
568
- const lines = ALIGN[style.alignContent] ?? Y.ALIGN_STRETCH;
738
+ const lines = ALIGN[crossContent(style)] ?? Y.ALIGN_STRETCH;
569
739
  if (lines !== Y.ALIGN_FLEX_START)
570
740
  node.setAlignContent(lines);
571
741
  }
572
742
  function applyItem(node, box, ctx, containingWidth, laid,
573
743
  /** Whether the flex box has a height its items are laid out in. */
574
744
  tall,
575
- /** Whether that height is definite, and a percentage may be of it: one a
576
- * column flexed the box to is not (`USED_HEIGHT`). */
577
- own) {
745
+ /** That height where it is definite, and a percentage may be of it, and
746
+ * else NaN: one a column flexed the box to is not (`USED_HEIGHT`). */
747
+ definite,
748
+ /** Whether its line weighs it against another as it shrinks them, by
749
+ * their flex base sizes (`layoutFlex`). */
750
+ weighed) {
578
751
  const style = box.style;
752
+ const own = Number.isFinite(definite);
753
+ const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
579
754
  laid.stretch = containingWidth - box.marginLeft - box.marginRight;
580
755
  // an `auto` margin takes the free space on its side, which is how
581
756
  // `margin-left: auto` puts an item at the end of its row (CSS Flexbox
@@ -604,15 +779,31 @@ own) {
604
779
  if (style.width !== AUTO) {
605
780
  laid.set = setLength(node, true, style.width, containingWidth, across);
606
781
  }
782
+ // Down a column, a basis of the content's — `content`, or a percentage
783
+ // of a height that is not definite (CSS Flexbox 7.2.3) — is its content's
784
+ // height, whatever height it has of its own (9.2.3, E), and Yoga takes
785
+ // the height of its own for a basis where it has one. So that is kept
786
+ // from Yoga, and the measure function answers with the content's.
787
+ const contentColumn = !row && (style.flexBasis === 'content' || (isPct(style.flexBasis) && !own));
607
788
  // a percentage of a height that is not definite is `auto` (CSS 2.1 10.5),
608
- // which Yoga makes of one where the flex box has no height at all
609
- if (style.height !== AUTO && (own || !tall || !isPct(style.height))) {
610
- setLength(node, false, style.height, NaN, down);
789
+ // and is not handed to Yoga, which makes one of the height of a flex box
790
+ // with none of its own but limits on one — and the height set on its
791
+ // node, where that is a length, and whether one is
792
+ let height = NaN;
793
+ let heightSet = false;
794
+ if (style.height !== AUTO &&
795
+ (own || !isPct(style.height)) &&
796
+ !contentColumn) {
797
+ height = setLength(node, false, style.height, definite, down);
798
+ heightSet = true;
611
799
  }
612
800
  // the least it may be along each axis, its border box's, as Yoga is
613
801
  // told it
614
802
  let leastWidth = NaN;
615
803
  let leastHeight = NaN;
804
+ // and the most, as Yoga is told it
805
+ let mostWidth = NaN;
806
+ let mostHeight = NaN;
616
807
  const minWidth = resolveOrNull(style.minWidth, containingWidth);
617
808
  if (minWidth !== null) {
618
809
  leastWidth = minWidth + across;
@@ -620,8 +811,10 @@ own) {
620
811
  }
621
812
  if (style.maxWidth !== 'none') {
622
813
  const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
623
- if (maxWidth !== null)
624
- node.setMaxWidth(maxWidth + across);
814
+ if (maxWidth !== null) {
815
+ mostWidth = maxWidth + across;
816
+ node.setMaxWidth(mostWidth);
817
+ }
625
818
  }
626
819
  if (style.widthKeyword || style.minWidthKeyword || style.maxWidthKeyword) {
627
820
  // an intrinsic size is the item's content's, measured here and handed
@@ -637,32 +830,87 @@ own) {
637
830
  leastWidth = size(style.minWidthKeyword);
638
831
  node.setMinWidth(leastWidth);
639
832
  }
640
- if (style.maxWidthKeyword)
641
- node.setMaxWidth(size(style.maxWidthKeyword));
833
+ if (style.maxWidthKeyword) {
834
+ mostWidth = size(style.maxWidthKeyword);
835
+ node.setMaxWidth(mostWidth);
836
+ }
642
837
  }
643
- const minHeight = resolveOrNull(style.minHeight, NaN);
838
+ // a percentage of a definite height, which a limit on a flex item's
839
+ // height is where its flex box has one
840
+ const minHeight = resolveOrNull(style.minHeight, definite);
644
841
  if (minHeight !== null) {
645
842
  leastHeight = minHeight + down;
646
843
  node.setMinHeight(leastHeight);
647
844
  }
648
845
  if (style.maxHeight !== 'none') {
649
- const maxHeight = resolveOrNull(style.maxHeight, NaN);
650
- if (maxHeight !== null)
651
- node.setMaxHeight(maxHeight + down);
846
+ const maxHeight = resolveOrNull(style.maxHeight, definite);
847
+ if (maxHeight !== null) {
848
+ mostHeight = maxHeight + down;
849
+ node.setMaxHeight(mostHeight);
850
+ }
851
+ }
852
+ // a `border-box` maximum under the item's padding and borders is them,
853
+ // its content box none wide (CSS Sizing 3, 3.3), where Yoga lays the
854
+ // item out as wide as them and shares out its line as though it were
855
+ // as wide as the maximum: the `auto` margins after `max-width:
856
+ // 20.668px` with 23px of padding and border took the 2.33px between
857
+ if (mostWidth < box.horizontalExtra) {
858
+ mostWidth = box.horizontalExtra;
859
+ node.setMaxWidth(mostWidth);
860
+ }
861
+ if (mostHeight < box.verticalExtra) {
862
+ mostHeight = box.verticalExtra;
863
+ node.setMaxHeight(mostHeight);
864
+ }
865
+ // a maximum under the minimum is the minimum (CSS 2.1 10.4, 10.7), where
866
+ // Yoga holds an item that comes to more than both at the maximum
867
+ if (mostWidth < leastWidth) {
868
+ mostWidth = leastWidth;
869
+ node.setMaxWidth(mostWidth);
870
+ }
871
+ if (mostHeight < leastHeight) {
872
+ mostHeight = leastHeight;
873
+ node.setMaxHeight(mostHeight);
652
874
  }
653
- const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
654
875
  laid.least = row ? leastWidth : leastHeight;
876
+ laid.most = row ? mostWidth : mostHeight;
877
+ laid.narrowest = leastWidth;
878
+ // A table is as wide as the flex layout makes it, as any item is: the
879
+ // size its line flexed it to along a row (9.7), and across a column's
880
+ // line the line's, stretched (9.4, step 11) — where in a block's flow it
881
+ // is as wide as its columns. Laid out at that width and then shrunk to
882
+ // them, it was a table its columns wide at the start of a column, and one
883
+ // `flex: 1` along a row stopped at them where Chrome's fills its share
884
+ laid.across =
885
+ box.kind === 'table' &&
886
+ (row ||
887
+ (box.parent !== null &&
888
+ !style.widthKeyword &&
889
+ stretches(box, box.parent.style, false)));
655
890
  // Yoga takes a length for a basis only where the flex box's main size is
656
891
  // definite, and else the item's own size along it: down a column of no
657
892
  // height of its own it read `flex: 0 0 3rem` as the item's height, or
658
893
  // its content's. There the basis is handed over as that height, which is
659
894
  // what the item's own is to a basis anyway — no more than its start
660
895
  const setBasis = (px) => {
896
+ laid.basis = px;
661
897
  if (row || tall)
662
898
  node.setFlexBasis(px);
663
899
  else
664
900
  node.setHeight(px);
665
901
  };
902
+ // Where Yoga takes the item's own size along the main axis for its
903
+ // basis, that size as a length, or its content's (`Laid.sized`) — and
904
+ // its content's, whatever size it has, for a basis that says so, which
905
+ // Yoga reads as its size down a column
906
+ const ownSize = (content = false) => {
907
+ if (content ||
908
+ (row ? style.width === AUTO && !style.widthKeyword : !heightSet)) {
909
+ laid.sized = true;
910
+ }
911
+ else
912
+ laid.basis = row ? laid.set : height;
913
+ };
666
914
  // down a column, an item with a ratio and a width is as tall as that
667
915
  // makes it, for a basis of its content or of its own `auto` height (CSS
668
916
  // Flexbox 9.2.3, B) — where Yoga measured it, and its block layout held
@@ -677,7 +925,8 @@ own) {
677
925
  // the content's size along a row, whatever the item's own width says
678
926
  // (CSS Flexbox 7.2.3), which Yoga reads as `auto` and takes the width
679
927
  // for; along a column, Yoga's measure is the content's
680
- node.setFlexBasis(contentBasis(box, ctx, containingWidth));
928
+ laid.basis = contentBasis(box, ctx, containingWidth);
929
+ node.setFlexBasis(laid.basis);
681
930
  }
682
931
  else if (columnBasis !== null) {
683
932
  // but a height of its own hides it from Yoga, and an item with a ratio
@@ -686,21 +935,32 @@ own) {
686
935
  }
687
936
  else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
688
937
  // Yoga's own
938
+ ownSize(style.flexBasis === 'content');
689
939
  }
690
- else if (isPct(style.flexBasis) && !row && tall && !own) {
940
+ else if (isPct(style.flexBasis) && !row && !own) {
691
941
  // a percentage of a main size that is not definite is `content` (CSS
692
942
  // Flexbox 7.2.3): Yoga's own, down a column
943
+ ownSize(true);
693
944
  }
694
945
  else if (isPct(style.flexBasis)) {
695
- // a percentage of the main size, which is known across a row
946
+ // a percentage of the main size, which is known across a row and down
947
+ // a column of a definite height: of a content box's size, as a width
948
+ // is, where the item's padding and border go on top of it
696
949
  const basis = style.flexBasis;
697
- if (!basis.px && !basis.of)
950
+ const extra = row ? across : down;
951
+ const known = row ? Number.isFinite(containingWidth) : own;
952
+ if (known) {
953
+ laid.basis = resolve(basis, row ? containingWidth : definite) + extra;
954
+ }
955
+ if (!basis.px && !basis.of && !(known && extra)) {
698
956
  node.setFlexBasisPercent(basis.pct);
699
- else if (row && Number.isFinite(containingWidth)) {
700
- node.setFlexBasis(resolve(basis, containingWidth));
701
957
  }
702
- else if (basis.of)
958
+ else if (known)
959
+ node.setFlexBasis(laid.basis);
960
+ else if (basis.of) {
703
961
  node.setFlexBasisAuto();
962
+ ownSize();
963
+ }
704
964
  else
705
965
  node.setFlexBasisPercent(basis.pct);
706
966
  }
@@ -732,8 +992,15 @@ own) {
732
992
  // table cell's is: taken per layout of the container, a flex box in a
733
993
  // flex box in a flex box laid its innermost out three times a level,
734
994
  // and twelve levels took two seconds.
735
- const alongRow = box.parent?.style.flexDirection.startsWith('row') ?? true;
995
+ const alongRow = row;
736
996
  const content = () => {
997
+ const width = maxContent();
998
+ // its flex base size, where that is its content's (`Laid.sized`)
999
+ if (alongRow)
1000
+ laid.content = width + box.horizontalExtra;
1001
+ return width;
1002
+ };
1003
+ function maxContent() {
737
1004
  // a replaced element's is the width it has with no limit — its
738
1005
  // natural one, or what its ratio makes of a height of its own — which
739
1006
  // its image, loading, may change, so it is not kept; taken as its
@@ -753,12 +1020,17 @@ own) {
753
1020
  if (width === undefined) {
754
1021
  width = measureIntrinsicWidth(box, ctx, Infinity) - box.horizontalExtra;
755
1022
  MAX_CONTENT.set(box, width);
1023
+ // measured, it is laid out with no limit on its width, and not at
1024
+ // the one it was last laid out at, which Yoga may give it again: an
1025
+ // item `min-width: min-content` that Yoga asked at its minimum and
1026
+ // then with no limit was left laid out infinitely wide
1027
+ laid.width = NaN;
756
1028
  }
757
1029
  // within what its least and greatest heights make of widths through
758
1030
  // its ratio: its size before it is flexed, and not after
759
1031
  const extra = box.horizontalExtra;
760
1032
  return Math.max(0, transferredWidth(box, width + extra) - extra);
761
- };
1033
+ }
762
1034
  // An item with a ratio is as wide, across a column, as the height it
763
1035
  // was given makes it through the ratio, where its width is its own to
764
1036
  // find (CSS Flexbox 9.4, its hypothetical cross size from its used main
@@ -792,24 +1064,109 @@ own) {
792
1064
  // 5.1)
793
1065
  const flexed = box.kind === 'replaced' || !laid.auto;
794
1066
  const through = widthThrough && given && !exact && flexed ? widthThrough(h) : null;
795
- // along a row, a replaced element is its own width wherever there is
796
- // room for less — its flex base size is not fitted to the room (9.2)
797
- // — and across a column it is fitted, as any item is
798
- const inner = through !== null
1067
+ // Along a row, an item's flex base size is its content at its widest,
1068
+ // whatever the room (CSS Flexbox 9.2.3 E, `content` as `max-content`),
1069
+ // which Yoga asks for as at most the row's width. One that may not
1070
+ // shrink is as wide as that (9.7): fitted to the room, a `shrink-0`
1071
+ // item of words was held to its content at its narrowest, and wrapped
1072
+ // where it overflows the row. And one that may shrink gives up room in
1073
+ // proportion to it, where its line weighs it against another (9.7,
1074
+ // step 4c, its flex shrink factor times its base): fitted, an item of
1075
+ // words wider than the row was weighed as no wider than the row, and
1076
+ // gave up too little of it beside a narrower one — 230 and 70 of a row
1077
+ // of 300, where Chrome has 245 and 55. One that shrinks alone is
1078
+ // fitted, which comes to the same and keeps the layout it is measured
1079
+ // at for the one it is given: at its widest, a paragraph in a row laid
1080
+ // itself out twice.
1081
+ // And every item of a row laid out in no room is fitted, which is the
1082
+ // flex box measured for its content at its narrowest
1083
+ // (`MIN_CONTENT_PROBE`): that is the sum of its items' content at their
1084
+ // narrowest, whatever they may shrink by, as Chrome and the css-flexbox
1085
+ // suite have it (`intrinsic-size/row-001`, `gap-015`), and not their
1086
+ // bases.
1087
+ // Across a column, the item is `fit-content` wide where it is not
1088
+ // stretched (9.4, step 7): fitted to the column, but no narrower than
1089
+ // its content at its narrowest, past the column where that is wider,
1090
+ // as a centred one is on both sides. Its `min-width` has no say in
1091
+ // that: an automatic minimum is the main axis's (4.5), so `auto` is 0
1092
+ // across a column, the same as the `min-width: 0` that lets an item
1093
+ // shrink along a row. Fitted to the column, a word wider than it ran
1094
+ // out of the item, and a centred item started at the column's start.
1095
+ // A replaced element's narrowest is its natural width, or one through
1096
+ // its ratio, and none where it has neither: an SVG with only a
1097
+ // `viewBox` is as wide as the column. And an item with something in
1098
+ // it that takes a percentage of a height is fitted to the column still:
1099
+ // the flex layout makes its height definite only after this, and
1100
+ // measured without it, an image `height: 100%` is its natural width.
1101
+ const widest = alongRow &&
1102
+ !exact &&
1103
+ (box.kind === 'replaced' ||
1104
+ (containingWidth !== MIN_CONTENT_PROBE &&
1105
+ (style.flexShrink === 0 || weighed)));
1106
+ const narrowest = alongRow || percentHeightsIn(box)
1107
+ ? undefined
1108
+ : box.kind === 'replaced'
1109
+ ? () => (replacedMinimum(box, containingWidth) ?? extra) - extra
1110
+ : () => minContentOf(box, ctx, laid) - extra;
1111
+ const fitted = through !== null
799
1112
  ? through
800
- : box.kind === 'replaced' && alongRow && !exact
1113
+ : widest
801
1114
  ? content()
802
- : innerWidth(width, wm, content);
1115
+ : innerWidth(width, wm, content, narrowest);
1116
+ // Across a column, no wider than its maximum: Yoga hands the measure
1117
+ // a room within it, but holds the answer's width to it only after,
1118
+ // and keeps the height measured at the wider — an image `max-width:
1119
+ // 100%` its natural width at its narrowest was as tall as that made it
1120
+ const capped = !alongRow && fitted > mostWidth - extra ? mostWidth - extra : fitted;
1121
+ // And no narrower than its minimum: Yoga holds the item to that only
1122
+ // after it has the answer, which it keeps for the width that makes of
1123
+ // it, so an item measured at the room and held to a wider minimum was
1124
+ // as tall as its content wrapped at the room — `min-w-max` in a narrow
1125
+ // row, or down a column narrower than it, one line of words two lines
1126
+ // tall, and an item held to its longest word in a row that wraps
1127
+ // (`holdAt`) a line taller than that makes it. Stretched across a
1128
+ // column, it is asked at the column's width.
1129
+ const inner = Number.isNaN(laid.narrowest)
1130
+ ? capped
1131
+ : Math.max(capped, laid.narrowest - extra);
1132
+ let answer;
803
1133
  if (box.kind === 'replaced' &&
804
1134
  exact &&
805
1135
  ratio > 0 &&
806
1136
  style.height === AUTO) {
807
- return { width: inner, height: inner / ratio };
1137
+ answer = { width: inner, height: inner / ratio };
808
1138
  }
809
- let answer = answers.get(inner);
810
- if (answer === undefined) {
811
- answer = measureBox(box, ctx, inner, containingWidth, laid);
812
- answers.set(inner, answer);
1139
+ else {
1140
+ answer = answers.get(inner);
1141
+ if (answer === undefined) {
1142
+ answer = measureBox(box, ctx, inner, containingWidth, laid);
1143
+ answers.set(inner, answer);
1144
+ }
1145
+ }
1146
+ // along a row, the width it answered where it might choose one
1147
+ if (alongRow && wm !== Y.MEASURE_MODE_EXACTLY) {
1148
+ laid.fitted = answer.width + box.horizontalExtra;
1149
+ }
1150
+ // down a column, its content's height where nothing held it to one:
1151
+ // its flex base size, where that is its content's (`Laid.sized`) — for
1152
+ // a basis that says so, what a height of its own has no say in
1153
+ if (!alongRow && !given) {
1154
+ if (contentColumn && style.height !== AUTO && box.kind !== 'replaced') {
1155
+ const width = inner + box.horizontalExtra;
1156
+ const kept = keptNatural(box, ctx, width, containingWidth);
1157
+ let content = kept?.content ?? NaN;
1158
+ if (Number.isNaN(content)) {
1159
+ if (kept === null || kept.serial !== box.layoutSerial) {
1160
+ layoutNaturally(box, ctx, width, containingWidth, laid);
1161
+ }
1162
+ content = columnContent(box, ctx, laid, width, containingWidth);
1163
+ }
1164
+ answer = {
1165
+ width: answer.width,
1166
+ height: Math.max(0, content - box.verticalExtra),
1167
+ };
1168
+ }
1169
+ laid.content = answer.height + box.verticalExtra;
813
1170
  }
814
1171
  return answer;
815
1172
  });
@@ -938,7 +1295,7 @@ containing, laid) {
938
1295
  }
939
1296
  else {
940
1297
  const at = ctx.positioned.length;
941
- ctx.layoutSubtree(box, width, containing);
1298
+ layoutAt(box, ctx, width, containing, laid);
942
1299
  // a replaced box is sized, not laid out, and has no layout to count
943
1300
  if (box.kind !== 'replaced') {
944
1301
  NATURAL.set(box, {
@@ -959,6 +1316,27 @@ containing, laid) {
959
1316
  laid.height = box.height;
960
1317
  laid.tall = NaN;
961
1318
  }
1319
+ /**
1320
+ * Lay an item out at a border-box width: a table the flex layout gives its
1321
+ * width at that width as its own (`Laid.across`), for this layout alone. A
1322
+ * width Yoga asks its content at is one the table comes to by its columns
1323
+ * all the same — its widest, or the room where that is less — so that is
1324
+ * no matter; an unbounded one, a probe of a box around the flex box, is
1325
+ * the table's to shrink in.
1326
+ */
1327
+ function layoutAt(box, ctx, width, containing, laid) {
1328
+ if (!laid.across || !Number.isFinite(width)) {
1329
+ ctx.layoutSubtree(box, width, containing);
1330
+ return;
1331
+ }
1332
+ STRETCHED_ACROSS.add(box);
1333
+ try {
1334
+ ctx.layoutSubtree(box, width, containing);
1335
+ }
1336
+ finally {
1337
+ STRETCHED_ACROSS.delete(box);
1338
+ }
1339
+ }
962
1340
  /**
963
1341
  * The layout this pass made of an item at its content's own height
964
1342
  * (`layoutNaturally`), where one at `width` now would be the same: the same
@@ -1055,8 +1433,9 @@ function readsPercentBase(box) {
1055
1433
  }
1056
1434
  /** The width an item's content is measured at: the width Yoga gives it
1057
1435
  * exactly, or its max-content width, no wider than any it may take up to
1058
- * (CSS Flexbox 9.2). */
1059
- function innerWidth(width, mode, maxContent) {
1436
+ * (CSS Flexbox 9.2) — and, given its min-content width, no narrower than
1437
+ * that either. */
1438
+ function innerWidth(width, mode, maxContent, minContent) {
1060
1439
  if (mode === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width)) {
1061
1440
  return Math.max(0, width);
1062
1441
  }
@@ -1065,9 +1444,10 @@ function innerWidth(width, mode, maxContent) {
1065
1444
  return content;
1066
1445
  }
1067
1446
  // a room exactly as wide as the content, as Yoga holds it, is room for it
1068
- return content <= width || nearly(content, width)
1069
- ? content
1070
- : Math.max(0, width);
1447
+ if (content <= width || nearly(content, width))
1448
+ return content;
1449
+ const room = Math.max(0, width);
1450
+ return minContent ? Math.min(content, Math.max(room, minContent())) : room;
1071
1451
  }
1072
1452
  /**
1073
1453
  * An item's automatic minimum (CSS Flexbox 4.5): with `min-width: auto` in
@@ -1089,13 +1469,39 @@ function autoMinimums(items, row, ctx, containingWidth, hold) {
1089
1469
  if (box.kind === 'text' || box.kind === 'break')
1090
1470
  continue;
1091
1471
  const style = box.style;
1472
+ const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
1473
+ // A table is no smaller than it can be along either axis, whatever its
1474
+ // own least says, `min-width: 0` and `min-height: 0` among it: its
1475
+ // columns at their narrowest along a row, and down a column its rows
1476
+ // and captions at the width it has, however short a height of its own
1477
+ // is (CSS 2.1 17.5.2 and 17.5.3; Blink, `length_utils.cc`: "Tables
1478
+ // can't shrink below their min-intrinsic size"). The line is laid out
1479
+ // with it so, and the items after it start where it ends: Yoga had it
1480
+ // as small as its line, and the next item over the rest of it — read
1481
+ // as a block's content, two pixels into it, the spacing under its rows
1482
+ if (box.kind === 'table') {
1483
+ // as wide as its columns at their widest, where Yoga measured that,
1484
+ // it has room for them at their narrowest, which is a layout less
1485
+ const widest = row ? MAX_CONTENT.get(box) : undefined;
1486
+ if (widest !== undefined &&
1487
+ laid.main >= widest + box.horizontalExtra - 0.01) {
1488
+ continue;
1489
+ }
1490
+ const least = row
1491
+ ? minContentOf(box, ctx, laid)
1492
+ : tableContent(box, ctx, width, containingWidth, laid);
1493
+ if (laid.main >= least - 0.01)
1494
+ continue;
1495
+ holdAt(node, laid, row, least, hold);
1496
+ changed = true;
1497
+ continue;
1498
+ }
1092
1499
  // a box that scrolls or clips has none, but for one that asks for its
1093
1500
  // content's height
1094
1501
  const asked = !row && style.minHeightKeyword !== null;
1095
1502
  if (!asked && scrolls(style)) {
1096
1503
  continue;
1097
1504
  }
1098
- const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
1099
1505
  // a replaced item's content along a row is its natural width
1100
1506
  if (row && box.kind === 'replaced') {
1101
1507
  if (style.minWidth !== AUTO || style.minWidthKeyword)
@@ -1220,30 +1626,9 @@ function autoMinimums(items, row, ctx, containingWidth, hold) {
1220
1626
  else {
1221
1627
  // its height, or where it has one of its own and its content comes to
1222
1628
  // less, its content's: the lesser of the two (4.5)
1223
- let content = read;
1224
- if (!Number.isNaN(content)) {
1225
- // read before
1226
- }
1227
- else if (box.kind === 'replaced')
1228
- content = laid.height;
1229
- else if (style.height !== AUTO && percentHeightsIn(box)) {
1230
- // what its content comes to where it has no height to take
1231
- // percentages of, as an intrinsic size is measured: laid out so
1232
- // apart, and the final pass lays it out again
1233
- FLEXED_HEIGHT.set(box, NaN);
1234
- try {
1235
- ctx.layoutSubtree(box, width, containingWidth);
1236
- }
1237
- finally {
1238
- FLEXED_HEIGHT.delete(box);
1239
- }
1240
- content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1241
- laid.width = NaN;
1242
- }
1243
- else {
1244
- content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1245
- remember(box, ctx, width, containingWidth, 'content', content);
1246
- }
1629
+ let content = Number.isNaN(read)
1630
+ ? columnContent(box, ctx, laid, width, containingWidth)
1631
+ : read;
1247
1632
  // within what the ratio makes of its least and greatest widths
1248
1633
  if (box.kind !== 'replaced') {
1249
1634
  content = transferredHeight(box, content, containingWidth);
@@ -1263,6 +1648,47 @@ function autoMinimums(items, row, ctx, containingWidth, hold) {
1263
1648
  }
1264
1649
  return changed;
1265
1650
  }
1651
+ /**
1652
+ * How far down a column item's content comes at a border-box width, its
1653
+ * border box's, from the layout its content's own height makes of it
1654
+ * there (`layoutNaturally`), which it has: what a height of its own has no
1655
+ * say in, which its automatic minimum reads (CSS Flexbox 4.5), and a flex
1656
+ * basis of `content` (9.2.3, E).
1657
+ */
1658
+ function columnContent(box, ctx, laid, width, containingWidth) {
1659
+ if (box.kind === 'replaced')
1660
+ return laid.height;
1661
+ if (box.style.height !== AUTO && percentHeightsIn(box)) {
1662
+ // what its content comes to where it has no height to take
1663
+ // percentages of, as an intrinsic size is measured: laid out so
1664
+ // apart, and the final pass lays it out again
1665
+ FLEXED_HEIGHT.set(box, NaN);
1666
+ try {
1667
+ ctx.layoutSubtree(box, width, containingWidth);
1668
+ }
1669
+ finally {
1670
+ FLEXED_HEIGHT.delete(box);
1671
+ }
1672
+ laid.width = NaN;
1673
+ return ratioContent(box, contentBottom(box) + box.verticalExtra);
1674
+ }
1675
+ const content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1676
+ remember(box, ctx, width, containingWidth, 'content', content);
1677
+ return content;
1678
+ }
1679
+ /** A table item's rows and captions at a border-box width, its border box's
1680
+ * height apart from one it sets (`TABLE_CONTENT`): read from its layout
1681
+ * there, which it is given where it has another, and kept for the pass. */
1682
+ function tableContent(box, ctx, width, containing, laid) {
1683
+ const kept = keptNatural(box, ctx, width, containing);
1684
+ if (kept !== null && !Number.isNaN(kept.content))
1685
+ return kept.content;
1686
+ // where the box has that layout still, it is put back and not made again
1687
+ layoutNaturally(box, ctx, width, containing, laid);
1688
+ const content = TABLE_CONTENT.get(box) ?? box.height;
1689
+ remember(box, ctx, width, containing, 'content', content);
1690
+ return content;
1691
+ }
1266
1692
  /** A laid-out item's content height, its border box's: no less than its
1267
1693
  * width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
1268
1694
  function ratioContent(box, content) {
@@ -1300,13 +1726,16 @@ function ratioContent(box, content) {
1300
1726
  * from its shrink factors is exactly what it added.
1301
1727
  */
1302
1728
  function holdAt(node, laid, row, least, hold) {
1303
- if (row)
1729
+ if (row) {
1304
1730
  node.setMinWidth(least);
1731
+ laid.narrowest = least;
1732
+ }
1305
1733
  else
1306
1734
  node.setMinHeight(least);
1307
1735
  if (!hold.frozen)
1308
1736
  return;
1309
1737
  laid.auto = true;
1738
+ laid.minimum = least;
1310
1739
  freezeAt(node, laid, row, least, hold);
1311
1740
  }
1312
1741
  /** Freeze an item at a size along the main axis: inflexible, and that size
@@ -1322,63 +1751,13 @@ function freezeAt(node, laid, row, size, hold) {
1322
1751
  else
1323
1752
  node.setHeight(size);
1324
1753
  }
1325
- /**
1326
- * Put right the one line of a box that does not wrap, where every item of
1327
- * it that may shrink is stopped by its minimum and Yoga made them wider for
1328
- * it; whether Yoga was asked again. The line is laid out with each of them
1329
- * frozen at its minimum, which is where CSS Flexbox leaves them (9.7, step
1330
- * 4): the line is short of room with every one of them as small as it
1331
- * goes.
1332
- *
1333
- * Yoga shares what a line is short of in two passes. The first takes each
1334
- * item a minimum stops out of the sum of the scaled shrink factors, one
1335
- * subtraction an item, and the second divides what the line is still short
1336
- * of by what is left of the sum. With every item stopped that is nothing:
1337
- * `(a + b) - a - b`, in float32s, which is 0 for some sizes and a rounding
1338
- * either side of it for others, 2^-18 for sizes about a hundred. Yoga
1339
- * 3.2.1 tests for 0, and the guard it has had since, a sum under 1e-6, is
1340
- * under that rounding (react/yoga#1665, #1974). A negative rounding sends
1341
- * every item under its minimum, which stops it, and a positive one makes
1342
- * each of them wider by billions of pixels: `width: 110.992px; min-width:
1343
- * 110.992px` beside `62.705625px` of the same, in a row 50 wide, were
1344
- * 3599091712 and 2033329408 wide, where 100 and 60 were as they are. So
1345
- * were two items with no minimum but their padding, and two with
1346
- * `min-width: 0` beside one that does not shrink and is wider than the row
1347
- * alone.
1348
- *
1349
- * A minimum of the item's own is Yoga's to hold it to, so this looks at
1350
- * its answer, for a line that is two things at once. It is short of room
1351
- * with every item that may shrink at its minimum — where 9.7 leaves each
1352
- * of them at that, whatever their sizes. And one of them is wider than its
1353
- * hypothetical size (9.7, step 2), which no sharing out of what a line is
1354
- * short of makes an item: Yoga is asked for the line with nothing
1355
- * shrinking, where each item is that size. Any other line is put back as
1356
- * it was, the two layouts that took being the cost of one that is short of
1357
- * room at its minimums and was not shared out to them; a line with room
1358
- * for its minimums, or one whose every item sits at its own, has cost the
1359
- * sum of its sizes.
1360
- *
1361
- * An automatic minimum is no part of it: an item held at one is frozen
1362
- * already (`holdAt`), and one not yet held has no minimum to Yoga but its
1363
- * padding and border, where this leaves it for `autoMinimums` to find
1364
- * short of its content.
1365
- */
1366
- function stoppedLine(items, row,
1367
- /** The flex box's size along the main axis. */
1368
- room,
1369
- /** The gap between two items. */
1370
- gap, hold,
1371
- /** Lay the flex box out, as its nodes now are. */
1372
- ask) {
1373
- // what is between the items' border boxes: the gaps, and their margins
1374
- let between = gap * (items.length - 1);
1375
- // the line's size with each item that may shrink at its minimum
1376
- let least = 0;
1377
- let shrinking = 0;
1378
- let over = false;
1379
- for (const { box, laid } of items) {
1754
+ /** What is between the border boxes of a line's items along the main
1755
+ * axis: the gaps, and their margins, an `auto` one none — which is what
1756
+ * it is while the line is shared out (CSS Flexbox 9.7, 8.1). */
1757
+ function betweenOf(items, row, gap) {
1758
+ let between = gap * Math.max(0, items.length - 1);
1759
+ for (const { box } of items) {
1380
1760
  const style = box.style;
1381
- // an `auto` margin is none on a line with no room (8.1)
1382
1761
  if (row) {
1383
1762
  if (style.marginLeft !== AUTO)
1384
1763
  between += box.marginLeft;
@@ -1391,67 +1770,626 @@ ask) {
1391
1770
  if (style.marginBottom !== AUTO)
1392
1771
  between += box.marginBottom;
1393
1772
  }
1394
- if (!shrinks(box, laid))
1395
- least += laid.main;
1396
- else {
1397
- const min = leastOf(box, laid, row);
1398
- shrinking += 1;
1399
- least += min;
1400
- if (laid.main > min + 0.01)
1401
- over = true;
1402
- }
1403
1773
  }
1404
- // two at the least: what is left of a sum of one, less it, is 0 exactly.
1405
- // A line within a thousandth of a pixel of room is short of it: what
1406
- // Yoga makes of that is as far out, and the minimums are that near
1407
- if (shrinking < 2 || !over || !(between + least > room - SHORT)) {
1774
+ return between;
1775
+ }
1776
+ /**
1777
+ * Share out the one line of a box that does not wrap as CSS Flexbox 9.7
1778
+ * does, where Yoga's answer for it may not be that, and freeze every item
1779
+ * at the size it comes to; whether Yoga was asked again.
1780
+ *
1781
+ * Yoga 3.2.1 shares a line out in two passes where 9.7 goes round until
1782
+ * no item's limit stops it, and each pass has its own way of coming out
1783
+ * elsewhere:
1784
+ *
1785
+ * - the first takes each item a limit stops out of the sum of the flex
1786
+ * factors as it goes, and divides what the line is short of by that
1787
+ * running sum, but takes the item's size off what the line is short of
1788
+ * only once it has been round them all (react/yoga#2006, put right on
1789
+ * Yoga's `main` behind an errata bit and in no release). Every item
1790
+ * after the first one stopped is shrunk too far for it, and may be
1791
+ * stopped too: `width: 100px` three times with `min-width` 95px, 45px
1792
+ * and 0 in a row of 150 was 100, 100 and 100 wide, and overflowed it,
1793
+ * where 9.7 has 95, 45 and 10 — with every item stopped, the line was
1794
+ * left no shorter than it started, and the second pass shared out
1795
+ * nothing. Where what is left of the sum is a float's rounding of
1796
+ * nothing, the second pass divided by that, and each item came out
1797
+ * billions of pixels wide, or none;
1798
+ * - it weighs an item's share of a shortfall by its flex base size where
1799
+ * the item is stopped by a limit before it is shrunk, and adds it to the
1800
+ * sum at its size within the limit, or the other way round: `width:
1801
+ * 200px; max-width: 110.992px` beside `200px` within `62.706px`, each
1802
+ * with a `min-width`, in a row of 50 were 76.67 and 43.31, where 9.7
1803
+ * shrinks both from 200 and has them at their minimums;
1804
+ * - and with no limit at work, it weighs that share by the flex base size,
1805
+ * its border box's, where 9.7 weighs it by the inner flex base size, its
1806
+ * content box's (step 4c), so an item gives up more the more padding
1807
+ * and borders it has (`weighsPadding`);
1808
+ * - a limit that is more than the flex base size stops the item before
1809
+ * any sharing out, and Yoga shares out from there: `flex: 1;
1810
+ * min-width: 200px` beside a `flex: 1` in a row of 600 was 400 and
1811
+ * 200, where each grows from 0 and is 300;
1812
+ * - where the shrink factors come to less than 1, the line keeps that
1813
+ * part of what it is short of (9.7, step 4b), and Yoga shares all of it
1814
+ * out: a `flex-shrink: 0.5` alone was as wide as its row, where it
1815
+ * keeps half of what it overflows by.
1816
+ *
1817
+ * Which is why a line is looked at only where Yoga's answer shows one of
1818
+ * these could be at work (`unshared`), and else left as it is: a line
1819
+ * none of whose items is stopped, that has been shared out, whose items'
1820
+ * limits do not clamp their flex base sizes, whose shrink factors come to
1821
+ * 1 or more, and which is not short of room with padding and borders more
1822
+ * of one item's flex base size than of another's is shared out by Yoga as
1823
+ * by 9.7.
1824
+ *
1825
+ * Each item's flex base size is what its style says it is, or its
1826
+ * content's size as the measure function found it (`Laid.sized`), and
1827
+ * where neither is known, Yoga is asked for the line with no limits and
1828
+ * nothing flexing, where each item is that size (`askBases`). An item held
1829
+ * at its automatic minimum is frozen there already (`holdAt`), and the
1830
+ * rest are shared out around it.
1831
+ */
1832
+ function resolveLine(items, row, line,
1833
+ /** The size along the main axis Yoga laid the line out in. */
1834
+ room, hold,
1835
+ /** Lay the flex box out, as its nodes now are. */
1836
+ ask) {
1837
+ if (!unshared(items, row, room - line.between, line.probe))
1408
1838
  return false;
1839
+ let unknown = false;
1840
+ for (const { box, laid } of items) {
1841
+ laid.base = baseOf(box, laid, row, line.probe);
1842
+ if (Number.isNaN(laid.base) && Number.isNaN(laid.held))
1843
+ unknown = true;
1409
1844
  }
1410
- const stopped = items.filter(({ box, laid }) => shrinks(box, laid));
1411
- for (const { node } of stopped)
1412
- node.setFlexShrink(0);
1845
+ if (unknown)
1846
+ askBases(items, row, ask);
1847
+ const sizes = shareLine(items, row, line);
1848
+ if (items.every(({ laid }, i) => Math.abs(sizes[i] - laid.main) <= NEAR)) {
1849
+ // Yoga's answer, which asking for the bases has to be put back to
1850
+ if (unknown)
1851
+ ask();
1852
+ return unknown;
1853
+ }
1854
+ line.resolved = true;
1855
+ freezeLine(items, row, sizes, hold);
1413
1856
  ask();
1414
- // each item its hypothetical size, which one that does not shrink is on
1415
- // a line short of room — where Yoga may have grown it, on a line it took
1416
- // for having some
1417
- let wider = false;
1418
- least = 0;
1419
- for (const { box, node, laid } of items) {
1420
- const hypothetical = row
1421
- ? node.getComputedWidth()
1422
- : node.getComputedHeight();
1423
- if (!shrinks(box, laid))
1424
- least += hypothetical;
1425
- else {
1426
- least += leastOf(box, laid, row);
1427
- if (laid.main > hypothetical && !nearly(laid.main, hypothetical)) {
1428
- wider = true;
1429
- }
1857
+ return true;
1858
+ }
1859
+ /** Freeze each item of a line at the size it was shared out to, but for
1860
+ * one held at its automatic minimum whose flex base size is not known,
1861
+ * which is frozen there already. */
1862
+ function freezeLine(items, row, sizes, hold) {
1863
+ items.forEach(({ node, laid }, i) => {
1864
+ if (Number.isNaN(laid.base))
1865
+ return;
1866
+ freezeAt(node, laid, row, sizes[i], hold);
1867
+ // its content's size, where that is what it came to (`Laid.auto`)
1868
+ laid.auto = sizes[i] <= laid.minimum + NEAR;
1869
+ });
1870
+ }
1871
+ /** How near to its room a line may be and have room to spare
1872
+ * (`cappedLines`). */
1873
+ const SHORT = 0.001;
1874
+ /** Share a line this engine has shared out again from its items' flex base
1875
+ * sizes, and freeze each item at its size: where one is since held at its
1876
+ * content's size, the rest of the line is shared out around it. */
1877
+ function shareOut(items, row, line, hold) {
1878
+ const sizes = shareLine(items, row, line);
1879
+ freezeLine(items, row, sizes, hold);
1880
+ }
1881
+ /** Each item's size along the main axis as 9.7 shares the line out from
1882
+ * their flex base sizes (`Laid.base`); one held at its automatic minimum
1883
+ * is frozen there. */
1884
+ function shareLine(items, row, line) {
1885
+ const flexing = items.map(({ box, laid }) => {
1886
+ // an automatic minimum it is held to is its minimum, which 9.7 freezes
1887
+ // it at where the line shares out less to it — and not before, which
1888
+ // would leave it out of the free space the line started with (4b)
1889
+ let least = leastOf(box, laid, row);
1890
+ if (!Number.isNaN(laid.minimum))
1891
+ least = Math.max(least, laid.minimum);
1892
+ return {
1893
+ base: laid.base,
1894
+ least,
1895
+ most: Math.max(mostOf(box, laid, row), least),
1896
+ extra: row ? box.horizontalExtra : box.verticalExtra,
1897
+ grow: box.style.flexGrow,
1898
+ shrink: box.style.flexShrink,
1899
+ frozen: Number.isNaN(laid.base) ? laid.held : NaN,
1900
+ };
1901
+ });
1902
+ let room = line.size;
1903
+ if (Number.isNaN(room)) {
1904
+ // down a column that is as tall as its items, within its limits: the
1905
+ // sum of their hypothetical sizes (9.9.1)
1906
+ room = line.between;
1907
+ for (const item of flexing)
1908
+ room += hypotheticalOf(item);
1909
+ room = Math.max(line.floor, Math.min(line.ceiling, room));
1910
+ }
1911
+ return resolveLengths(flexing, room - line.between);
1912
+ }
1913
+ /**
1914
+ * Whether Yoga's answer for the one line of a box that does not wrap may
1915
+ * not be what CSS Flexbox 9.7 makes of it (`resolveLine`): where an item
1916
+ * has a limit of its own along the main axis, which 9.7 is then asked —
1917
+ * sums of numbers, where each item's flex base size is known — and where
1918
+ * none has, what shows in Yoga's answer: an item a limit stopped as the
1919
+ * line was shared out, which with none of its own is its padding and
1920
+ * borders, a line not shared out while an item may yet flex the way it is
1921
+ * out, or flex factors that come to less than 1, which Yoga shares out as
1922
+ * though they came to 1 — or, for one item that may grow and shrink, from
1923
+ * a flex basis of nothing.
1924
+ */
1925
+ function unshared(items, row,
1926
+ /** The room for the items' border boxes. */
1927
+ room,
1928
+ /** Whether the line is a min-content probe's (`FlexLine.probe`). */
1929
+ probe) {
1930
+ let sum = 0;
1931
+ for (const { laid } of items)
1932
+ sum += laid.main;
1933
+ const over = sum > room + NEAR;
1934
+ const under = sum < room - NEAR;
1935
+ let grows = 0;
1936
+ let shrinks = 0;
1937
+ let growable = false;
1938
+ for (const { box, laid } of items) {
1939
+ // held at its content's size, and frozen (`holdAt`)
1940
+ if (!Number.isNaN(laid.held))
1941
+ continue;
1942
+ if (!Number.isNaN(laid.least) || !Number.isNaN(laid.most))
1943
+ return true;
1944
+ const { flexGrow: grow, flexShrink: shrink } = box.style;
1945
+ const least = leastOf(box, laid, row);
1946
+ const base = baseOf(box, laid, row, probe);
1947
+ const size = laid.main;
1948
+ grows += grow;
1949
+ shrinks += shrink;
1950
+ // stopped by its padding where it was shrunk to it, and not where it
1951
+ // is that size unflexed
1952
+ if (shrink > 0 &&
1953
+ !under &&
1954
+ size <= least + NEAR &&
1955
+ !(base <= size + NEAR)) {
1956
+ return true;
1430
1957
  }
1958
+ if (over && shrink > 0 && size > least + NEAR)
1959
+ return true;
1960
+ if (under && grow > 0)
1961
+ growable = true;
1431
1962
  }
1432
- const freeze = wider && between + least > room - SHORT;
1433
- for (const { box, node, laid } of stopped) {
1434
- if (freeze)
1435
- freezeAt(node, laid, row, leastOf(box, laid, row), hold);
1963
+ return ((growable && grows >= 1) ||
1964
+ (!under && shrinks > 0 && shrinks < 1) ||
1965
+ (!over && grows > 0 && grows < 1) ||
1966
+ (!under && weighsPadding(items, row, room, probe)));
1967
+ }
1968
+ /**
1969
+ * Whether Yoga may have shared out what a line is short of by its items'
1970
+ * padding and borders, where 9.7 leaves them out: Yoga weighs an item's
1971
+ * share by its flex shrink factor times its flex base size, its border
1972
+ * box's, and 9.7 by the factor times its inner flex base size, its content
1973
+ * box's (step 4c). The two are the same shares where padding and borders
1974
+ * are the same part of the flex base size of every item that shrinks —
1975
+ * none of it, most often — and else Yoga takes more from an item the more
1976
+ * of them it has: `width: 100px; padding-left: 50px` beside `width: 100px`
1977
+ * in a row of 150 was 90 and 60 wide, where each gives up 50 of its
1978
+ * content and is 100 and 50. Where an item's flex base size is not known,
1979
+ * its padding may be any part of it, and the line is looked at.
1980
+ */
1981
+ function weighsPadding(items, row,
1982
+ /** The room for the items' border boxes. */
1983
+ room, probe) {
1984
+ let sum = 0;
1985
+ let shrinking = 0;
1986
+ let padded = false;
1987
+ let unknown = false;
1988
+ // the part of the first flex base size that shrinks that is padding and
1989
+ // borders, and whether that of another is not the same
1990
+ let part = NaN;
1991
+ let uneven = false;
1992
+ for (const { box, laid } of items) {
1993
+ if (!Number.isNaN(laid.held)) {
1994
+ sum += laid.held;
1995
+ continue;
1996
+ }
1997
+ const base = baseOf(box, laid, row, probe);
1998
+ if (Number.isNaN(base))
1999
+ unknown = true;
1436
2000
  else
1437
- node.setFlexShrink(box.style.flexShrink);
2001
+ sum += base;
2002
+ if (!(box.style.flexShrink > 0))
2003
+ continue;
2004
+ shrinking += 1;
2005
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
2006
+ if (extra > 0)
2007
+ padded = true;
2008
+ if (Number.isNaN(base))
2009
+ continue;
2010
+ const of = base > 0 ? extra / base : 0;
2011
+ if (Number.isNaN(part))
2012
+ part = of;
2013
+ else if (Math.abs(of - part) > 1e-9)
2014
+ uneven = true;
2015
+ }
2016
+ if (shrinking < 2 || !padded)
2017
+ return false;
2018
+ return unknown || (uneven && sum > room + NEAR);
2019
+ }
2020
+ /** How near two sizes along the main axis are the same, as Yoga's float32s
2021
+ * have them. */
2022
+ const NEAR = 0.01;
2023
+ /** An item's flex base size, its border box's, where this engine knows it:
2024
+ * what its style says, or its content's size as last measured — fitted
2025
+ * to no room, in a min-content probe (`FlexLine.probe`) — and no less than
2026
+ * its padding and borders, as Yoga has it. Else NaN. */
2027
+ function baseOf(box, laid, row, probe) {
2028
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
2029
+ const content = probe ? laid.fitted : laid.content;
2030
+ return Math.max(laid.sized ? content : laid.basis, extra);
2031
+ }
2032
+ /**
2033
+ * Give each item that is not frozen the flex base size Yoga makes of it,
2034
+ * where this engine does not know it (`baseOf`): the line laid out with no
2035
+ * limit on any of them along the main axis and none of them flexing, where
2036
+ * each is that size. Their limits and flex factors are put back after.
2037
+ */
2038
+ function askBases(items, row, ask) {
2039
+ const open = items.filter(({ laid }) => Number.isNaN(laid.held));
2040
+ for (const { node } of open) {
2041
+ if (row) {
2042
+ node.setMinWidth(Number.NaN);
2043
+ node.setMaxWidth(Number.NaN);
2044
+ }
2045
+ else {
2046
+ node.setMinHeight(Number.NaN);
2047
+ node.setMaxHeight(Number.NaN);
2048
+ }
2049
+ node.setFlexGrow(0);
2050
+ node.setFlexShrink(0);
1438
2051
  }
1439
2052
  ask();
1440
- return true;
2053
+ for (const { box, node, laid } of open) {
2054
+ if (Number.isNaN(laid.base)) {
2055
+ laid.base = row ? node.getComputedWidth() : node.getComputedHeight();
2056
+ }
2057
+ if (!Number.isNaN(laid.least)) {
2058
+ if (row)
2059
+ node.setMinWidth(laid.least);
2060
+ else
2061
+ node.setMinHeight(laid.least);
2062
+ }
2063
+ if (!Number.isNaN(laid.most)) {
2064
+ if (row)
2065
+ node.setMaxWidth(laid.most);
2066
+ else
2067
+ node.setMaxHeight(laid.most);
2068
+ }
2069
+ node.setFlexGrow(box.style.flexGrow);
2070
+ node.setFlexShrink(box.style.flexShrink);
2071
+ }
1441
2072
  }
1442
- /** Whether Yoga may shrink an item: it has a shrink factor, and is not
1443
- * frozen (`freezeAt`). */
1444
- function shrinks(box, laid) {
1445
- return box.style.flexShrink > 0 && Number.isNaN(laid.held);
2073
+ /** An item's hypothetical main size: its flex base size within its limits
2074
+ * (CSS Flexbox 9.2, step 3), or where it is frozen, that size. */
2075
+ function hypotheticalOf(item) {
2076
+ return Number.isNaN(item.frozen)
2077
+ ? Math.max(item.least, Math.min(item.most, item.base))
2078
+ : item.frozen;
2079
+ }
2080
+ /**
2081
+ * Resolve the flexible lengths of a line (CSS Flexbox 9.7): each item's
2082
+ * size along the main axis, sharing out the room its border boxes have
2083
+ * from their flex base sizes, and going round again with each item a limit
2084
+ * stops frozen at it until none is — the minimums where the line is short
2085
+ * of more than the maximums give back, and the other way round.
2086
+ */
2087
+ function resolveLengths(line, room) {
2088
+ const count = line.length;
2089
+ const size = line.map(hypotheticalOf);
2090
+ let sum = 0;
2091
+ for (const s of size)
2092
+ sum += s;
2093
+ // step 1: grow where the line has room at the items' hypothetical sizes
2094
+ const grow = sum < room;
2095
+ // step 2: an item that does not flex, or would flex away from its limit,
2096
+ // is frozen at its hypothetical size — a flex base size Yoga worked out
2097
+ // being as near to that as its float32s come, and no further
2098
+ const frozen = line.map((item, i) => !Number.isNaN(item.frozen) ||
2099
+ (grow ? item.grow : item.shrink) === 0 ||
2100
+ (grow ? item.base > size[i] + NEAR : item.base < size[i] - NEAR));
2101
+ const free = () => {
2102
+ let left = room;
2103
+ for (let i = 0; i < count; i++)
2104
+ left -= frozen[i] ? size[i] : line[i].base;
2105
+ return left;
2106
+ };
2107
+ // step 3
2108
+ const initial = free();
2109
+ const off = new Array(count).fill(0);
2110
+ for (;;) {
2111
+ // step 4a
2112
+ let factors = 0;
2113
+ let open = false;
2114
+ for (let i = 0; i < count; i++) {
2115
+ if (frozen[i])
2116
+ continue;
2117
+ open = true;
2118
+ factors += grow ? line[i].grow : line[i].shrink;
2119
+ }
2120
+ if (!open)
2121
+ break;
2122
+ // 4b: flex factors that come to less than 1 share out that part of the
2123
+ // free space the line started with
2124
+ let left = free();
2125
+ if (factors < 1) {
2126
+ const part = initial * factors;
2127
+ if (Math.abs(part) < Math.abs(left))
2128
+ left = part;
2129
+ }
2130
+ // 4c: shared by the grow factors, or by the shrink factors each times
2131
+ // the item's inner flex base size
2132
+ let scaled = 0;
2133
+ if (!grow) {
2134
+ for (let i = 0; i < count; i++) {
2135
+ if (!frozen[i])
2136
+ scaled += line[i].shrink * (line[i].base - line[i].extra);
2137
+ }
2138
+ }
2139
+ // 4d: each within its limits, and how far that took it
2140
+ let violation = 0;
2141
+ for (let i = 0; i < count; i++) {
2142
+ if (frozen[i])
2143
+ continue;
2144
+ const item = line[i];
2145
+ let target = item.base;
2146
+ if (grow)
2147
+ target += (left * item.grow) / factors;
2148
+ else if (scaled > 0) {
2149
+ target -=
2150
+ (Math.abs(left) * item.shrink * (item.base - item.extra)) / scaled;
2151
+ }
2152
+ size[i] = Math.max(item.least, Math.min(item.most, target));
2153
+ off[i] = size[i] - target;
2154
+ violation += off[i];
2155
+ }
2156
+ // 4e: freeze those stopped the way most of them were, or all of them
2157
+ for (let i = 0; i < count; i++) {
2158
+ if (frozen[i])
2159
+ continue;
2160
+ if (violation === 0 || (violation > 0 ? off[i] > 0 : off[i] < 0)) {
2161
+ frozen[i] = true;
2162
+ }
2163
+ }
2164
+ }
2165
+ return size;
1446
2166
  }
1447
- /** How near to its room a line may be and be short of it (`stoppedLine`). */
1448
- const SHORT = 0.001;
1449
2167
  /** The least an item may be along the main axis, its border box: the
1450
2168
  * minimum of its own, and no less than its padding and borders. */
1451
2169
  function leastOf(box, laid, row) {
1452
2170
  const extra = row ? box.horizontalExtra : box.verticalExtra;
1453
2171
  return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
1454
2172
  }
2173
+ /**
2174
+ * Put right the lines of a flex box that wraps on which every item that
2175
+ * may grow is stopped by its maximum and Yoga made them smaller for it;
2176
+ * whether Yoga was asked again. The one line of a box that does not wrap
2177
+ * is shared out by 9.7 here where Yoga's answer may not be its
2178
+ * (`resolveLine`), which puts right this and more. Each such line is laid out with every item of it that
2179
+ * may grow frozen at its maximum, which is where CSS Flexbox leaves them
2180
+ * (9.7, step 4): the line has room left over with every one of them as
2181
+ * large as it goes. It is also where Yoga leaves them itself, where its
2182
+ * rounding is not below 0.
2183
+ *
2184
+ * It is a float's rounding of nothing. Yoga's first pass takes
2185
+ * each item its maximum stops out of the sum of the grow factors, and the
2186
+ * second divides the room still left by what is left of the sum: with
2187
+ * every item stopped, `(a + b + c) - a - b - c` in float32s. Where that is
2188
+ * 0, or a rounding above it, the quotient is vast and each item is held to
2189
+ * its maximum, which is right; where it is a rounding below, each is held
2190
+ * to its minimum. Three items of `flex-grow: 3.361`, `2.417` and `1.988`,
2191
+ * each `max-width: 10px`, in a row 100 wide came out nothing wide, as a
2192
+ * third of such lines of random factors do. Sums of whole numbers, and of
2193
+ * halves and quarters of them, are exact, so a box whose factors are those
2194
+ * is not looked at (`exactSums`).
2195
+ *
2196
+ * What makes a line one to put right is Yoga's answer. Every item of
2197
+ * the line that may grow has a
2198
+ * maximum, and the line has room to spare with each of them at it. And
2199
+ * one of them came out short of its maximum and no larger than its
2200
+ * hypothetical size (9.7, step 2), which Yoga, sharing out room, makes no
2201
+ * item: Yoga is asked for the line with nothing growing, where each item
2202
+ * is that size. No larger rather than smaller, because an item held to
2203
+ * its minimum is often that size: an empty one is nothing wide either
2204
+ * way. Any other line is put back as it was, and the two layouts that
2205
+ * took are the cost of a box with a factor that is not a whole number and
2206
+ * an item short of its maximum; any other box has cost a sum.
2207
+ *
2208
+ * A few of the lines put right are still not a browser's. Once
2209
+ * some of its items are frozen, 9.7 shares out to the rest only the part
2210
+ * of the room their factors come to, where that is less than 1 (step 4,
2211
+ * b), and Yoga has no such rule: an item of `flex-grow: 0.1` left alone
2212
+ * on a line is at its maximum here, as Yoga would have had it, and a
2213
+ * tenth of the room in Chrome.
2214
+ *
2215
+ * A box that wraps has lines like it: the last line of a grid of cards
2216
+ * with a `max-width` has room for every card it has. Yoga breaks lines by
2217
+ * its items' hypothetical sizes, which the layout with nothing growing
2218
+ * gives, and they are broken here as it breaks them (`linesOf`). Freezing
2219
+ * a line's items at their maximums moves no break: the line has room for
2220
+ * every item on it at that size, so it keeps them all, and the item after
2221
+ * it, which did not fit with them smaller, fits no better with them larger.
2222
+ * But Yoga holds a size to an item's maximum before its minimum, so where
2223
+ * the maximum is the smaller — or smaller than the item's padding and
2224
+ * borders — it breaks a line by one size and lays the item out at another:
2225
+ * `max-width: 2px; min-width: 10px` is 10 wide, on a line it was put on
2226
+ * as 2. A box that wraps with an item like that is left as Yoga had it.
2227
+ */
2228
+ function cappedLines(items, row,
2229
+ /** The flex box's size along the main axis. */
2230
+ room,
2231
+ /** The size Yoga breaks lines at: the flex box's where it wraps and has
2232
+ * one, and else Infinity, for the one line. */
2233
+ breaks,
2234
+ /** The gap between two items. */
2235
+ gap, hold,
2236
+ /** Lay the flex box out, as its nodes now are. */
2237
+ ask) {
2238
+ // two at the least: what is left of a sum of one, less it, is 0 exactly
2239
+ const growing = items.filter(({ box, laid }) => grows(box, laid));
2240
+ if (growing.length < 2 || exactSums(growing))
2241
+ return false;
2242
+ // one short of its maximum, as each on such a line is
2243
+ const short = growing.some(({ box, laid }) => laid.main < mostOf(box, laid, row) - 0.01);
2244
+ if (!short)
2245
+ return false;
2246
+ if (breaks === Infinity) {
2247
+ // and on the one line, every one of them with a maximum, and room left
2248
+ // over as Yoga has it
2249
+ let used = gap * (items.length - 1);
2250
+ for (const { box, laid } of items)
2251
+ used += laid.main + marginsOf(box, row);
2252
+ const capped = growing.every(({ box, laid }) => !Number.isNaN(laid.most));
2253
+ if (!capped || !(used < room - SHORT))
2254
+ return false;
2255
+ }
2256
+ else if (
2257
+ // and on lines broken where the layout says, with no item that Yoga
2258
+ // breaks a line by at another size
2259
+ items.some(({ box, laid }) => laid.most < leastOf(box, laid, row))) {
2260
+ return false;
2261
+ }
2262
+ const flexible = items.filter(({ laid }) => Number.isNaN(laid.held));
2263
+ for (const { node } of flexible) {
2264
+ node.setFlexGrow(0);
2265
+ node.setFlexShrink(0);
2266
+ }
2267
+ ask();
2268
+ // each item its hypothetical size, on every line
2269
+ const hypothetical = items.map(({ node, laid }) => row ? node.getComputedWidth() : heightOf(node, laid));
2270
+ const frozen = new Set();
2271
+ for (const line of linesOf(items, hypothetical, row, breaks, gap)) {
2272
+ const growers = line.filter((i) => grows(items[i].box, items[i].laid));
2273
+ if (growers.length < 2)
2274
+ continue;
2275
+ // the line with each item that may grow at its maximum: Infinity where
2276
+ // one has none
2277
+ let size = gap * (line.length - 1);
2278
+ let smaller = false;
2279
+ for (const i of line) {
2280
+ const { box, laid } = items[i];
2281
+ size += marginsOf(box, row);
2282
+ if (!grows(box, laid)) {
2283
+ size += hypothetical[i];
2284
+ continue;
2285
+ }
2286
+ const most = mostOf(box, laid, row);
2287
+ size += most;
2288
+ if (laid.main < most - 0.01 &&
2289
+ (laid.main < hypothetical[i] || nearly(laid.main, hypothetical[i]))) {
2290
+ smaller = true;
2291
+ }
2292
+ }
2293
+ if (!smaller || !(size < room - SHORT))
2294
+ continue;
2295
+ for (const i of growers)
2296
+ frozen.add(items[i].box);
2297
+ }
2298
+ for (const { box, node, laid } of flexible) {
2299
+ if (frozen.has(box)) {
2300
+ freezeAt(node, laid, row, mostOf(box, laid, row), hold);
2301
+ }
2302
+ else {
2303
+ node.setFlexGrow(box.style.flexGrow);
2304
+ node.setFlexShrink(box.style.flexShrink);
2305
+ }
2306
+ }
2307
+ ask();
2308
+ return true;
2309
+ }
2310
+ /**
2311
+ * The lines Yoga breaks a flex box's items into, as indices of them. A line
2312
+ * takes items while their hypothetical sizes, their margins and the gaps
2313
+ * between them come to no more than the size it breaks at, and always
2314
+ * takes one — added up in float32s, in the order Yoga adds them, so that an
2315
+ * item that fits exactly is on the line it is on in Yoga's.
2316
+ */
2317
+ function linesOf(items,
2318
+ /** Each item's hypothetical size along the main axis. */
2319
+ sizes, row, breaks, gap) {
2320
+ if (breaks === Infinity)
2321
+ return [items.map((_, i) => i)];
2322
+ const f = Math.fround;
2323
+ const limit = f(breaks);
2324
+ const lines = [];
2325
+ let line = [];
2326
+ let used = 0;
2327
+ items.forEach(({ box }, i) => {
2328
+ const size = f(sizes[i]);
2329
+ const style = box.style;
2330
+ const [start, end] = row
2331
+ ? [style.marginLeft, style.marginRight]
2332
+ : [style.marginTop, style.marginBottom];
2333
+ const margin = f((start === AUTO ? 0 : f(row ? box.marginLeft : box.marginTop)) +
2334
+ (end === AUTO ? 0 : f(row ? box.marginRight : box.marginBottom)));
2335
+ let lead = line.length ? f(gap) : 0;
2336
+ if (line.length && f(f(f(used + size) + margin) + lead) > limit) {
2337
+ lines.push(line);
2338
+ line = [];
2339
+ used = 0;
2340
+ lead = 0;
2341
+ }
2342
+ used = f(used + f(f(size + margin) + lead));
2343
+ line.push(i);
2344
+ });
2345
+ lines.push(line);
2346
+ return lines;
2347
+ }
2348
+ /** Whether Yoga may grow an item: it has a grow factor, and is not frozen
2349
+ * (`freezeAt`). */
2350
+ function grows(box, laid) {
2351
+ return box.style.flexGrow > 0 && Number.isNaN(laid.held);
2352
+ }
2353
+ /** The most an item may be along the main axis, its border box: the
2354
+ * maximum of its own, where it has one, and no less than its least size;
2355
+ * Infinity where it has none. */
2356
+ function mostOf(box, laid, row) {
2357
+ if (Number.isNaN(laid.most))
2358
+ return Infinity;
2359
+ return Math.max(laid.most, leastOf(box, laid, row));
2360
+ }
2361
+ /** An item's margins along the main axis, where they are not `auto`: an
2362
+ * `auto` margin is none to the size of its line (CSS Flexbox 8.1). */
2363
+ function marginsOf(box, row) {
2364
+ const style = box.style;
2365
+ if (row) {
2366
+ return ((style.marginLeft === AUTO ? 0 : box.marginLeft) +
2367
+ (style.marginRight === AUTO ? 0 : box.marginRight));
2368
+ }
2369
+ return ((style.marginTop === AUTO ? 0 : box.marginTop) +
2370
+ (style.marginBottom === AUTO ? 0 : box.marginBottom));
2371
+ }
2372
+ /**
2373
+ * Whether Yoga's sums of these items' grow factors are exact: in float32s,
2374
+ * as it adds them, a sum of whole numbers is, and so is one of halves and
2375
+ * quarters of them, and what is left of it less each of its terms is then
2376
+ * 0 exactly (`cappedLines`).
2377
+ */
2378
+ function exactSums(items) {
2379
+ let sum = 0;
2380
+ for (const { box } of items) {
2381
+ const factor = box.style.flexGrow * EXACT;
2382
+ if (!Number.isInteger(factor))
2383
+ return false;
2384
+ sum += factor;
2385
+ }
2386
+ // every sum along the way a whole number of the parts under a float32's
2387
+ // 24 bits of significand
2388
+ return sum < 2 ** 24;
2389
+ }
2390
+ /** The parts of one a grow factor may be a whole number of and have its
2391
+ * sums exact (`exactSums`). */
2392
+ const EXACT = 1024;
1455
2393
  /** An item's min-content width, its border box's, taken the once: its
1456
2394
  * content's, whatever width it has of its own (`keywordWidth`'s too). */
1457
2395
  function minContentOf(box, ctx, laid) {
@@ -1537,6 +2475,100 @@ function baselineLines(style, items) {
1537
2475
  // one alone on its line is at its start already
1538
2476
  return several ? out : null;
1539
2477
  }
2478
+ /**
2479
+ * Whether Yoga made a line of a row that wraps the row's height, where its
2480
+ * items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
2481
+ * Where `align-content` stretches the lines, that is an item stretched
2482
+ * across one that is shorter than its content at the width it was given:
2483
+ * its line is as tall as that, past the box. Where it centres them or ends
2484
+ * them, it is the one line of the row not as tall as the row, which moves
2485
+ * it from the row's start — and so where it spaces them out around or
2486
+ * evenly, which centres one line (CSS Box Alignment 3, 5.1). Lines it
2487
+ * starts at the row's start are there in Yoga's layout as well, and so is
2488
+ * the one line `space-between` starts there.
2489
+ */
2490
+ function heldLine(container, ctx, items,
2491
+ /** The row's content height. */
2492
+ height,
2493
+ /** The row's content width, the items' containing block's. */
2494
+ width,
2495
+ /** The gap between two items along the row. */
2496
+ gap) {
2497
+ const style = container.style;
2498
+ const lines = crossContent(style);
2499
+ if (lines !== 'stretch' &&
2500
+ lines !== 'center' &&
2501
+ lines !== 'flex-end' &&
2502
+ lines !== 'space-around' &&
2503
+ lines !== 'space-evenly') {
2504
+ return false;
2505
+ }
2506
+ const stretched = items.filter(({ box }) => stretches(box, style));
2507
+ if (stretched.length === 0)
2508
+ return false;
2509
+ if (lines === 'stretch') {
2510
+ return stretched.some(({ box, node, laid }) => naturalAcross(box, ctx, laid, width) > heightOf(node, laid) + 0.01);
2511
+ }
2512
+ // one line, which the items come to no more than the row's width along:
2513
+ // Yoga made a line each of them only where they come to more
2514
+ let along = gap * (items.length - 1);
2515
+ for (const { box, laid } of items)
2516
+ along += laid.main + marginsOf(box, true);
2517
+ if (along > width + 0.01)
2518
+ return false;
2519
+ // as tall as its tallest item, as Yoga has them: it lays them out at
2520
+ // their content's height where `align-content` does not stretch lines
2521
+ let line = 0;
2522
+ for (const { box, node, laid } of items) {
2523
+ line = Math.max(line, heightOf(node, laid) + marginsOf(box, false));
2524
+ }
2525
+ return Math.abs(line - height) > 0.01;
2526
+ }
2527
+ /**
2528
+ * A row item's border-box height as its content makes it at the width the
2529
+ * flex layout gave it, within its limits: the layout `placeItems` gives it
2530
+ * there, made now where it has none (`layoutItemAt`), and a replaced one's
2531
+ * through its ratio or its natural height.
2532
+ */
2533
+ function naturalAcross(box, ctx, laid,
2534
+ /** The flex box's content width, the item's containing block's. */
2535
+ containing) {
2536
+ const width = meant(laid.main, laid.set, laid.stretch, laid.width, laid.held);
2537
+ if (box.kind === 'replaced') {
2538
+ const own = box.intrinsic;
2539
+ const natural = heightThroughRatio(box, width) ??
2540
+ (own && !(own.missing & 2) ? own.height + box.verticalExtra : null);
2541
+ return natural === null ? 0 : clampHeight(box, natural);
2542
+ }
2543
+ if (box.kind === 'text' || box.kind === 'break')
2544
+ return 0;
2545
+ const kept = naturalHeight(box, ctx, width, containing, laid);
2546
+ if (!Number.isNaN(kept))
2547
+ return kept;
2548
+ layoutNaturally(box, ctx, width, containing, laid);
2549
+ return box.height;
2550
+ }
2551
+ /**
2552
+ * The alignment an item with an `auto` margin across a row that wraps comes
2553
+ * to in its line (CSS Flexbox 8.1, 9.6 step 13), as Yoga is told it
2554
+ * (`layoutFlex`): at the end where the margin at the line's cross start is
2555
+ * `auto`, at the start where the one at its end is, and centred where both
2556
+ * are; null where neither is. Its line's cross start is the box's bottom
2557
+ * where its lines wrap in reverse.
2558
+ *
2559
+ * The line is as tall as its tallest item, margins and all, so the item
2560
+ * comes to no more than it, and the margins take a share of no less than
2561
+ * nothing: the alignment puts it where they would.
2562
+ */
2563
+ function autoAcross(item, reverse) {
2564
+ const top = item.marginTop === AUTO;
2565
+ const bottom = item.marginBottom === AUTO;
2566
+ if (top && bottom)
2567
+ return 'center';
2568
+ if (!top && !bottom)
2569
+ return null;
2570
+ return top !== reverse ? 'flex-end' : 'flex-start';
2571
+ }
1540
2572
  /**
1541
2573
  * How far an item is from where it belongs across its line, where Yoga
1542
2574
  * (3.2.1) aligned the lines in the pass it takes for a box that wraps or
@@ -1545,33 +2577,51 @@ function baselineLines(style, items) {
1545
2577
  * none at either — `items-start` in a wrapping row put every card's top
1546
2578
  * margin under it. By the margin at the start of the line's cross axis, and
1547
2579
  * by half what it exceeds the one at the end by, as Yoga holds them: for an
1548
- * item set at its line's start (`started`), its baseline's. A column's,
1549
- * whose cross axis runs right to left, and a box that wraps in reverse are
1550
- * left as Yoga has them.
2580
+ * item set at its line's start (`started`), its baseline's.
2581
+ *
2582
+ * A row whose lines wrap in reverse Yoga lays out as though they wrapped
2583
+ * forward, and then turns each item over across the box, its margins with
2584
+ * it: each is where the other margin would have put it. One at its line's
2585
+ * start, the bottom, stands on nothing; one at its end, the top, or
2586
+ * stretched, is its bottom margin down from its line's top where it is its
2587
+ * top one; and one centred, half the difference. A column's, whose cross
2588
+ * axis runs right to left, or that wraps in reverse, is left as Yoga has
2589
+ * it, and so is an item an `auto` margin aligns in a row that does not
2590
+ * wrap.
1551
2591
  */
1552
2592
  function lineMarginFix(container, item, node, row, started = false) {
1553
- if (container.flexWrap === 'wrap-reverse')
1554
- return 0;
1555
- if (!row && container.direction === 'rtl')
2593
+ const reverse = container.flexWrap === 'wrap-reverse';
2594
+ if (!row && (reverse || container.direction === 'rtl'))
1556
2595
  return 0;
1557
2596
  const style = item.style;
1558
- // a margin that is `auto` takes the item over its alignment
2597
+ // a margin that is `auto` takes the item over its alignment, which Yoga
2598
+ // is told it comes to in a row that wraps (`autoAcross`)
1559
2599
  const lead = row ? style.marginTop : style.marginLeft;
1560
2600
  const trail = row ? style.marginBottom : style.marginRight;
1561
- if (lead === AUTO || trail === AUTO)
1562
- return 0;
1563
- const align = started
1564
- ? 'flex-start'
1565
- : style.alignSelf === AUTO
1566
- ? container.alignItems
1567
- : style.alignSelf;
1568
- if (align !== 'flex-start' && align !== 'center')
1569
- return 0;
2601
+ let align;
2602
+ if (started)
2603
+ align = 'flex-start';
2604
+ else if (lead === AUTO || trail === AUTO) {
2605
+ align =
2606
+ row && container.flexWrap !== 'nowrap'
2607
+ ? autoAcross(style, reverse)
2608
+ : null;
2609
+ }
2610
+ else {
2611
+ align = style.alignSelf === AUTO ? container.alignItems : style.alignSelf;
2612
+ }
1570
2613
  const leading = node.getComputedMargin(row ? Y.EDGE_TOP : Y.EDGE_LEFT);
1571
- if (align === 'flex-start')
1572
- return leading;
1573
2614
  const trailing = node.getComputedMargin(row ? Y.EDGE_BOTTOM : Y.EDGE_RIGHT);
1574
- return (leading - trailing) / 2;
2615
+ switch (align) {
2616
+ case 'flex-start':
2617
+ return reverse ? -trailing : leading;
2618
+ case 'center':
2619
+ return (leading - trailing) / 2;
2620
+ case 'flex-end':
2621
+ case 'stretch':
2622
+ return reverse ? leading - trailing : 0;
2623
+ }
2624
+ return 0;
1575
2625
  }
1576
2626
  /** An item's first baseline, down from its border edge: its content's
1577
2627
  * first line's, or where it has none, or is replaced, one synthesized
@@ -1584,6 +2634,207 @@ function itemBaseline(item) {
1584
2634
  }
1585
2635
  return item.height;
1586
2636
  }
2637
+ /**
2638
+ * The items of a column that wraps that Yoga (3.2.1) lays out at another
2639
+ * height than it sized them at, as it stretches them across their lines:
2640
+ * each it aligns `stretch`, with no width of its own, whose left and right
2641
+ * margins come to another sum than its top and bottom ones.
2642
+ *
2643
+ * Yoga aligns the lines of a box that wraps in a pass of its own, after it
2644
+ * has sized and placed every item down them, and lays each item it
2645
+ * stretches out again there at its line's width — and at its height plus
2646
+ * the margins across the line, where it means the ones down it
2647
+ * (CalculateLayout.cpp's multi-line `Align::Stretch`, still so on Yoga's
2648
+ * main). A leaf laid out at a width and a height is that size, so the item
2649
+ * came out its side margins taller and its top and bottom ones shorter
2650
+ * than the height its line was shared out to, which its place and the
2651
+ * next item's had been worked out from. The pass lays an item out again
2652
+ * wherever its side margins are not nothing, or its line is another width
2653
+ * than the one it was measured at, so `margin: 0 5px` on a line of words
2654
+ * was 28 tall where Chrome's is 18, and `margin: 4px 0 2px` in a column
2655
+ * with two lines 6 short; where the two sums are the same, the pass gives
2656
+ * the height back unchanged.
2657
+ */
2658
+ function stretchedDown(root, items) {
2659
+ const lines = root.getAlignItems();
2660
+ return items.filter(({ box, node }) => {
2661
+ const own = node.getAlignSelf();
2662
+ if ((own === Y.ALIGN_AUTO ? lines : own) !== Y.ALIGN_STRETCH)
2663
+ return false;
2664
+ const width = node.getWidth().unit;
2665
+ if (width !== Y.UNIT_AUTO && width !== Y.UNIT_UNDEFINED)
2666
+ return false;
2667
+ // as Yoga adds them up, an `auto` margin as nothing
2668
+ const style = box.style;
2669
+ const of = (len, px) => (len === AUTO ? 0 : px);
2670
+ const across = of(style.marginLeft, box.marginLeft) +
2671
+ of(style.marginRight, box.marginRight);
2672
+ const down = of(style.marginTop, box.marginTop) +
2673
+ of(style.marginBottom, box.marginBottom);
2674
+ return Math.abs(across - down) >= 0.0001;
2675
+ });
2676
+ }
2677
+ /**
2678
+ * Lay a column that wraps out, keeping for each item Yoga stretches across
2679
+ * its line the height it sized it at (`stretchedDown`, `Laid.unstretched`).
2680
+ *
2681
+ * Yoga is asked again with each such item set at its line's start, which
2682
+ * its multi-line pass does not lay out again: each is as tall as its line
2683
+ * made it. Its flex base size has to come out the same for that, so its
2684
+ * lines break and share out the same — and where Yoga hands the column's
2685
+ * width over as a size, it measures an item it stretches at that width, and
2686
+ * one it does not at its content's, so there the item is handed the width
2687
+ * as its own too. Then Yoga is asked once more as it was, so the lines and
2688
+ * the widths are its own, and only the heights are taken from the second
2689
+ * layout. Where an item's place down its line moved between the two, a
2690
+ * base moved too, and Yoga's height is kept, as it was.
2691
+ */
2692
+ function unstretch(items, stretched,
2693
+ /** The column's content width, where Yoga is handed it as a size, and
2694
+ * else null. */
2695
+ width, layout) {
2696
+ layout();
2697
+ const tops = items.map(({ node }) => node.getComputedTop());
2698
+ const aligned = stretched.map(({ box, node }) => {
2699
+ const own = node.getAlignSelf();
2700
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
2701
+ if (width !== null)
2702
+ node.setWidth(measuredAcross(box, node, width));
2703
+ return own;
2704
+ });
2705
+ layout();
2706
+ const kept = items.every(({ node }, i) => Math.abs(node.getComputedTop() - tops[i]) < 0.01);
2707
+ const heights = stretched.map(({ node }) => node.getComputedHeight());
2708
+ stretched.forEach(({ node }, i) => {
2709
+ node.setAlignSelf(aligned[i]);
2710
+ if (width !== null)
2711
+ node.setWidthAuto();
2712
+ });
2713
+ layout();
2714
+ stretched.forEach(({ laid }, i) => {
2715
+ laid.unstretched = kept ? heights[i] : NaN;
2716
+ });
2717
+ }
2718
+ /** The border-box width Yoga measures a column's item at for its flex base
2719
+ * size where it stretches it across a column of a width: the column's,
2720
+ * less its margins across, within its maximum. */
2721
+ function measuredAcross(box, node, width) {
2722
+ const style = box.style;
2723
+ const of = (len, px) => (len === AUTO ? 0 : px);
2724
+ const across = of(style.marginLeft, box.marginLeft) +
2725
+ of(style.marginRight, box.marginRight);
2726
+ const most = node.getMaxWidth();
2727
+ const limit = most.unit === Y.UNIT_POINT
2728
+ ? most.value
2729
+ : most.unit === Y.UNIT_PERCENT
2730
+ ? (most.value / 100) * width
2731
+ : Infinity;
2732
+ return Math.min(width - across, limit);
2733
+ }
2734
+ /** An item's border-box height, as Yoga sized it: the one it had before its
2735
+ * line stretched it, where that laid it out at another (`unstretch`,
2736
+ * `unspace`). */
2737
+ function heightOf(node, laid) {
2738
+ return Number.isNaN(laid.unstretched)
2739
+ ? node.getComputedHeight()
2740
+ : laid.unstretched;
2741
+ }
2742
+ /** Whether `align-content` spaces a flex box's lines out, putting what
2743
+ * room it has past them between them and around them. */
2744
+ function spacesLines(style) {
2745
+ const lines = crossContent(style);
2746
+ return (lines === 'space-between' ||
2747
+ lines === 'space-around' ||
2748
+ lines === 'space-evenly');
2749
+ }
2750
+ /**
2751
+ * Lay a row that wraps out, its lines spaced out by `align-content`,
2752
+ * keeping each item Yoga stretches across its line as tall as the line
2753
+ * (`Laid.unstretched`; CSS Flexbox 9.4, step 11).
2754
+ *
2755
+ * Yoga (3.2.1) sets the lines of a box that wraps in a pass of its own,
2756
+ * after it has sized them, and lays each item it stretches out again there
2757
+ * as tall as its line and the room it puts after the line as well
2758
+ * (CalculateLayout.cpp's multi-line `Align::Stretch`, still so on Yoga's
2759
+ * main). Each line was where it belongs, and every item on it that is not
2760
+ * stretched, but a stretched item ran down to the next line: two lines of
2761
+ * 18px items in a row 100 tall came to 82 apiece with `space-between`. So
2762
+ * Yoga is asked with the lines set at the row's start, where it puts no
2763
+ * room between them and each item is as tall as its line. Where the lines
2764
+ * leave the row no room (`linesReach`), that is the layout the spacing
2765
+ * makes too, and is kept. Where they do, Yoga is asked again as the row
2766
+ * is, for where the lines and the items are, and the heights are kept from
2767
+ * the first layout.
2768
+ */
2769
+ function unspace(root, items,
2770
+ /** Whether the lines run bottom to top. */
2771
+ reverse, layout) {
2772
+ for (const { laid } of items)
2773
+ laid.unstretched = NaN;
2774
+ // each item Yoga stretches: `stretch`, with no height of its own
2775
+ const align = root.getAlignItems();
2776
+ const stretched = items.filter(({ node }) => {
2777
+ const own = node.getAlignSelf();
2778
+ if ((own === Y.ALIGN_AUTO ? align : own) !== Y.ALIGN_STRETCH)
2779
+ return false;
2780
+ const height = node.getHeight().unit;
2781
+ return height === Y.UNIT_AUTO || height === Y.UNIT_UNDEFINED;
2782
+ });
2783
+ if (stretched.length === 0) {
2784
+ layout();
2785
+ return;
2786
+ }
2787
+ const lines = root.getAlignContent();
2788
+ root.setAlignContent(Y.ALIGN_FLEX_START);
2789
+ layout();
2790
+ root.setAlignContent(lines);
2791
+ if (!(root.getComputedHeight() - linesReach(root, items, reverse) > NEAR)) {
2792
+ return;
2793
+ }
2794
+ const heights = stretched.map(({ node }) => node.getComputedHeight());
2795
+ layout();
2796
+ stretched.forEach(({ node, laid }, i) => {
2797
+ if (Math.abs(node.getComputedHeight() - heights[i]) >= NEAR) {
2798
+ laid.unstretched = heights[i];
2799
+ }
2800
+ });
2801
+ }
2802
+ /**
2803
+ * How far down a row that wraps its lines reach, at the least, in the
2804
+ * layout Yoga made of it with the lines at its start (`unspace`): the most,
2805
+ * over its items, of where the item's line ends at the least. An item is as
2806
+ * tall as its line with its margins where it is stretched across it, and
2807
+ * no taller where it is not, and Yoga ends it at its line's end, or with
2808
+ * its margins at the line's start, or centres it with neither
2809
+ * (`lineMarginFix`). Read with the lines turned back the right way up where
2810
+ * they run bottom to top.
2811
+ */
2812
+ function linesReach(root, items, reverse) {
2813
+ const height = root.getComputedHeight();
2814
+ const lines = root.getAlignItems();
2815
+ let reach = 0;
2816
+ for (const { box, node } of items) {
2817
+ const tall = node.getComputedHeight();
2818
+ const top = reverse
2819
+ ? height - node.getComputedTop() - tall
2820
+ : node.getComputedTop();
2821
+ const style = box.style;
2822
+ const above = style.marginTop === AUTO ? 0 : box.marginTop;
2823
+ const below = style.marginBottom === AUTO ? 0 : box.marginBottom;
2824
+ const own = node.getAlignSelf();
2825
+ const align = own === Y.ALIGN_AUTO ? lines : own;
2826
+ // where Yoga set it at its line's start with no margin above it, the
2827
+ // line runs on past it by both its margins at the least, and where it
2828
+ // centred it, by half of them
2829
+ const lead = align === Y.ALIGN_FLEX_START
2830
+ ? above
2831
+ : align === Y.ALIGN_CENTER
2832
+ ? (above - below) / 2
2833
+ : 0;
2834
+ reach = Math.max(reach, top + tall + below + lead);
2835
+ }
2836
+ return reach;
2837
+ }
1587
2838
  /** Whether an item is stretched across its line: `stretch`, its own or its
1588
2839
  * container's, with no height of its own and no `auto` margin across. */
1589
2840
  function stretches(box, container,
@@ -1602,6 +2853,150 @@ row = true) {
1602
2853
  style.marginLeft !== AUTO &&
1603
2854
  style.marginRight !== AUTO;
1604
2855
  }
2856
+ /** Whether a flex box laid out at a width is a column that wraps, whose
2857
+ * lines are as wide as their items and not as the box, and are placed
2858
+ * across it here (`crossLines`). */
2859
+ function wrapsColumn(style, width) {
2860
+ return (Number.isFinite(width) &&
2861
+ !style.flexDirection.startsWith('row') &&
2862
+ style.flexWrap !== 'nowrap');
2863
+ }
2864
+ /**
2865
+ * Place a column that wraps across: its lines across the box, and each
2866
+ * item across its line (CSS Flexbox 9.4, steps 8 to 11, and 9.6, steps 13
2867
+ * to 16). From the layout Yoga made of it with every item at its line's
2868
+ * start (`layoutFlex`), in which each item is as wide as it is before it is
2869
+ * stretched, `fit-content` or its own width within its limits, and each
2870
+ * line starts its gap and a pixel past the one before, which is how the
2871
+ * items of one line are told from the next one's.
2872
+ *
2873
+ * A line is as wide as its widest item, margins and all. `align-content`
2874
+ * (its `start` and `end` the box's own sides, `crossContent`) shares out
2875
+ * what the box has past its lines: `stretch` to each line alike,
2876
+ * where they are short of the box; `center` and `flex-end` whether they are
2877
+ * or not, so that lines wider than the box run past both its sides, or its
2878
+ * start; and the spacing values only where there is room, setting the lines
2879
+ * at the box's start where there is none (CSS Box Alignment 3, 4.3:
2880
+ * `space-between` falls back to `flex-start`, `space-around` and
2881
+ * `space-evenly` to `safe center`, the start where the lines overflow). An
2882
+ * item with an `auto` margin across its line takes the room the line has
2883
+ * past it on that side (8.1), and none where there is none; a stretched
2884
+ * item is as wide as its line less its margins, within its limits (9.4,
2885
+ * step 11), and as tall as the column made it; and the rest are where
2886
+ * `align-self` says, a baseline at the line's start, as a column has none
2887
+ * across it to align by.
2888
+ */
2889
+ function crossLines(style, items,
2890
+ /** The flex box's content width. */
2891
+ width,
2892
+ /** The gap between two lines. */
2893
+ gap) {
2894
+ // the lines start at the right where the box runs right to left, or
2895
+ // where they wrap in reverse, and at the left where both are so
2896
+ const fromRight = (style.direction === 'rtl') !== (style.flexWrap === 'wrap-reverse');
2897
+ const lines = [];
2898
+ const sizes = [];
2899
+ // each item's margins across, from its line's start, an `auto` one none
2900
+ const lead = [];
2901
+ const trail = [];
2902
+ let last = NaN;
2903
+ items.forEach(({ box, node, laid }, i) => {
2904
+ const yoga = node.getComputedWidth();
2905
+ const left = node.getComputedLeft();
2906
+ const start = fromRight ? width - left - yoga : left;
2907
+ if (!(Math.abs(start - last) < 0.5))
2908
+ lines.push([]);
2909
+ last = start;
2910
+ lines[lines.length - 1].push(i);
2911
+ sizes.push(meant(yoga, laid.set, laid.stretch, laid.width));
2912
+ const own = box.style;
2913
+ const l = own.marginLeft === AUTO ? 0 : box.marginLeft;
2914
+ const r = own.marginRight === AUTO ? 0 : box.marginRight;
2915
+ lead.push(fromRight ? r : l);
2916
+ trail.push(fromRight ? l : r);
2917
+ });
2918
+ const across = lines.map((line) => line.reduce((most, i) => Math.max(most, sizes[i] + lead[i] + trail[i]), 0));
2919
+ const count = lines.length;
2920
+ let room = width - gap * (count - 1);
2921
+ for (const size of across)
2922
+ room -= size;
2923
+ let at = 0;
2924
+ let between = gap;
2925
+ switch (crossContent(style)) {
2926
+ case 'flex-end':
2927
+ at = room;
2928
+ break;
2929
+ case 'center':
2930
+ at = room / 2;
2931
+ break;
2932
+ case 'stretch':
2933
+ if (room > 0)
2934
+ across.forEach((_, k) => (across[k] += room / count));
2935
+ break;
2936
+ case 'space-between':
2937
+ if (room > 0 && count > 1)
2938
+ between += room / (count - 1);
2939
+ break;
2940
+ case 'space-around':
2941
+ if (room > 0) {
2942
+ at = room / count / 2;
2943
+ between += room / count;
2944
+ }
2945
+ break;
2946
+ case 'space-evenly':
2947
+ if (room > 0) {
2948
+ at = room / (count + 1);
2949
+ between += room / (count + 1);
2950
+ }
2951
+ break;
2952
+ }
2953
+ const placed = new Map();
2954
+ lines.forEach((line, k) => {
2955
+ const size = across[k];
2956
+ for (const i of line) {
2957
+ const { box, node } = items[i];
2958
+ const own = box.style;
2959
+ let itemWidth = sizes[i];
2960
+ let offset = lead[i];
2961
+ const free = size - itemWidth - lead[i] - trail[i];
2962
+ const before = fromRight ? own.marginRight : own.marginLeft;
2963
+ const after = fromRight ? own.marginLeft : own.marginRight;
2964
+ if (before === AUTO || after === AUTO) {
2965
+ if (free > 0 && before === AUTO) {
2966
+ offset += after === AUTO ? free / 2 : free;
2967
+ }
2968
+ }
2969
+ else if (stretches(box, style, false) && !own.widthKeyword) {
2970
+ const most = pointsOf(node.getMaxWidth());
2971
+ const least = pointsOf(node.getMinWidth());
2972
+ itemWidth = size - lead[i] - trail[i];
2973
+ if (itemWidth > most)
2974
+ itemWidth = most;
2975
+ if (itemWidth < least)
2976
+ itemWidth = least;
2977
+ itemWidth = Math.max(itemWidth, box.horizontalExtra);
2978
+ }
2979
+ else {
2980
+ const align = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
2981
+ if (align === 'center')
2982
+ offset += free / 2;
2983
+ else if (align === 'flex-end')
2984
+ offset += free;
2985
+ }
2986
+ const start = at + offset;
2987
+ placed.set(box, {
2988
+ x: fromRight ? width - start - itemWidth : start,
2989
+ width: itemWidth,
2990
+ });
2991
+ }
2992
+ at += size + between;
2993
+ });
2994
+ return placed;
2995
+ }
2996
+ /** A length Yoga holds in points, or NaN where it holds none. */
2997
+ function pointsOf(value) {
2998
+ return value.unit === Y.UNIT_POINT ? value.value : NaN;
2999
+ }
1605
3000
  /** How deep flex boxes are laid out by Yoga, one inside another's measure
1606
3001
  * (`layoutFlex`). */
1607
3002
  const FLEX_DEPTH = 64;
@@ -1660,6 +3055,20 @@ function mainJustify(style) {
1660
3055
  start = (justify === 'left') === (style.direction !== 'rtl');
1661
3056
  return start !== reverse ? 'flex-start' : 'flex-end';
1662
3057
  }
3058
+ /**
3059
+ * An `align-content` as the cross axis takes it (CSS Box Alignment 3,
3060
+ * 5.1): `start` and `end` are the box's own edges, which are its lines'
3061
+ * cross ends turned round where the box wraps in reverse — `end` in a row
3062
+ * `wrap-reverse` is the bottom, where its lines start.
3063
+ */
3064
+ function crossContent(style) {
3065
+ const lines = style.alignContent;
3066
+ if (lines !== 'start' && lines !== 'end')
3067
+ return lines;
3068
+ return (lines === 'start') === (style.flexWrap !== 'wrap-reverse')
3069
+ ? 'flex-start'
3070
+ : 'flex-end';
3071
+ }
1663
3072
  const JUSTIFY = {
1664
3073
  'flex-start': Y?.JUSTIFY_FLEX_START,
1665
3074
  'flex-end': Y?.JUSTIFY_FLEX_END,
@@ -1677,5 +3086,6 @@ const ALIGN = {
1677
3086
  baseline: Y?.ALIGN_BASELINE,
1678
3087
  'space-between': Y?.ALIGN_SPACE_BETWEEN,
1679
3088
  'space-around': Y?.ALIGN_SPACE_AROUND,
3089
+ 'space-evenly': Y?.ALIGN_SPACE_EVENLY,
1680
3090
  };
1681
3091
  //# sourceMappingURL=flex.js.map