@react-x11/components 0.14.1 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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 +27 -8
  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 +29 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +172 -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 +1759 -238
  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 +64 -19
  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 +122 -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 +27 -8
  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 +211 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1975 -231
  90. package/src/html/layout/inline.ts +70 -16
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +155 -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,103 @@ 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
+ // Yoga aligns the lines of a box that wraps, or of a row that aligns by
214
+ // baselines, in one pass of its own, which drops a margin
215
+ // (`lineMarginFix`) — and not those of a column that does not wrap,
216
+ // whatever it aligns by, whose margins Yoga keeps
217
+ let baselines = row &&
218
+ (box.style.alignItems === 'baseline' ||
219
+ items.some(({ box: child }) => child.style.alignSelf === 'baseline'));
220
+ // An `auto` margin across a row that wraps takes the room its line has
221
+ // past the item (CSS Flexbox 8.1, 9.6), where Yoga's pass that sets the
222
+ // items across their lines reads an item's alignment and not its
223
+ // margins: it stretched one with `margin-top: auto` to its line, and set
224
+ // one with a height at its line's start. So Yoga is told the alignment
225
+ // the margins come to (`autoAcross`), which it then places it by.
226
+ if (row && box.style.flexWrap !== 'nowrap') {
227
+ const reverse = box.style.flexWrap === 'wrap-reverse';
228
+ for (const { box: child, node } of items) {
229
+ const aligned = autoAcross(child.style, reverse);
230
+ if (aligned)
231
+ node.setAlignSelf(ALIGN[aligned]);
232
+ }
233
+ }
234
+ else if (baselines) {
235
+ // Yoga takes a row that does not wrap through that pass as well, and
236
+ // sets its line there as tall as its items and not the row: an item
237
+ // centred or at the end of a row of a height was centred or ended in
238
+ // the items' height. It has sized that line already, as the most any
239
+ // item reaches down to its bottom edge — a baseline, to Yoga — and the
240
+ // largest bottom margin besides, whichever items those are: a row of
241
+ // no height of its own was as tall as both. And it sets an item with
242
+ // an `auto` margin by its baseline: at the top, where the margin puts
243
+ // it at the bottom, and every item beside it as far down as its height
244
+ // reached. That item takes no part in aligning the line by baselines
245
+ // (9.4 step 8), and Yoga is told the alignment its margins come to.
246
+ // With fewer than two items left that do take part, they come to their
247
+ // line's start (8.3), which Yoga is told as well: then nothing in the
248
+ // row is aligned by its baseline, Yoga takes no such pass, and the line
249
+ // is the row. Two or more are lined up by their baselines once those
250
+ // are known (`baselineLines`), and the row laid out again with none.
251
+ for (const { box: child, node } of items) {
252
+ const aligned = autoAcross(child.style, false);
253
+ if (aligned)
254
+ node.setAlignSelf(ALIGN[aligned]);
255
+ }
256
+ const aligned = items.filter(({ box: child }) => byBaseline(box.style, child.style));
257
+ if (aligned.length < 2) {
258
+ for (const { node } of aligned)
259
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
260
+ if (box.style.alignItems === 'baseline') {
261
+ root.setAlignItems(Y.ALIGN_FLEX_START);
262
+ }
263
+ baselines = false;
264
+ }
265
+ }
167
266
  // frozen at its least size on the one line of a box that does not
168
267
  // wrap, by a basis where Yoga takes one along the main axis
169
268
  // (`applyItem`'s `setBasis`)
@@ -171,27 +270,76 @@ export function layoutFlex(box, ctx, contentWidth) {
171
270
  frozen: box.style.flexWrap === 'nowrap',
172
271
  basis: row ? bounded : height !== null,
173
272
  };
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);
273
+ const layout = () => root.calculateLayout(bounded ? contentWidth : Number.NaN, height ?? Number.NaN, direction);
274
+ // Down a column that wraps, the items Yoga stretches across their lines
275
+ // come out of its layout at another height than it sized them at, where
276
+ // their side margins are not their top and bottom ones (`unstretch`):
277
+ // laid out so that each has the height it was sized at
278
+ const restretched = row || hold.frozen ? [] : stretchedDown(root, items);
279
+ const sized = bounded ? contentWidth : null;
280
+ // and across a row that wraps, the items Yoga stretches across lines
281
+ // `align-content` spaces out come out of its layout as tall as their
282
+ // line and the room after it as well (`unspace`): laid out so that each
283
+ // is as tall as its line, where the box has room to space them out in —
284
+ // a height, or a minimum one — and with the lines at its start where it
285
+ // may be too short for them, a height or a maximum one
286
+ const spaced = row &&
287
+ bounded &&
288
+ !hold.frozen &&
289
+ (height !== null || floor > 0 || ceiling < Infinity) &&
290
+ spacesLines(box.style);
291
+ const ask = restretched.length > 0
292
+ ? () => unstretch(items, restretched, sized, layout)
293
+ : spaced
294
+ ? () => unspace(root, items, box.style.flexWrap === 'wrap-reverse', layout)
295
+ : layout;
296
+ // The one line of a box that does not wrap is shared out by CSS
297
+ // Flexbox 9.7 where Yoga's answer for it may not be what that makes of
298
+ // it (`resolveLine`): along a row of a width, or down a column of a
299
+ // height or of limits on one, where a line may have room to share out
300
+ const line = hold.frozen &&
301
+ (row ? bounded : height !== null || floor > 0 || ceiling < Infinity)
302
+ ? {
303
+ resolved: false,
304
+ size: row ? contentWidth : (height ?? NaN),
305
+ floor,
306
+ ceiling,
307
+ between: betweenOf(items, row, row ? columnGap : rowGap),
308
+ probe: row && contentWidth === MIN_CONTENT_PROBE,
309
+ }
310
+ : null;
311
+ // and the lines of a box that wraps may have room left over, where a
312
+ // row has a width to have it in: Yoga's answer for one every item of
313
+ // which that may grow its maximum stops is put right (`cappedLines`) —
314
+ // broken into lines as Yoga breaks them, at the box's size along them
315
+ const roomy = !hold.frozen && (row ? bounded : true);
316
+ const breaks = row ? contentWidth : (height ?? Infinity);
180
317
  // each item's size along the main axis, read the once a layout (`Laid`)
181
318
  const read = () => {
182
319
  for (const { node, laid } of items) {
183
- laid.main = row ? node.getComputedWidth() : node.getComputedHeight();
320
+ laid.main = row ? node.getComputedWidth() : heightOf(node, laid);
184
321
  }
185
322
  };
186
323
  const calculate = () => {
324
+ // a line shared out here is again, before Yoga places it: an item
325
+ // since held at its content's size is frozen there (`autoMinimums`)
326
+ if (line?.resolved)
327
+ shareOut(items, row, line, hold);
187
328
  ask();
188
329
  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();
330
+ if (line && !line.resolved) {
331
+ const room = Number.isNaN(line.size)
332
+ ? root.getComputedHeight()
333
+ : line.size;
334
+ if (resolveLine(items, row, line, room, hold, ask))
335
+ read();
336
+ }
337
+ else if (roomy) {
338
+ const room = row ? contentWidth : (height ?? root.getComputedHeight());
339
+ const gap = row ? columnGap : rowGap;
340
+ if (cappedLines(items, row, room, breaks, gap, hold, ask))
341
+ read();
342
+ }
195
343
  };
196
344
  ctx.flexDepth = depth + 1;
197
345
  try {
@@ -206,21 +354,62 @@ export function layoutFlex(box, ctx, contentWidth) {
206
354
  finally {
207
355
  ctx.flexDepth = depth;
208
356
  }
209
- // Yoga aligns the lines of a box that wraps, or aligns by baselines, in
210
- // one pass of its own, which drops a margin (`lineMarginFix`)
211
- const baselines = box.style.alignItems === 'baseline' ||
212
- items.some(({ box: child }) => child.style.alignSelf === 'baseline');
213
- let bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap' || baselines, contentWidth);
357
+ // The lines of a row that wraps are as tall as their tallest items, each
358
+ // as tall as its content across (CSS Flexbox 9.4, steps 7 and 8), and
359
+ // `align-content` stretches lines short of the box to it, or centres
360
+ // them, or ends them, past its edges where they are taller (step 9).
361
+ // Handed the box's height as a size, Yoga measures a stretched item at it
362
+ // wherever the row has room along it for every item, and so its line is
363
+ // the box's height: an item taller than the box was squashed to it, and a
364
+ // line `align-content` centres stayed at the box's top. Where that is so
365
+ // (`heldLine`), the box is held to its height from both sides instead,
366
+ // and laid out again: Yoga measures each item as at most that tall, which
367
+ // its content passes where it is taller — the maximum a percentage, as a
368
+ // minimum and a maximum that are the same are a size to Yoga. Only there,
369
+ // which takes in the one line `space-around` and `space-evenly` centre,
370
+ // and not where the row is measured for its widest, which is laid out
371
+ // for its width alone.
372
+ if (row &&
373
+ bounded &&
374
+ height !== null &&
375
+ box.style.flexWrap !== 'nowrap' &&
376
+ heldLine(box, ctx, items, height, contentWidth, columnGap)) {
377
+ root.setHeightAuto();
378
+ root.setMinHeight(height);
379
+ root.setMaxHeightPercent(100);
380
+ // and a percentage height resolved: Yoga takes one inside a box it
381
+ // does not hold to a size for none, and measured `h-1/2` as tall as
382
+ // its content
383
+ if (own) {
384
+ for (const { box: child, node } of items) {
385
+ const set = child.style.height;
386
+ if (!isPct(set))
387
+ continue;
388
+ const down = child.style.boxSizing === 'border-box' ? 0 : child.verticalExtra;
389
+ node.setHeight(resolve(set, definite) + down);
390
+ }
391
+ }
392
+ ctx.flexDepth = depth + 1;
393
+ try {
394
+ calculate();
395
+ }
396
+ finally {
397
+ ctx.flexDepth = depth;
398
+ }
399
+ }
400
+ // whether Yoga's last layout took its pass for lines (`placeItems`)
401
+ let lined = box.style.flexWrap !== 'nowrap' || baselines;
402
+ let bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, undefined, crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined);
214
403
  // Items aligned by their baselines, which Yoga cannot see, are aligned
215
404
  // with their baselines known (`baselineLines`)
216
405
  // by the flex layout again, set at their lines' starts with the margins
217
406
  // that put their baselines together, which makes each line as tall as
218
407
  // that does
219
- const lines = row && baselines ? baselineLines(box.style, items) : null;
408
+ const lines = baselines ? baselineLines(box.style, items) : null;
409
+ const started = new Set();
220
410
  if (lines) {
221
411
  // every one of them, and the box's own `baseline` too, which is what
222
412
  // takes Yoga down that pass
223
- const started = new Set();
224
413
  for (const line of lines) {
225
414
  line.forEach(({ box: child, node }, i) => {
226
415
  node.setAlignSelf(Y.ALIGN_FLEX_START);
@@ -231,6 +420,8 @@ export function layoutFlex(box, ctx, contentWidth) {
231
420
  if (box.style.alignItems === 'baseline') {
232
421
  root.setAlignItems(Y.ALIGN_FLEX_START);
233
422
  }
423
+ // so it takes it only where the row wraps
424
+ lined = box.style.flexWrap !== 'nowrap';
234
425
  ctx.flexDepth = depth + 1;
235
426
  try {
236
427
  calculate();
@@ -238,7 +429,7 @@ export function layoutFlex(box, ctx, contentWidth) {
238
429
  finally {
239
430
  ctx.flexDepth = depth;
240
431
  }
241
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap', contentWidth, started);
432
+ bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started);
242
433
  }
243
434
  // A replaced item with a ratio that is stretched across a line of a
244
435
  // definite size takes its flex base size from the stretched one through
@@ -256,7 +447,7 @@ export function layoutFlex(box, ctx, contentWidth) {
256
447
  finally {
257
448
  ctx.flexDepth = depth;
258
449
  }
259
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap === 'wrap' || baselines, contentWidth);
450
+ bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started);
260
451
  }
261
452
  const contentHeight = root.getComputedHeight();
262
453
  root.freeRecursive();
@@ -284,6 +475,10 @@ function keepRatios(container, items, row) {
284
475
  if (Math.abs(basis - laid.main) < 0.5)
285
476
  continue;
286
477
  node.setFlexBasis(basis);
478
+ // and is shared out from it where its line is here (`shareOut`)
479
+ laid.basis = basis;
480
+ laid.sized = false;
481
+ laid.base = basis;
287
482
  // one held at its least size (`holdAt`) flexes from the new basis, no
288
483
  // further down than that
289
484
  if (!Number.isNaN(laid.held)) {
@@ -417,16 +612,29 @@ own,
417
612
  /** Whether Yoga aligned the lines in its pass that drops a margin. */
418
613
  lined,
419
614
  /** The flex box's content width: its items' containing block's. */
420
- contentWidth, started) {
615
+ contentWidth, started,
616
+ /** Where each item is across a column that wraps (`crossLines`). */
617
+ crossed) {
421
618
  let bottom = 0;
619
+ // the lines of a row that wraps in reverse are turned round to run up it
620
+ const reverse = row && box.style.flexWrap === 'wrap-reverse';
422
621
  for (const { box: child, node, laid } of items) {
423
- const across = lined
424
- ? lineMarginFix(box.style, child, node, row, started?.has(child))
622
+ const placed = crossed?.get(child);
623
+ const across = lined && !placed
624
+ ? lineMarginFix(box.style, child, node, row, own, started?.has(child))
425
625
  : 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;
626
+ const itemHeight = row ? heightOf(node, laid) : laid.main;
627
+ const left = box.contentX +
628
+ (placed ? placed.x : node.getComputedLeft() + (row ? 0 : across));
629
+ // which Yoga turns an item round in by the height it laid it out at, and
630
+ // not the one it is (`unspace`)
631
+ const top = box.contentY +
632
+ node.getComputedTop() +
633
+ (row ? across : 0) +
634
+ (reverse ? node.getComputedHeight() - itemHeight : 0);
635
+ const width = placed
636
+ ? placed.width
637
+ : meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
430
638
  // The item is laid out again at the width the flex pass settled on: the
431
639
  // measure function answered a question, and the answer is not a layout —
432
640
  // its line breaks were computed against a width that may have changed
@@ -488,7 +696,8 @@ column = false) {
488
696
  // one: the tallest card of a row is laid out the once. A table shares
489
697
  // what it is given past its rows among them (`layoutTable`), and a
490
698
  // height short of them is none it can have: it is laid out again where
491
- // it was given more.
699
+ // it was given more — or less than a height of its own, which it takes
700
+ // in place of that one, down to its rows.
492
701
  const given = definite ?? (column ? height : null);
493
702
  let shares = false;
494
703
  if (given !== null && (box.kind === 'flex' || box.kind === 'table')) {
@@ -496,7 +705,8 @@ column = false) {
496
705
  shares =
497
706
  box.kind === 'flex'
498
707
  ? !(Math.abs(natural - given) <= 0.01)
499
- : !(natural >= given - 0.01);
708
+ : !(natural >= given - 0.01) ||
709
+ (box.style.height !== AUTO && natural > given + 0.01);
500
710
  }
501
711
  if (given !== null) {
502
712
  const inner = Math.max(0, given - box.verticalExtra);
@@ -522,7 +732,7 @@ column = false) {
522
732
  const heights = definite !== null ? FLEXED_HEIGHT : USED_HEIGHT;
523
733
  heights.set(box, tall);
524
734
  try {
525
- ctx.layoutSubtree(box, width, containing);
735
+ layoutAt(box, ctx, width, containing, laid);
526
736
  }
527
737
  finally {
528
738
  heights.delete(box);
@@ -565,17 +775,22 @@ function applyContainer(node, style) {
565
775
  const items = ALIGN[style.alignItems] ?? Y.ALIGN_STRETCH;
566
776
  if (items !== Y.ALIGN_STRETCH)
567
777
  node.setAlignItems(items);
568
- const lines = ALIGN[style.alignContent] ?? Y.ALIGN_STRETCH;
778
+ const lines = ALIGN[crossContent(style)] ?? Y.ALIGN_STRETCH;
569
779
  if (lines !== Y.ALIGN_FLEX_START)
570
780
  node.setAlignContent(lines);
571
781
  }
572
782
  function applyItem(node, box, ctx, containingWidth, laid,
573
783
  /** Whether the flex box has a height its items are laid out in. */
574
784
  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) {
785
+ /** That height where it is definite, and a percentage may be of it, and
786
+ * else NaN: one a column flexed the box to is not (`USED_HEIGHT`). */
787
+ definite,
788
+ /** Whether its line weighs it against another as it shrinks them, by
789
+ * their flex base sizes (`layoutFlex`). */
790
+ weighed) {
578
791
  const style = box.style;
792
+ const own = Number.isFinite(definite);
793
+ const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
579
794
  laid.stretch = containingWidth - box.marginLeft - box.marginRight;
580
795
  // an `auto` margin takes the free space on its side, which is how
581
796
  // `margin-left: auto` puts an item at the end of its row (CSS Flexbox
@@ -596,6 +811,14 @@ own) {
596
811
  node.setFlexShrink(style.flexShrink);
597
812
  if (style.alignSelf !== AUTO)
598
813
  node.setAlignSelf(ALIGN[style.alignSelf] ?? Y.ALIGN_AUTO);
814
+ // `stretch` sets an item whose height is a percentage of none at its
815
+ // line's start, as tall as its content (`heightOfNone`), where Yoga,
816
+ // which is handed no height for it, stretched it across the line
817
+ if (row && box.parent && heightOfNone(style, own)) {
818
+ const align = style.alignSelf === AUTO ? box.parent.style.alignItems : style.alignSelf;
819
+ if (align === 'stretch')
820
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
821
+ }
599
822
  // Yoga's sizes are border boxes, as `box-sizing: border-box` has them;
600
823
  // a `content-box` length is the content's, and the item's padding and
601
824
  // border go on top of it
@@ -604,15 +827,31 @@ own) {
604
827
  if (style.width !== AUTO) {
605
828
  laid.set = setLength(node, true, style.width, containingWidth, across);
606
829
  }
830
+ // Down a column, a basis of the content's — `content`, or a percentage
831
+ // of a height that is not definite (CSS Flexbox 7.2.3) — is its content's
832
+ // height, whatever height it has of its own (9.2.3, E), and Yoga takes
833
+ // the height of its own for a basis where it has one. So that is kept
834
+ // from Yoga, and the measure function answers with the content's.
835
+ const contentColumn = !row && (style.flexBasis === 'content' || (isPct(style.flexBasis) && !own));
607
836
  // 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);
837
+ // and is not handed to Yoga, which makes one of the height of a flex box
838
+ // with none of its own but limits on one — and the height set on its
839
+ // node, where that is a length, and whether one is
840
+ let height = NaN;
841
+ let heightSet = false;
842
+ if (style.height !== AUTO &&
843
+ (own || !isPct(style.height)) &&
844
+ !contentColumn) {
845
+ height = setLength(node, false, style.height, definite, down);
846
+ heightSet = true;
611
847
  }
612
848
  // the least it may be along each axis, its border box's, as Yoga is
613
849
  // told it
614
850
  let leastWidth = NaN;
615
851
  let leastHeight = NaN;
852
+ // and the most, as Yoga is told it
853
+ let mostWidth = NaN;
854
+ let mostHeight = NaN;
616
855
  const minWidth = resolveOrNull(style.minWidth, containingWidth);
617
856
  if (minWidth !== null) {
618
857
  leastWidth = minWidth + across;
@@ -620,8 +859,10 @@ own) {
620
859
  }
621
860
  if (style.maxWidth !== 'none') {
622
861
  const maxWidth = resolveOrNull(style.maxWidth, containingWidth);
623
- if (maxWidth !== null)
624
- node.setMaxWidth(maxWidth + across);
862
+ if (maxWidth !== null) {
863
+ mostWidth = maxWidth + across;
864
+ node.setMaxWidth(mostWidth);
865
+ }
625
866
  }
626
867
  if (style.widthKeyword || style.minWidthKeyword || style.maxWidthKeyword) {
627
868
  // an intrinsic size is the item's content's, measured here and handed
@@ -637,32 +878,87 @@ own) {
637
878
  leastWidth = size(style.minWidthKeyword);
638
879
  node.setMinWidth(leastWidth);
639
880
  }
640
- if (style.maxWidthKeyword)
641
- node.setMaxWidth(size(style.maxWidthKeyword));
881
+ if (style.maxWidthKeyword) {
882
+ mostWidth = size(style.maxWidthKeyword);
883
+ node.setMaxWidth(mostWidth);
884
+ }
642
885
  }
643
- const minHeight = resolveOrNull(style.minHeight, NaN);
886
+ // a percentage of a definite height, which a limit on a flex item's
887
+ // height is where its flex box has one
888
+ const minHeight = resolveOrNull(style.minHeight, definite);
644
889
  if (minHeight !== null) {
645
890
  leastHeight = minHeight + down;
646
891
  node.setMinHeight(leastHeight);
647
892
  }
648
893
  if (style.maxHeight !== 'none') {
649
- const maxHeight = resolveOrNull(style.maxHeight, NaN);
650
- if (maxHeight !== null)
651
- node.setMaxHeight(maxHeight + down);
894
+ const maxHeight = resolveOrNull(style.maxHeight, definite);
895
+ if (maxHeight !== null) {
896
+ mostHeight = maxHeight + down;
897
+ node.setMaxHeight(mostHeight);
898
+ }
899
+ }
900
+ // a `border-box` maximum under the item's padding and borders is them,
901
+ // its content box none wide (CSS Sizing 3, 3.3), where Yoga lays the
902
+ // item out as wide as them and shares out its line as though it were
903
+ // as wide as the maximum: the `auto` margins after `max-width:
904
+ // 20.668px` with 23px of padding and border took the 2.33px between
905
+ if (mostWidth < box.horizontalExtra) {
906
+ mostWidth = box.horizontalExtra;
907
+ node.setMaxWidth(mostWidth);
908
+ }
909
+ if (mostHeight < box.verticalExtra) {
910
+ mostHeight = box.verticalExtra;
911
+ node.setMaxHeight(mostHeight);
912
+ }
913
+ // a maximum under the minimum is the minimum (CSS 2.1 10.4, 10.7), where
914
+ // Yoga holds an item that comes to more than both at the maximum
915
+ if (mostWidth < leastWidth) {
916
+ mostWidth = leastWidth;
917
+ node.setMaxWidth(mostWidth);
918
+ }
919
+ if (mostHeight < leastHeight) {
920
+ mostHeight = leastHeight;
921
+ node.setMaxHeight(mostHeight);
652
922
  }
653
- const row = box.parent?.style.flexDirection.startsWith('row') ?? true;
654
923
  laid.least = row ? leastWidth : leastHeight;
924
+ laid.most = row ? mostWidth : mostHeight;
925
+ laid.narrowest = leastWidth;
926
+ // A table is as wide as the flex layout makes it, as any item is: the
927
+ // size its line flexed it to along a row (9.7), and across a column's
928
+ // line the line's, stretched (9.4, step 11) — where in a block's flow it
929
+ // is as wide as its columns. Laid out at that width and then shrunk to
930
+ // them, it was a table its columns wide at the start of a column, and one
931
+ // `flex: 1` along a row stopped at them where Chrome's fills its share
932
+ laid.across =
933
+ box.kind === 'table' &&
934
+ (row ||
935
+ (box.parent !== null &&
936
+ !style.widthKeyword &&
937
+ stretches(box, box.parent.style, false)));
655
938
  // Yoga takes a length for a basis only where the flex box's main size is
656
939
  // definite, and else the item's own size along it: down a column of no
657
940
  // height of its own it read `flex: 0 0 3rem` as the item's height, or
658
941
  // its content's. There the basis is handed over as that height, which is
659
942
  // what the item's own is to a basis anyway — no more than its start
660
943
  const setBasis = (px) => {
944
+ laid.basis = px;
661
945
  if (row || tall)
662
946
  node.setFlexBasis(px);
663
947
  else
664
948
  node.setHeight(px);
665
949
  };
950
+ // Where Yoga takes the item's own size along the main axis for its
951
+ // basis, that size as a length, or its content's (`Laid.sized`) — and
952
+ // its content's, whatever size it has, for a basis that says so, which
953
+ // Yoga reads as its size down a column
954
+ const ownSize = (content = false) => {
955
+ if (content ||
956
+ (row ? style.width === AUTO && !style.widthKeyword : !heightSet)) {
957
+ laid.sized = true;
958
+ }
959
+ else
960
+ laid.basis = row ? laid.set : height;
961
+ };
666
962
  // down a column, an item with a ratio and a width is as tall as that
667
963
  // makes it, for a basis of its content or of its own `auto` height (CSS
668
964
  // Flexbox 9.2.3, B) — where Yoga measured it, and its block layout held
@@ -677,7 +973,8 @@ own) {
677
973
  // the content's size along a row, whatever the item's own width says
678
974
  // (CSS Flexbox 7.2.3), which Yoga reads as `auto` and takes the width
679
975
  // for; along a column, Yoga's measure is the content's
680
- node.setFlexBasis(contentBasis(box, ctx, containingWidth));
976
+ laid.basis = contentBasis(box, ctx, containingWidth);
977
+ node.setFlexBasis(laid.basis);
681
978
  }
682
979
  else if (columnBasis !== null) {
683
980
  // but a height of its own hides it from Yoga, and an item with a ratio
@@ -686,21 +983,32 @@ own) {
686
983
  }
687
984
  else if (style.flexBasis === 'content' || style.flexBasis === AUTO) {
688
985
  // Yoga's own
986
+ ownSize(style.flexBasis === 'content');
689
987
  }
690
- else if (isPct(style.flexBasis) && !row && tall && !own) {
988
+ else if (isPct(style.flexBasis) && !row && !own) {
691
989
  // a percentage of a main size that is not definite is `content` (CSS
692
990
  // Flexbox 7.2.3): Yoga's own, down a column
991
+ ownSize(true);
693
992
  }
694
993
  else if (isPct(style.flexBasis)) {
695
- // a percentage of the main size, which is known across a row
994
+ // a percentage of the main size, which is known across a row and down
995
+ // a column of a definite height: of a content box's size, as a width
996
+ // is, where the item's padding and border go on top of it
696
997
  const basis = style.flexBasis;
697
- if (!basis.px && !basis.of)
998
+ const extra = row ? across : down;
999
+ const known = row ? Number.isFinite(containingWidth) : own;
1000
+ if (known) {
1001
+ laid.basis = resolve(basis, row ? containingWidth : definite) + extra;
1002
+ }
1003
+ if (!basis.px && !basis.of && !(known && extra)) {
698
1004
  node.setFlexBasisPercent(basis.pct);
699
- else if (row && Number.isFinite(containingWidth)) {
700
- node.setFlexBasis(resolve(basis, containingWidth));
701
1005
  }
702
- else if (basis.of)
1006
+ else if (known)
1007
+ node.setFlexBasis(laid.basis);
1008
+ else if (basis.of) {
703
1009
  node.setFlexBasisAuto();
1010
+ ownSize();
1011
+ }
704
1012
  else
705
1013
  node.setFlexBasisPercent(basis.pct);
706
1014
  }
@@ -732,8 +1040,15 @@ own) {
732
1040
  // table cell's is: taken per layout of the container, a flex box in a
733
1041
  // flex box in a flex box laid its innermost out three times a level,
734
1042
  // and twelve levels took two seconds.
735
- const alongRow = box.parent?.style.flexDirection.startsWith('row') ?? true;
1043
+ const alongRow = row;
736
1044
  const content = () => {
1045
+ const width = maxContent();
1046
+ // its flex base size, where that is its content's (`Laid.sized`)
1047
+ if (alongRow)
1048
+ laid.content = width + box.horizontalExtra;
1049
+ return width;
1050
+ };
1051
+ function maxContent() {
737
1052
  // a replaced element's is the width it has with no limit — its
738
1053
  // natural one, or what its ratio makes of a height of its own — which
739
1054
  // its image, loading, may change, so it is not kept; taken as its
@@ -753,12 +1068,17 @@ own) {
753
1068
  if (width === undefined) {
754
1069
  width = measureIntrinsicWidth(box, ctx, Infinity) - box.horizontalExtra;
755
1070
  MAX_CONTENT.set(box, width);
1071
+ // measured, it is laid out with no limit on its width, and not at
1072
+ // the one it was last laid out at, which Yoga may give it again: an
1073
+ // item `min-width: min-content` that Yoga asked at its minimum and
1074
+ // then with no limit was left laid out infinitely wide
1075
+ laid.width = NaN;
756
1076
  }
757
1077
  // within what its least and greatest heights make of widths through
758
1078
  // its ratio: its size before it is flexed, and not after
759
1079
  const extra = box.horizontalExtra;
760
1080
  return Math.max(0, transferredWidth(box, width + extra) - extra);
761
- };
1081
+ }
762
1082
  // An item with a ratio is as wide, across a column, as the height it
763
1083
  // was given makes it through the ratio, where its width is its own to
764
1084
  // find (CSS Flexbox 9.4, its hypothetical cross size from its used main
@@ -792,24 +1112,109 @@ own) {
792
1112
  // 5.1)
793
1113
  const flexed = box.kind === 'replaced' || !laid.auto;
794
1114
  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
1115
+ // Along a row, an item's flex base size is its content at its widest,
1116
+ // whatever the room (CSS Flexbox 9.2.3 E, `content` as `max-content`),
1117
+ // which Yoga asks for as at most the row's width. One that may not
1118
+ // shrink is as wide as that (9.7): fitted to the room, a `shrink-0`
1119
+ // item of words was held to its content at its narrowest, and wrapped
1120
+ // where it overflows the row. And one that may shrink gives up room in
1121
+ // proportion to it, where its line weighs it against another (9.7,
1122
+ // step 4c, its flex shrink factor times its base): fitted, an item of
1123
+ // words wider than the row was weighed as no wider than the row, and
1124
+ // gave up too little of it beside a narrower one — 230 and 70 of a row
1125
+ // of 300, where Chrome has 245 and 55. One that shrinks alone is
1126
+ // fitted, which comes to the same and keeps the layout it is measured
1127
+ // at for the one it is given: at its widest, a paragraph in a row laid
1128
+ // itself out twice.
1129
+ // And every item of a row laid out in no room is fitted, which is the
1130
+ // flex box measured for its content at its narrowest
1131
+ // (`MIN_CONTENT_PROBE`): that is the sum of its items' content at their
1132
+ // narrowest, whatever they may shrink by, as Chrome and the css-flexbox
1133
+ // suite have it (`intrinsic-size/row-001`, `gap-015`), and not their
1134
+ // bases.
1135
+ // Across a column, the item is `fit-content` wide where it is not
1136
+ // stretched (9.4, step 7): fitted to the column, but no narrower than
1137
+ // its content at its narrowest, past the column where that is wider,
1138
+ // as a centred one is on both sides. Its `min-width` has no say in
1139
+ // that: an automatic minimum is the main axis's (4.5), so `auto` is 0
1140
+ // across a column, the same as the `min-width: 0` that lets an item
1141
+ // shrink along a row. Fitted to the column, a word wider than it ran
1142
+ // out of the item, and a centred item started at the column's start.
1143
+ // A replaced element's narrowest is its natural width, or one through
1144
+ // its ratio, and none where it has neither: an SVG with only a
1145
+ // `viewBox` is as wide as the column. And an item with something in
1146
+ // it that takes a percentage of a height is fitted to the column still:
1147
+ // the flex layout makes its height definite only after this, and
1148
+ // measured without it, an image `height: 100%` is its natural width.
1149
+ const widest = alongRow &&
1150
+ !exact &&
1151
+ (box.kind === 'replaced' ||
1152
+ (containingWidth !== MIN_CONTENT_PROBE &&
1153
+ (style.flexShrink === 0 || weighed)));
1154
+ const narrowest = alongRow || percentHeightsIn(box)
1155
+ ? undefined
1156
+ : box.kind === 'replaced'
1157
+ ? () => (replacedMinimum(box, containingWidth) ?? extra) - extra
1158
+ : () => minContentOf(box, ctx, laid) - extra;
1159
+ const fitted = through !== null
799
1160
  ? through
800
- : box.kind === 'replaced' && alongRow && !exact
1161
+ : widest
801
1162
  ? content()
802
- : innerWidth(width, wm, content);
1163
+ : innerWidth(width, wm, content, narrowest);
1164
+ // Across a column, no wider than its maximum: Yoga hands the measure
1165
+ // a room within it, but holds the answer's width to it only after,
1166
+ // and keeps the height measured at the wider — an image `max-width:
1167
+ // 100%` its natural width at its narrowest was as tall as that made it
1168
+ const capped = !alongRow && fitted > mostWidth - extra ? mostWidth - extra : fitted;
1169
+ // And no narrower than its minimum: Yoga holds the item to that only
1170
+ // after it has the answer, which it keeps for the width that makes of
1171
+ // it, so an item measured at the room and held to a wider minimum was
1172
+ // as tall as its content wrapped at the room — `min-w-max` in a narrow
1173
+ // row, or down a column narrower than it, one line of words two lines
1174
+ // tall, and an item held to its longest word in a row that wraps
1175
+ // (`holdAt`) a line taller than that makes it. Stretched across a
1176
+ // column, it is asked at the column's width.
1177
+ const inner = Number.isNaN(laid.narrowest)
1178
+ ? capped
1179
+ : Math.max(capped, laid.narrowest - extra);
1180
+ let answer;
803
1181
  if (box.kind === 'replaced' &&
804
1182
  exact &&
805
1183
  ratio > 0 &&
806
1184
  style.height === AUTO) {
807
- return { width: inner, height: inner / ratio };
1185
+ answer = { width: inner, height: inner / ratio };
1186
+ }
1187
+ else {
1188
+ answer = answers.get(inner);
1189
+ if (answer === undefined) {
1190
+ answer = measureBox(box, ctx, inner, containingWidth, laid);
1191
+ answers.set(inner, answer);
1192
+ }
1193
+ }
1194
+ // along a row, the width it answered where it might choose one
1195
+ if (alongRow && wm !== Y.MEASURE_MODE_EXACTLY) {
1196
+ laid.fitted = answer.width + box.horizontalExtra;
808
1197
  }
809
- let answer = answers.get(inner);
810
- if (answer === undefined) {
811
- answer = measureBox(box, ctx, inner, containingWidth, laid);
812
- answers.set(inner, answer);
1198
+ // down a column, its content's height where nothing held it to one:
1199
+ // its flex base size, where that is its content's (`Laid.sized`) — for
1200
+ // a basis that says so, what a height of its own has no say in
1201
+ if (!alongRow && !given) {
1202
+ if (contentColumn && style.height !== AUTO && box.kind !== 'replaced') {
1203
+ const width = inner + box.horizontalExtra;
1204
+ const kept = keptNatural(box, ctx, width, containingWidth);
1205
+ let content = kept?.content ?? NaN;
1206
+ if (Number.isNaN(content)) {
1207
+ if (kept === null || kept.serial !== box.layoutSerial) {
1208
+ layoutNaturally(box, ctx, width, containingWidth, laid);
1209
+ }
1210
+ content = columnContent(box, ctx, laid, width, containingWidth);
1211
+ }
1212
+ answer = {
1213
+ width: answer.width,
1214
+ height: Math.max(0, content - box.verticalExtra),
1215
+ };
1216
+ }
1217
+ laid.content = answer.height + box.verticalExtra;
813
1218
  }
814
1219
  return answer;
815
1220
  });
@@ -938,7 +1343,7 @@ containing, laid) {
938
1343
  }
939
1344
  else {
940
1345
  const at = ctx.positioned.length;
941
- ctx.layoutSubtree(box, width, containing);
1346
+ layoutAt(box, ctx, width, containing, laid);
942
1347
  // a replaced box is sized, not laid out, and has no layout to count
943
1348
  if (box.kind !== 'replaced') {
944
1349
  NATURAL.set(box, {
@@ -959,6 +1364,27 @@ containing, laid) {
959
1364
  laid.height = box.height;
960
1365
  laid.tall = NaN;
961
1366
  }
1367
+ /**
1368
+ * Lay an item out at a border-box width: a table the flex layout gives its
1369
+ * width at that width as its own (`Laid.across`), for this layout alone. A
1370
+ * width Yoga asks its content at is one the table comes to by its columns
1371
+ * all the same — its widest, or the room where that is less — so that is
1372
+ * no matter; an unbounded one, a probe of a box around the flex box, is
1373
+ * the table's to shrink in.
1374
+ */
1375
+ function layoutAt(box, ctx, width, containing, laid) {
1376
+ if (!laid.across || !Number.isFinite(width)) {
1377
+ ctx.layoutSubtree(box, width, containing);
1378
+ return;
1379
+ }
1380
+ STRETCHED_ACROSS.add(box);
1381
+ try {
1382
+ ctx.layoutSubtree(box, width, containing);
1383
+ }
1384
+ finally {
1385
+ STRETCHED_ACROSS.delete(box);
1386
+ }
1387
+ }
962
1388
  /**
963
1389
  * The layout this pass made of an item at its content's own height
964
1390
  * (`layoutNaturally`), where one at `width` now would be the same: the same
@@ -1055,8 +1481,9 @@ function readsPercentBase(box) {
1055
1481
  }
1056
1482
  /** The width an item's content is measured at: the width Yoga gives it
1057
1483
  * 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) {
1484
+ * (CSS Flexbox 9.2) — and, given its min-content width, no narrower than
1485
+ * that either. */
1486
+ function innerWidth(width, mode, maxContent, minContent) {
1060
1487
  if (mode === Y.MEASURE_MODE_EXACTLY && Number.isFinite(width)) {
1061
1488
  return Math.max(0, width);
1062
1489
  }
@@ -1065,9 +1492,10 @@ function innerWidth(width, mode, maxContent) {
1065
1492
  return content;
1066
1493
  }
1067
1494
  // 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);
1495
+ if (content <= width || nearly(content, width))
1496
+ return content;
1497
+ const room = Math.max(0, width);
1498
+ return minContent ? Math.min(content, Math.max(room, minContent())) : room;
1071
1499
  }
1072
1500
  /**
1073
1501
  * An item's automatic minimum (CSS Flexbox 4.5): with `min-width: auto` in
@@ -1089,13 +1517,39 @@ function autoMinimums(items, row, ctx, containingWidth, hold) {
1089
1517
  if (box.kind === 'text' || box.kind === 'break')
1090
1518
  continue;
1091
1519
  const style = box.style;
1520
+ const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
1521
+ // A table is no smaller than it can be along either axis, whatever its
1522
+ // own least says, `min-width: 0` and `min-height: 0` among it: its
1523
+ // columns at their narrowest along a row, and down a column its rows
1524
+ // and captions at the width it has, however short a height of its own
1525
+ // is (CSS 2.1 17.5.2 and 17.5.3; Blink, `length_utils.cc`: "Tables
1526
+ // can't shrink below their min-intrinsic size"). The line is laid out
1527
+ // with it so, and the items after it start where it ends: Yoga had it
1528
+ // as small as its line, and the next item over the rest of it — read
1529
+ // as a block's content, two pixels into it, the spacing under its rows
1530
+ if (box.kind === 'table') {
1531
+ // as wide as its columns at their widest, where Yoga measured that,
1532
+ // it has room for them at their narrowest, which is a layout less
1533
+ const widest = row ? MAX_CONTENT.get(box) : undefined;
1534
+ if (widest !== undefined &&
1535
+ laid.main >= widest + box.horizontalExtra - 0.01) {
1536
+ continue;
1537
+ }
1538
+ const least = row
1539
+ ? minContentOf(box, ctx, laid)
1540
+ : tableContent(box, ctx, width, containingWidth, laid);
1541
+ if (laid.main >= least - 0.01)
1542
+ continue;
1543
+ holdAt(node, laid, row, least, hold);
1544
+ changed = true;
1545
+ continue;
1546
+ }
1092
1547
  // a box that scrolls or clips has none, but for one that asks for its
1093
1548
  // content's height
1094
1549
  const asked = !row && style.minHeightKeyword !== null;
1095
1550
  if (!asked && scrolls(style)) {
1096
1551
  continue;
1097
1552
  }
1098
- const width = meant(row ? laid.main : node.getComputedWidth(), laid.set, laid.stretch, laid.width, row ? laid.held : NaN);
1099
1553
  // a replaced item's content along a row is its natural width
1100
1554
  if (row && box.kind === 'replaced') {
1101
1555
  if (style.minWidth !== AUTO || style.minWidthKeyword)
@@ -1220,30 +1674,9 @@ function autoMinimums(items, row, ctx, containingWidth, hold) {
1220
1674
  else {
1221
1675
  // its height, or where it has one of its own and its content comes to
1222
1676
  // 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
- }
1677
+ let content = Number.isNaN(read)
1678
+ ? columnContent(box, ctx, laid, width, containingWidth)
1679
+ : read;
1247
1680
  // within what the ratio makes of its least and greatest widths
1248
1681
  if (box.kind !== 'replaced') {
1249
1682
  content = transferredHeight(box, content, containingWidth);
@@ -1263,6 +1696,47 @@ function autoMinimums(items, row, ctx, containingWidth, hold) {
1263
1696
  }
1264
1697
  return changed;
1265
1698
  }
1699
+ /**
1700
+ * How far down a column item's content comes at a border-box width, its
1701
+ * border box's, from the layout its content's own height makes of it
1702
+ * there (`layoutNaturally`), which it has: what a height of its own has no
1703
+ * say in, which its automatic minimum reads (CSS Flexbox 4.5), and a flex
1704
+ * basis of `content` (9.2.3, E).
1705
+ */
1706
+ function columnContent(box, ctx, laid, width, containingWidth) {
1707
+ if (box.kind === 'replaced')
1708
+ return laid.height;
1709
+ if (box.style.height !== AUTO && percentHeightsIn(box)) {
1710
+ // what its content comes to where it has no height to take
1711
+ // percentages of, as an intrinsic size is measured: laid out so
1712
+ // apart, and the final pass lays it out again
1713
+ FLEXED_HEIGHT.set(box, NaN);
1714
+ try {
1715
+ ctx.layoutSubtree(box, width, containingWidth);
1716
+ }
1717
+ finally {
1718
+ FLEXED_HEIGHT.delete(box);
1719
+ }
1720
+ laid.width = NaN;
1721
+ return ratioContent(box, contentBottom(box) + box.verticalExtra);
1722
+ }
1723
+ const content = ratioContent(box, contentBottom(box) + box.verticalExtra);
1724
+ remember(box, ctx, width, containingWidth, 'content', content);
1725
+ return content;
1726
+ }
1727
+ /** A table item's rows and captions at a border-box width, its border box's
1728
+ * height apart from one it sets (`TABLE_CONTENT`): read from its layout
1729
+ * there, which it is given where it has another, and kept for the pass. */
1730
+ function tableContent(box, ctx, width, containing, laid) {
1731
+ const kept = keptNatural(box, ctx, width, containing);
1732
+ if (kept !== null && !Number.isNaN(kept.content))
1733
+ return kept.content;
1734
+ // where the box has that layout still, it is put back and not made again
1735
+ layoutNaturally(box, ctx, width, containing, laid);
1736
+ const content = TABLE_CONTENT.get(box) ?? box.height;
1737
+ remember(box, ctx, width, containing, 'content', content);
1738
+ return content;
1739
+ }
1266
1740
  /** A laid-out item's content height, its border box's: no less than its
1267
1741
  * width through its ratio, height of its own or not (CSS Sizing 4, 5.1). */
1268
1742
  function ratioContent(box, content) {
@@ -1300,13 +1774,16 @@ function ratioContent(box, content) {
1300
1774
  * from its shrink factors is exactly what it added.
1301
1775
  */
1302
1776
  function holdAt(node, laid, row, least, hold) {
1303
- if (row)
1777
+ if (row) {
1304
1778
  node.setMinWidth(least);
1779
+ laid.narrowest = least;
1780
+ }
1305
1781
  else
1306
1782
  node.setMinHeight(least);
1307
1783
  if (!hold.frozen)
1308
1784
  return;
1309
1785
  laid.auto = true;
1786
+ laid.minimum = least;
1310
1787
  freezeAt(node, laid, row, least, hold);
1311
1788
  }
1312
1789
  /** Freeze an item at a size along the main axis: inflexible, and that size
@@ -1322,63 +1799,13 @@ function freezeAt(node, laid, row, size, hold) {
1322
1799
  else
1323
1800
  node.setHeight(size);
1324
1801
  }
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) {
1802
+ /** What is between the border boxes of a line's items along the main
1803
+ * axis: the gaps, and their margins, an `auto` one none — which is what
1804
+ * it is while the line is shared out (CSS Flexbox 9.7, 8.1). */
1805
+ function betweenOf(items, row, gap) {
1806
+ let between = gap * Math.max(0, items.length - 1);
1807
+ for (const { box } of items) {
1380
1808
  const style = box.style;
1381
- // an `auto` margin is none on a line with no room (8.1)
1382
1809
  if (row) {
1383
1810
  if (style.marginLeft !== AUTO)
1384
1811
  between += box.marginLeft;
@@ -1391,67 +1818,626 @@ ask) {
1391
1818
  if (style.marginBottom !== AUTO)
1392
1819
  between += box.marginBottom;
1393
1820
  }
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
1821
  }
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)) {
1822
+ return between;
1823
+ }
1824
+ /**
1825
+ * Share out the one line of a box that does not wrap as CSS Flexbox 9.7
1826
+ * does, where Yoga's answer for it may not be that, and freeze every item
1827
+ * at the size it comes to; whether Yoga was asked again.
1828
+ *
1829
+ * Yoga 3.2.1 shares a line out in two passes where 9.7 goes round until
1830
+ * no item's limit stops it, and each pass has its own way of coming out
1831
+ * elsewhere:
1832
+ *
1833
+ * - the first takes each item a limit stops out of the sum of the flex
1834
+ * factors as it goes, and divides what the line is short of by that
1835
+ * running sum, but takes the item's size off what the line is short of
1836
+ * only once it has been round them all (react/yoga#2006, put right on
1837
+ * Yoga's `main` behind an errata bit and in no release). Every item
1838
+ * after the first one stopped is shrunk too far for it, and may be
1839
+ * stopped too: `width: 100px` three times with `min-width` 95px, 45px
1840
+ * and 0 in a row of 150 was 100, 100 and 100 wide, and overflowed it,
1841
+ * where 9.7 has 95, 45 and 10 — with every item stopped, the line was
1842
+ * left no shorter than it started, and the second pass shared out
1843
+ * nothing. Where what is left of the sum is a float's rounding of
1844
+ * nothing, the second pass divided by that, and each item came out
1845
+ * billions of pixels wide, or none;
1846
+ * - it weighs an item's share of a shortfall by its flex base size where
1847
+ * the item is stopped by a limit before it is shrunk, and adds it to the
1848
+ * sum at its size within the limit, or the other way round: `width:
1849
+ * 200px; max-width: 110.992px` beside `200px` within `62.706px`, each
1850
+ * with a `min-width`, in a row of 50 were 76.67 and 43.31, where 9.7
1851
+ * shrinks both from 200 and has them at their minimums;
1852
+ * - and with no limit at work, it weighs that share by the flex base size,
1853
+ * its border box's, where 9.7 weighs it by the inner flex base size, its
1854
+ * content box's (step 4c), so an item gives up more the more padding
1855
+ * and borders it has (`weighsPadding`);
1856
+ * - a limit that is more than the flex base size stops the item before
1857
+ * any sharing out, and Yoga shares out from there: `flex: 1;
1858
+ * min-width: 200px` beside a `flex: 1` in a row of 600 was 400 and
1859
+ * 200, where each grows from 0 and is 300;
1860
+ * - where the shrink factors come to less than 1, the line keeps that
1861
+ * part of what it is short of (9.7, step 4b), and Yoga shares all of it
1862
+ * out: a `flex-shrink: 0.5` alone was as wide as its row, where it
1863
+ * keeps half of what it overflows by.
1864
+ *
1865
+ * Which is why a line is looked at only where Yoga's answer shows one of
1866
+ * these could be at work (`unshared`), and else left as it is: a line
1867
+ * none of whose items is stopped, that has been shared out, whose items'
1868
+ * limits do not clamp their flex base sizes, whose shrink factors come to
1869
+ * 1 or more, and which is not short of room with padding and borders more
1870
+ * of one item's flex base size than of another's is shared out by Yoga as
1871
+ * by 9.7.
1872
+ *
1873
+ * Each item's flex base size is what its style says it is, or its
1874
+ * content's size as the measure function found it (`Laid.sized`), and
1875
+ * where neither is known, Yoga is asked for the line with no limits and
1876
+ * nothing flexing, where each item is that size (`askBases`). An item held
1877
+ * at its automatic minimum is frozen there already (`holdAt`), and the
1878
+ * rest are shared out around it.
1879
+ */
1880
+ function resolveLine(items, row, line,
1881
+ /** The size along the main axis Yoga laid the line out in. */
1882
+ room, hold,
1883
+ /** Lay the flex box out, as its nodes now are. */
1884
+ ask) {
1885
+ if (!unshared(items, row, room - line.between, line.probe))
1408
1886
  return false;
1887
+ let unknown = false;
1888
+ for (const { box, laid } of items) {
1889
+ laid.base = baseOf(box, laid, row, line.probe);
1890
+ if (Number.isNaN(laid.base) && Number.isNaN(laid.held))
1891
+ unknown = true;
1409
1892
  }
1410
- const stopped = items.filter(({ box, laid }) => shrinks(box, laid));
1411
- for (const { node } of stopped)
1412
- node.setFlexShrink(0);
1893
+ if (unknown)
1894
+ askBases(items, row, ask);
1895
+ const sizes = shareLine(items, row, line);
1896
+ if (items.every(({ laid }, i) => Math.abs(sizes[i] - laid.main) <= NEAR)) {
1897
+ // Yoga's answer, which asking for the bases has to be put back to
1898
+ if (unknown)
1899
+ ask();
1900
+ return unknown;
1901
+ }
1902
+ line.resolved = true;
1903
+ freezeLine(items, row, sizes, hold);
1413
1904
  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
- }
1905
+ return true;
1906
+ }
1907
+ /** Freeze each item of a line at the size it was shared out to, but for
1908
+ * one held at its automatic minimum whose flex base size is not known,
1909
+ * which is frozen there already. */
1910
+ function freezeLine(items, row, sizes, hold) {
1911
+ items.forEach(({ node, laid }, i) => {
1912
+ if (Number.isNaN(laid.base))
1913
+ return;
1914
+ freezeAt(node, laid, row, sizes[i], hold);
1915
+ // its content's size, where that is what it came to (`Laid.auto`)
1916
+ laid.auto = sizes[i] <= laid.minimum + NEAR;
1917
+ });
1918
+ }
1919
+ /** How near to its room a line may be and have room to spare
1920
+ * (`cappedLines`). */
1921
+ const SHORT = 0.001;
1922
+ /** Share a line this engine has shared out again from its items' flex base
1923
+ * sizes, and freeze each item at its size: where one is since held at its
1924
+ * content's size, the rest of the line is shared out around it. */
1925
+ function shareOut(items, row, line, hold) {
1926
+ const sizes = shareLine(items, row, line);
1927
+ freezeLine(items, row, sizes, hold);
1928
+ }
1929
+ /** Each item's size along the main axis as 9.7 shares the line out from
1930
+ * their flex base sizes (`Laid.base`); one held at its automatic minimum
1931
+ * is frozen there. */
1932
+ function shareLine(items, row, line) {
1933
+ const flexing = items.map(({ box, laid }) => {
1934
+ // an automatic minimum it is held to is its minimum, which 9.7 freezes
1935
+ // it at where the line shares out less to it — and not before, which
1936
+ // would leave it out of the free space the line started with (4b)
1937
+ let least = leastOf(box, laid, row);
1938
+ if (!Number.isNaN(laid.minimum))
1939
+ least = Math.max(least, laid.minimum);
1940
+ return {
1941
+ base: laid.base,
1942
+ least,
1943
+ most: Math.max(mostOf(box, laid, row), least),
1944
+ extra: row ? box.horizontalExtra : box.verticalExtra,
1945
+ grow: box.style.flexGrow,
1946
+ shrink: box.style.flexShrink,
1947
+ frozen: Number.isNaN(laid.base) ? laid.held : NaN,
1948
+ };
1949
+ });
1950
+ let room = line.size;
1951
+ if (Number.isNaN(room)) {
1952
+ // down a column that is as tall as its items, within its limits: the
1953
+ // sum of their hypothetical sizes (9.9.1)
1954
+ room = line.between;
1955
+ for (const item of flexing)
1956
+ room += hypotheticalOf(item);
1957
+ room = Math.max(line.floor, Math.min(line.ceiling, room));
1958
+ }
1959
+ return resolveLengths(flexing, room - line.between);
1960
+ }
1961
+ /**
1962
+ * Whether Yoga's answer for the one line of a box that does not wrap may
1963
+ * not be what CSS Flexbox 9.7 makes of it (`resolveLine`): where an item
1964
+ * has a limit of its own along the main axis, which 9.7 is then asked —
1965
+ * sums of numbers, where each item's flex base size is known — and where
1966
+ * none has, what shows in Yoga's answer: an item a limit stopped as the
1967
+ * line was shared out, which with none of its own is its padding and
1968
+ * borders, a line not shared out while an item may yet flex the way it is
1969
+ * out, or flex factors that come to less than 1, which Yoga shares out as
1970
+ * though they came to 1 — or, for one item that may grow and shrink, from
1971
+ * a flex basis of nothing.
1972
+ */
1973
+ function unshared(items, row,
1974
+ /** The room for the items' border boxes. */
1975
+ room,
1976
+ /** Whether the line is a min-content probe's (`FlexLine.probe`). */
1977
+ probe) {
1978
+ let sum = 0;
1979
+ for (const { laid } of items)
1980
+ sum += laid.main;
1981
+ const over = sum > room + NEAR;
1982
+ const under = sum < room - NEAR;
1983
+ let grows = 0;
1984
+ let shrinks = 0;
1985
+ let growable = false;
1986
+ for (const { box, laid } of items) {
1987
+ // held at its content's size, and frozen (`holdAt`)
1988
+ if (!Number.isNaN(laid.held))
1989
+ continue;
1990
+ if (!Number.isNaN(laid.least) || !Number.isNaN(laid.most))
1991
+ return true;
1992
+ const { flexGrow: grow, flexShrink: shrink } = box.style;
1993
+ const least = leastOf(box, laid, row);
1994
+ const base = baseOf(box, laid, row, probe);
1995
+ const size = laid.main;
1996
+ grows += grow;
1997
+ shrinks += shrink;
1998
+ // stopped by its padding where it was shrunk to it, and not where it
1999
+ // is that size unflexed
2000
+ if (shrink > 0 &&
2001
+ !under &&
2002
+ size <= least + NEAR &&
2003
+ !(base <= size + NEAR)) {
2004
+ return true;
1430
2005
  }
2006
+ if (over && shrink > 0 && size > least + NEAR)
2007
+ return true;
2008
+ if (under && grow > 0)
2009
+ growable = true;
1431
2010
  }
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);
2011
+ return ((growable && grows >= 1) ||
2012
+ (!under && shrinks > 0 && shrinks < 1) ||
2013
+ (!over && grows > 0 && grows < 1) ||
2014
+ (!under && weighsPadding(items, row, room, probe)));
2015
+ }
2016
+ /**
2017
+ * Whether Yoga may have shared out what a line is short of by its items'
2018
+ * padding and borders, where 9.7 leaves them out: Yoga weighs an item's
2019
+ * share by its flex shrink factor times its flex base size, its border
2020
+ * box's, and 9.7 by the factor times its inner flex base size, its content
2021
+ * box's (step 4c). The two are the same shares where padding and borders
2022
+ * are the same part of the flex base size of every item that shrinks —
2023
+ * none of it, most often — and else Yoga takes more from an item the more
2024
+ * of them it has: `width: 100px; padding-left: 50px` beside `width: 100px`
2025
+ * in a row of 150 was 90 and 60 wide, where each gives up 50 of its
2026
+ * content and is 100 and 50. Where an item's flex base size is not known,
2027
+ * its padding may be any part of it, and the line is looked at.
2028
+ */
2029
+ function weighsPadding(items, row,
2030
+ /** The room for the items' border boxes. */
2031
+ room, probe) {
2032
+ let sum = 0;
2033
+ let shrinking = 0;
2034
+ let padded = false;
2035
+ let unknown = false;
2036
+ // the part of the first flex base size that shrinks that is padding and
2037
+ // borders, and whether that of another is not the same
2038
+ let part = NaN;
2039
+ let uneven = false;
2040
+ for (const { box, laid } of items) {
2041
+ if (!Number.isNaN(laid.held)) {
2042
+ sum += laid.held;
2043
+ continue;
2044
+ }
2045
+ const base = baseOf(box, laid, row, probe);
2046
+ if (Number.isNaN(base))
2047
+ unknown = true;
1436
2048
  else
1437
- node.setFlexShrink(box.style.flexShrink);
2049
+ sum += base;
2050
+ if (!(box.style.flexShrink > 0))
2051
+ continue;
2052
+ shrinking += 1;
2053
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
2054
+ if (extra > 0)
2055
+ padded = true;
2056
+ if (Number.isNaN(base))
2057
+ continue;
2058
+ const of = base > 0 ? extra / base : 0;
2059
+ if (Number.isNaN(part))
2060
+ part = of;
2061
+ else if (Math.abs(of - part) > 1e-9)
2062
+ uneven = true;
2063
+ }
2064
+ if (shrinking < 2 || !padded)
2065
+ return false;
2066
+ return unknown || (uneven && sum > room + NEAR);
2067
+ }
2068
+ /** How near two sizes along the main axis are the same, as Yoga's float32s
2069
+ * have them. */
2070
+ const NEAR = 0.01;
2071
+ /** An item's flex base size, its border box's, where this engine knows it:
2072
+ * what its style says, or its content's size as last measured — fitted
2073
+ * to no room, in a min-content probe (`FlexLine.probe`) — and no less than
2074
+ * its padding and borders, as Yoga has it. Else NaN. */
2075
+ function baseOf(box, laid, row, probe) {
2076
+ const extra = row ? box.horizontalExtra : box.verticalExtra;
2077
+ const content = probe ? laid.fitted : laid.content;
2078
+ return Math.max(laid.sized ? content : laid.basis, extra);
2079
+ }
2080
+ /**
2081
+ * Give each item that is not frozen the flex base size Yoga makes of it,
2082
+ * where this engine does not know it (`baseOf`): the line laid out with no
2083
+ * limit on any of them along the main axis and none of them flexing, where
2084
+ * each is that size. Their limits and flex factors are put back after.
2085
+ */
2086
+ function askBases(items, row, ask) {
2087
+ const open = items.filter(({ laid }) => Number.isNaN(laid.held));
2088
+ for (const { node } of open) {
2089
+ if (row) {
2090
+ node.setMinWidth(Number.NaN);
2091
+ node.setMaxWidth(Number.NaN);
2092
+ }
2093
+ else {
2094
+ node.setMinHeight(Number.NaN);
2095
+ node.setMaxHeight(Number.NaN);
2096
+ }
2097
+ node.setFlexGrow(0);
2098
+ node.setFlexShrink(0);
1438
2099
  }
1439
2100
  ask();
1440
- return true;
2101
+ for (const { box, node, laid } of open) {
2102
+ if (Number.isNaN(laid.base)) {
2103
+ laid.base = row ? node.getComputedWidth() : node.getComputedHeight();
2104
+ }
2105
+ if (!Number.isNaN(laid.least)) {
2106
+ if (row)
2107
+ node.setMinWidth(laid.least);
2108
+ else
2109
+ node.setMinHeight(laid.least);
2110
+ }
2111
+ if (!Number.isNaN(laid.most)) {
2112
+ if (row)
2113
+ node.setMaxWidth(laid.most);
2114
+ else
2115
+ node.setMaxHeight(laid.most);
2116
+ }
2117
+ node.setFlexGrow(box.style.flexGrow);
2118
+ node.setFlexShrink(box.style.flexShrink);
2119
+ }
1441
2120
  }
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);
2121
+ /** An item's hypothetical main size: its flex base size within its limits
2122
+ * (CSS Flexbox 9.2, step 3), or where it is frozen, that size. */
2123
+ function hypotheticalOf(item) {
2124
+ return Number.isNaN(item.frozen)
2125
+ ? Math.max(item.least, Math.min(item.most, item.base))
2126
+ : item.frozen;
2127
+ }
2128
+ /**
2129
+ * Resolve the flexible lengths of a line (CSS Flexbox 9.7): each item's
2130
+ * size along the main axis, sharing out the room its border boxes have
2131
+ * from their flex base sizes, and going round again with each item a limit
2132
+ * stops frozen at it until none is — the minimums where the line is short
2133
+ * of more than the maximums give back, and the other way round.
2134
+ */
2135
+ function resolveLengths(line, room) {
2136
+ const count = line.length;
2137
+ const size = line.map(hypotheticalOf);
2138
+ let sum = 0;
2139
+ for (const s of size)
2140
+ sum += s;
2141
+ // step 1: grow where the line has room at the items' hypothetical sizes
2142
+ const grow = sum < room;
2143
+ // step 2: an item that does not flex, or would flex away from its limit,
2144
+ // is frozen at its hypothetical size — a flex base size Yoga worked out
2145
+ // being as near to that as its float32s come, and no further
2146
+ const frozen = line.map((item, i) => !Number.isNaN(item.frozen) ||
2147
+ (grow ? item.grow : item.shrink) === 0 ||
2148
+ (grow ? item.base > size[i] + NEAR : item.base < size[i] - NEAR));
2149
+ const free = () => {
2150
+ let left = room;
2151
+ for (let i = 0; i < count; i++)
2152
+ left -= frozen[i] ? size[i] : line[i].base;
2153
+ return left;
2154
+ };
2155
+ // step 3
2156
+ const initial = free();
2157
+ const off = new Array(count).fill(0);
2158
+ for (;;) {
2159
+ // step 4a
2160
+ let factors = 0;
2161
+ let open = false;
2162
+ for (let i = 0; i < count; i++) {
2163
+ if (frozen[i])
2164
+ continue;
2165
+ open = true;
2166
+ factors += grow ? line[i].grow : line[i].shrink;
2167
+ }
2168
+ if (!open)
2169
+ break;
2170
+ // 4b: flex factors that come to less than 1 share out that part of the
2171
+ // free space the line started with
2172
+ let left = free();
2173
+ if (factors < 1) {
2174
+ const part = initial * factors;
2175
+ if (Math.abs(part) < Math.abs(left))
2176
+ left = part;
2177
+ }
2178
+ // 4c: shared by the grow factors, or by the shrink factors each times
2179
+ // the item's inner flex base size
2180
+ let scaled = 0;
2181
+ if (!grow) {
2182
+ for (let i = 0; i < count; i++) {
2183
+ if (!frozen[i])
2184
+ scaled += line[i].shrink * (line[i].base - line[i].extra);
2185
+ }
2186
+ }
2187
+ // 4d: each within its limits, and how far that took it
2188
+ let violation = 0;
2189
+ for (let i = 0; i < count; i++) {
2190
+ if (frozen[i])
2191
+ continue;
2192
+ const item = line[i];
2193
+ let target = item.base;
2194
+ if (grow)
2195
+ target += (left * item.grow) / factors;
2196
+ else if (scaled > 0) {
2197
+ target -=
2198
+ (Math.abs(left) * item.shrink * (item.base - item.extra)) / scaled;
2199
+ }
2200
+ size[i] = Math.max(item.least, Math.min(item.most, target));
2201
+ off[i] = size[i] - target;
2202
+ violation += off[i];
2203
+ }
2204
+ // 4e: freeze those stopped the way most of them were, or all of them
2205
+ for (let i = 0; i < count; i++) {
2206
+ if (frozen[i])
2207
+ continue;
2208
+ if (violation === 0 || (violation > 0 ? off[i] > 0 : off[i] < 0)) {
2209
+ frozen[i] = true;
2210
+ }
2211
+ }
2212
+ }
2213
+ return size;
1446
2214
  }
1447
- /** How near to its room a line may be and be short of it (`stoppedLine`). */
1448
- const SHORT = 0.001;
1449
2215
  /** The least an item may be along the main axis, its border box: the
1450
2216
  * minimum of its own, and no less than its padding and borders. */
1451
2217
  function leastOf(box, laid, row) {
1452
2218
  const extra = row ? box.horizontalExtra : box.verticalExtra;
1453
2219
  return Number.isNaN(laid.least) ? extra : Math.max(laid.least, extra);
1454
2220
  }
2221
+ /**
2222
+ * Put right the lines of a flex box that wraps on which every item that
2223
+ * may grow is stopped by its maximum and Yoga made them smaller for it;
2224
+ * whether Yoga was asked again. The one line of a box that does not wrap
2225
+ * is shared out by 9.7 here where Yoga's answer may not be its
2226
+ * (`resolveLine`), which puts right this and more. Each such line is laid out with every item of it that
2227
+ * may grow frozen at its maximum, which is where CSS Flexbox leaves them
2228
+ * (9.7, step 4): the line has room left over with every one of them as
2229
+ * large as it goes. It is also where Yoga leaves them itself, where its
2230
+ * rounding is not below 0.
2231
+ *
2232
+ * It is a float's rounding of nothing. Yoga's first pass takes
2233
+ * each item its maximum stops out of the sum of the grow factors, and the
2234
+ * second divides the room still left by what is left of the sum: with
2235
+ * every item stopped, `(a + b + c) - a - b - c` in float32s. Where that is
2236
+ * 0, or a rounding above it, the quotient is vast and each item is held to
2237
+ * its maximum, which is right; where it is a rounding below, each is held
2238
+ * to its minimum. Three items of `flex-grow: 3.361`, `2.417` and `1.988`,
2239
+ * each `max-width: 10px`, in a row 100 wide came out nothing wide, as a
2240
+ * third of such lines of random factors do. Sums of whole numbers, and of
2241
+ * halves and quarters of them, are exact, so a box whose factors are those
2242
+ * is not looked at (`exactSums`).
2243
+ *
2244
+ * What makes a line one to put right is Yoga's answer. Every item of
2245
+ * the line that may grow has a
2246
+ * maximum, and the line has room to spare with each of them at it. And
2247
+ * one of them came out short of its maximum and no larger than its
2248
+ * hypothetical size (9.7, step 2), which Yoga, sharing out room, makes no
2249
+ * item: Yoga is asked for the line with nothing growing, where each item
2250
+ * is that size. No larger rather than smaller, because an item held to
2251
+ * its minimum is often that size: an empty one is nothing wide either
2252
+ * way. Any other line is put back as it was, and the two layouts that
2253
+ * took are the cost of a box with a factor that is not a whole number and
2254
+ * an item short of its maximum; any other box has cost a sum.
2255
+ *
2256
+ * A few of the lines put right are still not a browser's. Once
2257
+ * some of its items are frozen, 9.7 shares out to the rest only the part
2258
+ * of the room their factors come to, where that is less than 1 (step 4,
2259
+ * b), and Yoga has no such rule: an item of `flex-grow: 0.1` left alone
2260
+ * on a line is at its maximum here, as Yoga would have had it, and a
2261
+ * tenth of the room in Chrome.
2262
+ *
2263
+ * A box that wraps has lines like it: the last line of a grid of cards
2264
+ * with a `max-width` has room for every card it has. Yoga breaks lines by
2265
+ * its items' hypothetical sizes, which the layout with nothing growing
2266
+ * gives, and they are broken here as it breaks them (`linesOf`). Freezing
2267
+ * a line's items at their maximums moves no break: the line has room for
2268
+ * every item on it at that size, so it keeps them all, and the item after
2269
+ * it, which did not fit with them smaller, fits no better with them larger.
2270
+ * But Yoga holds a size to an item's maximum before its minimum, so where
2271
+ * the maximum is the smaller — or smaller than the item's padding and
2272
+ * borders — it breaks a line by one size and lays the item out at another:
2273
+ * `max-width: 2px; min-width: 10px` is 10 wide, on a line it was put on
2274
+ * as 2. A box that wraps with an item like that is left as Yoga had it.
2275
+ */
2276
+ function cappedLines(items, row,
2277
+ /** The flex box's size along the main axis. */
2278
+ room,
2279
+ /** The size Yoga breaks lines at: the flex box's where it wraps and has
2280
+ * one, and else Infinity, for the one line. */
2281
+ breaks,
2282
+ /** The gap between two items. */
2283
+ gap, hold,
2284
+ /** Lay the flex box out, as its nodes now are. */
2285
+ ask) {
2286
+ // two at the least: what is left of a sum of one, less it, is 0 exactly
2287
+ const growing = items.filter(({ box, laid }) => grows(box, laid));
2288
+ if (growing.length < 2 || exactSums(growing))
2289
+ return false;
2290
+ // one short of its maximum, as each on such a line is
2291
+ const short = growing.some(({ box, laid }) => laid.main < mostOf(box, laid, row) - 0.01);
2292
+ if (!short)
2293
+ return false;
2294
+ if (breaks === Infinity) {
2295
+ // and on the one line, every one of them with a maximum, and room left
2296
+ // over as Yoga has it
2297
+ let used = gap * (items.length - 1);
2298
+ for (const { box, laid } of items)
2299
+ used += laid.main + marginsOf(box, row);
2300
+ const capped = growing.every(({ box, laid }) => !Number.isNaN(laid.most));
2301
+ if (!capped || !(used < room - SHORT))
2302
+ return false;
2303
+ }
2304
+ else if (
2305
+ // and on lines broken where the layout says, with no item that Yoga
2306
+ // breaks a line by at another size
2307
+ items.some(({ box, laid }) => laid.most < leastOf(box, laid, row))) {
2308
+ return false;
2309
+ }
2310
+ const flexible = items.filter(({ laid }) => Number.isNaN(laid.held));
2311
+ for (const { node } of flexible) {
2312
+ node.setFlexGrow(0);
2313
+ node.setFlexShrink(0);
2314
+ }
2315
+ ask();
2316
+ // each item its hypothetical size, on every line
2317
+ const hypothetical = items.map(({ node, laid }) => row ? node.getComputedWidth() : heightOf(node, laid));
2318
+ const frozen = new Set();
2319
+ for (const line of linesOf(items, hypothetical, row, breaks, gap)) {
2320
+ const growers = line.filter((i) => grows(items[i].box, items[i].laid));
2321
+ if (growers.length < 2)
2322
+ continue;
2323
+ // the line with each item that may grow at its maximum: Infinity where
2324
+ // one has none
2325
+ let size = gap * (line.length - 1);
2326
+ let smaller = false;
2327
+ for (const i of line) {
2328
+ const { box, laid } = items[i];
2329
+ size += marginsOf(box, row);
2330
+ if (!grows(box, laid)) {
2331
+ size += hypothetical[i];
2332
+ continue;
2333
+ }
2334
+ const most = mostOf(box, laid, row);
2335
+ size += most;
2336
+ if (laid.main < most - 0.01 &&
2337
+ (laid.main < hypothetical[i] || nearly(laid.main, hypothetical[i]))) {
2338
+ smaller = true;
2339
+ }
2340
+ }
2341
+ if (!smaller || !(size < room - SHORT))
2342
+ continue;
2343
+ for (const i of growers)
2344
+ frozen.add(items[i].box);
2345
+ }
2346
+ for (const { box, node, laid } of flexible) {
2347
+ if (frozen.has(box)) {
2348
+ freezeAt(node, laid, row, mostOf(box, laid, row), hold);
2349
+ }
2350
+ else {
2351
+ node.setFlexGrow(box.style.flexGrow);
2352
+ node.setFlexShrink(box.style.flexShrink);
2353
+ }
2354
+ }
2355
+ ask();
2356
+ return true;
2357
+ }
2358
+ /**
2359
+ * The lines Yoga breaks a flex box's items into, as indices of them. A line
2360
+ * takes items while their hypothetical sizes, their margins and the gaps
2361
+ * between them come to no more than the size it breaks at, and always
2362
+ * takes one — added up in float32s, in the order Yoga adds them, so that an
2363
+ * item that fits exactly is on the line it is on in Yoga's.
2364
+ */
2365
+ function linesOf(items,
2366
+ /** Each item's hypothetical size along the main axis. */
2367
+ sizes, row, breaks, gap) {
2368
+ if (breaks === Infinity)
2369
+ return [items.map((_, i) => i)];
2370
+ const f = Math.fround;
2371
+ const limit = f(breaks);
2372
+ const lines = [];
2373
+ let line = [];
2374
+ let used = 0;
2375
+ items.forEach(({ box }, i) => {
2376
+ const size = f(sizes[i]);
2377
+ const style = box.style;
2378
+ const [start, end] = row
2379
+ ? [style.marginLeft, style.marginRight]
2380
+ : [style.marginTop, style.marginBottom];
2381
+ const margin = f((start === AUTO ? 0 : f(row ? box.marginLeft : box.marginTop)) +
2382
+ (end === AUTO ? 0 : f(row ? box.marginRight : box.marginBottom)));
2383
+ let lead = line.length ? f(gap) : 0;
2384
+ if (line.length && f(f(f(used + size) + margin) + lead) > limit) {
2385
+ lines.push(line);
2386
+ line = [];
2387
+ used = 0;
2388
+ lead = 0;
2389
+ }
2390
+ used = f(used + f(f(size + margin) + lead));
2391
+ line.push(i);
2392
+ });
2393
+ lines.push(line);
2394
+ return lines;
2395
+ }
2396
+ /** Whether Yoga may grow an item: it has a grow factor, and is not frozen
2397
+ * (`freezeAt`). */
2398
+ function grows(box, laid) {
2399
+ return box.style.flexGrow > 0 && Number.isNaN(laid.held);
2400
+ }
2401
+ /** The most an item may be along the main axis, its border box: the
2402
+ * maximum of its own, where it has one, and no less than its least size;
2403
+ * Infinity where it has none. */
2404
+ function mostOf(box, laid, row) {
2405
+ if (Number.isNaN(laid.most))
2406
+ return Infinity;
2407
+ return Math.max(laid.most, leastOf(box, laid, row));
2408
+ }
2409
+ /** An item's margins along the main axis, where they are not `auto`: an
2410
+ * `auto` margin is none to the size of its line (CSS Flexbox 8.1). */
2411
+ function marginsOf(box, row) {
2412
+ const style = box.style;
2413
+ if (row) {
2414
+ return ((style.marginLeft === AUTO ? 0 : box.marginLeft) +
2415
+ (style.marginRight === AUTO ? 0 : box.marginRight));
2416
+ }
2417
+ return ((style.marginTop === AUTO ? 0 : box.marginTop) +
2418
+ (style.marginBottom === AUTO ? 0 : box.marginBottom));
2419
+ }
2420
+ /**
2421
+ * Whether Yoga's sums of these items' grow factors are exact: in float32s,
2422
+ * as it adds them, a sum of whole numbers is, and so is one of halves and
2423
+ * quarters of them, and what is left of it less each of its terms is then
2424
+ * 0 exactly (`cappedLines`).
2425
+ */
2426
+ function exactSums(items) {
2427
+ let sum = 0;
2428
+ for (const { box } of items) {
2429
+ const factor = box.style.flexGrow * EXACT;
2430
+ if (!Number.isInteger(factor))
2431
+ return false;
2432
+ sum += factor;
2433
+ }
2434
+ // every sum along the way a whole number of the parts under a float32's
2435
+ // 24 bits of significand
2436
+ return sum < 2 ** 24;
2437
+ }
2438
+ /** The parts of one a grow factor may be a whole number of and have its
2439
+ * sums exact (`exactSums`). */
2440
+ const EXACT = 1024;
1455
2441
  /** An item's min-content width, its border box's, taken the once: its
1456
2442
  * content's, whatever width it has of its own (`keywordWidth`'s too). */
1457
2443
  function minContentOf(box, ctx, laid) {
@@ -1524,19 +2510,116 @@ function baselineLines(style, items) {
1524
2510
  line = [];
1525
2511
  }
1526
2512
  last = box.x;
1527
- const own = box.style;
1528
- const aligned = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
1529
- // an `auto` margin across the line takes it over alignment
1530
- if (aligned === 'baseline' &&
1531
- own.marginTop !== AUTO &&
1532
- own.marginBottom !== AUTO) {
2513
+ if (byBaseline(style, box.style))
1533
2514
  line.push(item);
1534
- }
1535
2515
  }
1536
2516
  close();
1537
2517
  // one alone on its line is at its start already
1538
2518
  return several ? out : null;
1539
2519
  }
2520
+ /** Whether an item takes part in aligning its line by baselines (CSS
2521
+ * Flexbox 8.3, 9.4 step 8): aligned by its baseline, with no `auto`
2522
+ * margin across the line, which takes it over its alignment. */
2523
+ function byBaseline(container, item) {
2524
+ const aligned = item.alignSelf === AUTO ? container.alignItems : item.alignSelf;
2525
+ return (aligned === 'baseline' &&
2526
+ item.marginTop !== AUTO &&
2527
+ item.marginBottom !== AUTO);
2528
+ }
2529
+ /**
2530
+ * Whether Yoga made a line of a row that wraps the row's height, where its
2531
+ * items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
2532
+ * Where `align-content` stretches the lines, that is an item stretched
2533
+ * across one that is shorter than its content at the width it was given:
2534
+ * its line is as tall as that, past the box. Where it centres them or ends
2535
+ * them, it is the one line of the row not as tall as the row, which moves
2536
+ * it from the row's start — and so where it spaces them out around or
2537
+ * evenly, which centres one line (CSS Box Alignment 3, 5.1). Lines it
2538
+ * starts at the row's start are there in Yoga's layout as well, and so is
2539
+ * the one line `space-between` starts there.
2540
+ */
2541
+ function heldLine(container, ctx, items,
2542
+ /** The row's content height. */
2543
+ height,
2544
+ /** The row's content width, the items' containing block's. */
2545
+ width,
2546
+ /** The gap between two items along the row. */
2547
+ gap) {
2548
+ const style = container.style;
2549
+ const lines = crossContent(style);
2550
+ if (lines !== 'stretch' &&
2551
+ lines !== 'center' &&
2552
+ lines !== 'flex-end' &&
2553
+ lines !== 'space-around' &&
2554
+ lines !== 'space-evenly') {
2555
+ return false;
2556
+ }
2557
+ const stretched = items.filter(({ box }) => stretches(box, style));
2558
+ if (stretched.length === 0)
2559
+ return false;
2560
+ if (lines === 'stretch') {
2561
+ return stretched.some(({ box, node, laid }) => naturalAcross(box, ctx, laid, width) > heightOf(node, laid) + 0.01);
2562
+ }
2563
+ // one line, which the items come to no more than the row's width along:
2564
+ // Yoga made a line each of them only where they come to more
2565
+ let along = gap * (items.length - 1);
2566
+ for (const { box, laid } of items)
2567
+ along += laid.main + marginsOf(box, true);
2568
+ if (along > width + 0.01)
2569
+ return false;
2570
+ // as tall as its tallest item, as Yoga has them: it lays them out at
2571
+ // their content's height where `align-content` does not stretch lines
2572
+ let line = 0;
2573
+ for (const { box, node, laid } of items) {
2574
+ line = Math.max(line, heightOf(node, laid) + marginsOf(box, false));
2575
+ }
2576
+ return Math.abs(line - height) > 0.01;
2577
+ }
2578
+ /**
2579
+ * A row item's border-box height as its content makes it at the width the
2580
+ * flex layout gave it, within its limits: the layout `placeItems` gives it
2581
+ * there, made now where it has none (`layoutItemAt`), and a replaced one's
2582
+ * through its ratio or its natural height.
2583
+ */
2584
+ function naturalAcross(box, ctx, laid,
2585
+ /** The flex box's content width, the item's containing block's. */
2586
+ containing) {
2587
+ const width = meant(laid.main, laid.set, laid.stretch, laid.width, laid.held);
2588
+ if (box.kind === 'replaced') {
2589
+ const own = box.intrinsic;
2590
+ const natural = heightThroughRatio(box, width) ??
2591
+ (own && !(own.missing & 2) ? own.height + box.verticalExtra : null);
2592
+ return natural === null ? 0 : clampHeight(box, natural);
2593
+ }
2594
+ if (box.kind === 'text' || box.kind === 'break')
2595
+ return 0;
2596
+ const kept = naturalHeight(box, ctx, width, containing, laid);
2597
+ if (!Number.isNaN(kept))
2598
+ return kept;
2599
+ layoutNaturally(box, ctx, width, containing, laid);
2600
+ return box.height;
2601
+ }
2602
+ /**
2603
+ * The alignment an item with an `auto` margin across a row that wraps comes
2604
+ * to in its line (CSS Flexbox 8.1, 9.6 step 13), as Yoga is told it
2605
+ * (`layoutFlex`): at the end where the margin at the line's cross start is
2606
+ * `auto`, at the start where the one at its end is, and centred where both
2607
+ * are; null where neither is. Its line's cross start is the box's bottom
2608
+ * where its lines wrap in reverse.
2609
+ *
2610
+ * The line is as tall as its tallest item, margins and all, so the item
2611
+ * comes to no more than it, and the margins take a share of no less than
2612
+ * nothing: the alignment puts it where they would.
2613
+ */
2614
+ function autoAcross(item, reverse) {
2615
+ const top = item.marginTop === AUTO;
2616
+ const bottom = item.marginBottom === AUTO;
2617
+ if (top && bottom)
2618
+ return 'center';
2619
+ if (!top && !bottom)
2620
+ return null;
2621
+ return top !== reverse ? 'flex-end' : 'flex-start';
2622
+ }
1540
2623
  /**
1541
2624
  * How far an item is from where it belongs across its line, where Yoga
1542
2625
  * (3.2.1) aligned the lines in the pass it takes for a box that wraps or
@@ -1545,45 +2628,315 @@ function baselineLines(style, items) {
1545
2628
  * none at either — `items-start` in a wrapping row put every card's top
1546
2629
  * margin under it. By the margin at the start of the line's cross axis, and
1547
2630
  * 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.
2631
+ * item set at its line's start (`started`), its baseline's.
2632
+ *
2633
+ * A row whose lines wrap in reverse Yoga lays out as though they wrapped
2634
+ * forward, and then turns each item over across the box, its margins with
2635
+ * it: each is where the other margin would have put it. One at its line's
2636
+ * start, the bottom, stands on nothing; one at its end, the top, or
2637
+ * stretched, is its bottom margin down from its line's top where it is its
2638
+ * top one; and one centred, half the difference. A column's, whose cross
2639
+ * axis runs right to left, or that wraps in reverse, is left as Yoga has
2640
+ * it, and so is an item an `auto` margin aligns in a row that does not
2641
+ * wrap: where Yoga still takes that pass for such a row, its items are
2642
+ * laid out again without it, aligned by their baselines (`layoutFlex`).
1551
2643
  */
1552
- function lineMarginFix(container, item, node, row, started = false) {
1553
- if (container.flexWrap === 'wrap-reverse')
1554
- return 0;
1555
- if (!row && container.direction === 'rtl')
2644
+ function lineMarginFix(container, item, node, row,
2645
+ /** Whether the flex box has a definite height. */
2646
+ own, started = false) {
2647
+ const reverse = container.flexWrap === 'wrap-reverse';
2648
+ if (!row && (reverse || container.direction === 'rtl'))
1556
2649
  return 0;
1557
2650
  const style = item.style;
1558
- // a margin that is `auto` takes the item over its alignment
2651
+ // a margin that is `auto` takes the item over its alignment, which Yoga
2652
+ // is told it comes to in a row that wraps (`autoAcross`)
1559
2653
  const lead = row ? style.marginTop : style.marginLeft;
1560
2654
  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;
2655
+ let align;
2656
+ if (started)
2657
+ align = 'flex-start';
2658
+ else if (lead === AUTO || trail === AUTO) {
2659
+ align =
2660
+ row && container.flexWrap !== 'nowrap'
2661
+ ? autoAcross(style, reverse)
2662
+ : null;
2663
+ }
2664
+ else {
2665
+ align = style.alignSelf === AUTO ? container.alignItems : style.alignSelf;
2666
+ // which sets one whose height is a percentage of none at its line's
2667
+ // start, as Yoga is told it does (`applyItem`)
2668
+ if (align === 'stretch' && row && heightOfNone(style, own)) {
2669
+ align = 'flex-start';
2670
+ }
2671
+ }
1570
2672
  const leading = node.getComputedMargin(row ? Y.EDGE_TOP : Y.EDGE_LEFT);
1571
- if (align === 'flex-start')
1572
- return leading;
1573
2673
  const trailing = node.getComputedMargin(row ? Y.EDGE_BOTTOM : Y.EDGE_RIGHT);
1574
- return (leading - trailing) / 2;
2674
+ switch (align) {
2675
+ case 'flex-start':
2676
+ return reverse ? -trailing : leading;
2677
+ case 'center':
2678
+ return (leading - trailing) / 2;
2679
+ case 'flex-end':
2680
+ case 'stretch':
2681
+ return reverse ? leading - trailing : 0;
2682
+ }
2683
+ return 0;
1575
2684
  }
1576
2685
  /** An item's first baseline, down from its border edge: its content's
1577
2686
  * first line's, or where it has none, or is replaced, one synthesized
1578
- * from its border box — its bottom edge. */
2687
+ * from its border box — its bottom edge. Past that edge where the line
2688
+ * is: only a scroll container's is held to its box (`firstBaselineIn`),
2689
+ * and an item shorter than its text, or whose padding puts the text
2690
+ * under it, sits on its text where it had sat on its bottom. */
1579
2691
  function itemBaseline(item) {
1580
2692
  if (item.kind !== 'replaced') {
1581
2693
  const found = firstBaselineIn(item);
1582
2694
  if (found !== null)
1583
- return Math.min(found - item.y, item.height);
2695
+ return found - item.y;
1584
2696
  }
1585
2697
  return item.height;
1586
2698
  }
2699
+ /**
2700
+ * The items of a column that wraps that Yoga (3.2.1) lays out at another
2701
+ * height than it sized them at, as it stretches them across their lines:
2702
+ * each it aligns `stretch`, with no width of its own, whose left and right
2703
+ * margins come to another sum than its top and bottom ones.
2704
+ *
2705
+ * Yoga aligns the lines of a box that wraps in a pass of its own, after it
2706
+ * has sized and placed every item down them, and lays each item it
2707
+ * stretches out again there at its line's width — and at its height plus
2708
+ * the margins across the line, where it means the ones down it
2709
+ * (CalculateLayout.cpp's multi-line `Align::Stretch`, still so on Yoga's
2710
+ * main). A leaf laid out at a width and a height is that size, so the item
2711
+ * came out its side margins taller and its top and bottom ones shorter
2712
+ * than the height its line was shared out to, which its place and the
2713
+ * next item's had been worked out from. The pass lays an item out again
2714
+ * wherever its side margins are not nothing, or its line is another width
2715
+ * than the one it was measured at, so `margin: 0 5px` on a line of words
2716
+ * was 28 tall where Chrome's is 18, and `margin: 4px 0 2px` in a column
2717
+ * with two lines 6 short; where the two sums are the same, the pass gives
2718
+ * the height back unchanged.
2719
+ */
2720
+ function stretchedDown(root, items) {
2721
+ const lines = root.getAlignItems();
2722
+ return items.filter(({ box, node }) => {
2723
+ const own = node.getAlignSelf();
2724
+ if ((own === Y.ALIGN_AUTO ? lines : own) !== Y.ALIGN_STRETCH)
2725
+ return false;
2726
+ const width = node.getWidth().unit;
2727
+ if (width !== Y.UNIT_AUTO && width !== Y.UNIT_UNDEFINED)
2728
+ return false;
2729
+ // as Yoga adds them up, an `auto` margin as nothing
2730
+ const style = box.style;
2731
+ const of = (len, px) => (len === AUTO ? 0 : px);
2732
+ const across = of(style.marginLeft, box.marginLeft) +
2733
+ of(style.marginRight, box.marginRight);
2734
+ const down = of(style.marginTop, box.marginTop) +
2735
+ of(style.marginBottom, box.marginBottom);
2736
+ return Math.abs(across - down) >= 0.0001;
2737
+ });
2738
+ }
2739
+ /**
2740
+ * Lay a column that wraps out, keeping for each item Yoga stretches across
2741
+ * its line the height it sized it at (`stretchedDown`, `Laid.unstretched`).
2742
+ *
2743
+ * Yoga is asked again with each such item set at its line's start, which
2744
+ * its multi-line pass does not lay out again: each is as tall as its line
2745
+ * made it. Its flex base size has to come out the same for that, so its
2746
+ * lines break and share out the same — and where Yoga hands the column's
2747
+ * width over as a size, it measures an item it stretches at that width, and
2748
+ * one it does not at its content's, so there the item is handed the width
2749
+ * as its own too. Then Yoga is asked once more as it was, so the lines and
2750
+ * the widths are its own, and only the heights are taken from the second
2751
+ * layout. Where an item's place down its line moved between the two, a
2752
+ * base moved too, and Yoga's height is kept, as it was.
2753
+ */
2754
+ function unstretch(items, stretched,
2755
+ /** The column's content width, where Yoga is handed it as a size, and
2756
+ * else null. */
2757
+ width, layout) {
2758
+ layout();
2759
+ const tops = items.map(({ node }) => node.getComputedTop());
2760
+ const aligned = stretched.map(({ box, node }) => {
2761
+ const own = node.getAlignSelf();
2762
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
2763
+ if (width !== null)
2764
+ node.setWidth(measuredAcross(box, node, width));
2765
+ return own;
2766
+ });
2767
+ layout();
2768
+ const kept = items.every(({ node }, i) => Math.abs(node.getComputedTop() - tops[i]) < 0.01);
2769
+ const heights = stretched.map(({ node }) => node.getComputedHeight());
2770
+ stretched.forEach(({ node }, i) => {
2771
+ node.setAlignSelf(aligned[i]);
2772
+ if (width !== null)
2773
+ node.setWidthAuto();
2774
+ });
2775
+ layout();
2776
+ stretched.forEach(({ laid }, i) => {
2777
+ laid.unstretched = kept ? heights[i] : NaN;
2778
+ });
2779
+ }
2780
+ /** The border-box width Yoga measures a column's item at for its flex base
2781
+ * size where it stretches it across a column of a width: the column's,
2782
+ * less its margins across, within its maximum. */
2783
+ function measuredAcross(box, node, width) {
2784
+ const style = box.style;
2785
+ const of = (len, px) => (len === AUTO ? 0 : px);
2786
+ const across = of(style.marginLeft, box.marginLeft) +
2787
+ of(style.marginRight, box.marginRight);
2788
+ const most = node.getMaxWidth();
2789
+ const limit = most.unit === Y.UNIT_POINT
2790
+ ? most.value
2791
+ : most.unit === Y.UNIT_PERCENT
2792
+ ? (most.value / 100) * width
2793
+ : Infinity;
2794
+ return Math.min(width - across, limit);
2795
+ }
2796
+ /** An item's border-box height, as Yoga sized it: the one it had before its
2797
+ * line stretched it, where that laid it out at another (`unstretch`,
2798
+ * `unspace`). */
2799
+ function heightOf(node, laid) {
2800
+ return Number.isNaN(laid.unstretched)
2801
+ ? node.getComputedHeight()
2802
+ : laid.unstretched;
2803
+ }
2804
+ /** Whether `align-content` spaces a flex box's lines out, putting what
2805
+ * room it has past them between them and around them. */
2806
+ function spacesLines(style) {
2807
+ const lines = crossContent(style);
2808
+ return (lines === 'space-between' ||
2809
+ lines === 'space-around' ||
2810
+ lines === 'space-evenly');
2811
+ }
2812
+ /**
2813
+ * Lay a row that wraps out, its lines spaced out by `align-content`,
2814
+ * keeping each item Yoga stretches across its line as tall as the line
2815
+ * (`Laid.unstretched`; CSS Flexbox 9.4, step 11).
2816
+ *
2817
+ * Yoga (3.2.1) sets the lines of a box that wraps in a pass of its own,
2818
+ * after it has sized them, and lays each item it stretches out again there
2819
+ * as tall as its line and the room it puts after the line as well
2820
+ * (CalculateLayout.cpp's multi-line `Align::Stretch`, still so on Yoga's
2821
+ * main). Each line was where it belongs, and every item on it that is not
2822
+ * stretched, but a stretched item ran down to the next line: two lines of
2823
+ * 18px items in a row 100 tall came to 82 apiece with `space-between`. So
2824
+ * Yoga is asked with the lines set at the row's start, where it puts no
2825
+ * room between them and each item is as tall as its line. Where the lines
2826
+ * leave the row no room (`linesReach`), that is the layout the spacing
2827
+ * makes too, and is kept. Where they do, Yoga is asked again as the row
2828
+ * is, for where the lines and the items are, and the heights are kept from
2829
+ * the first layout.
2830
+ *
2831
+ * And where the lines are taller than the row, `space-around` and
2832
+ * `space-evenly` fall back to `safe center`, which sets them at the row's
2833
+ * own start, its top (CSS Box Alignment 3, 4.3 and 5.1; CSS Flexbox 8.4).
2834
+ * Yoga falls back to `flex-start` for both, which is the bottom where the
2835
+ * lines run bottom to top, and ran them out past the row's top. It sets
2836
+ * the lines of a row that wraps in reverse top to bottom and turns them
2837
+ * over once they are set, so it is asked for them at `flex-end` there,
2838
+ * which comes out at the top — where the lines it set at their start, or
2839
+ * spaced, reach past the row's bottom.
2840
+ */
2841
+ function unspace(root, items,
2842
+ /** Whether the lines run bottom to top. */
2843
+ reverse, layout) {
2844
+ for (const { laid } of items)
2845
+ laid.unstretched = NaN;
2846
+ const lines = root.getAlignContent();
2847
+ // where the lines start at the row's bottom, its start is their end
2848
+ const safe = reverse &&
2849
+ (lines === Y.ALIGN_SPACE_AROUND || lines === Y.ALIGN_SPACE_EVENLY);
2850
+ const atEnd = () => {
2851
+ root.setAlignContent(Y.ALIGN_FLEX_END);
2852
+ layout();
2853
+ root.setAlignContent(lines);
2854
+ };
2855
+ // each item Yoga stretches: `stretch`, with no height of its own
2856
+ const align = root.getAlignItems();
2857
+ const stretched = items.filter(({ node }) => {
2858
+ const own = node.getAlignSelf();
2859
+ if ((own === Y.ALIGN_AUTO ? align : own) !== Y.ALIGN_STRETCH)
2860
+ return false;
2861
+ const height = node.getHeight().unit;
2862
+ return height === Y.UNIT_AUTO || height === Y.UNIT_UNDEFINED;
2863
+ });
2864
+ if (stretched.length === 0) {
2865
+ layout();
2866
+ // spaced where there is room, and else at their start, past the bottom
2867
+ if (safe &&
2868
+ linesReach(root, items, reverse) - root.getComputedHeight() > NEAR) {
2869
+ atEnd();
2870
+ }
2871
+ return;
2872
+ }
2873
+ root.setAlignContent(Y.ALIGN_FLEX_START);
2874
+ layout();
2875
+ root.setAlignContent(lines);
2876
+ const room = root.getComputedHeight() - linesReach(root, items, reverse);
2877
+ if (safe && room < -NEAR) {
2878
+ atEnd();
2879
+ return;
2880
+ }
2881
+ if (!(room > NEAR))
2882
+ return;
2883
+ const heights = stretched.map(({ node }) => node.getComputedHeight());
2884
+ layout();
2885
+ stretched.forEach(({ node, laid }, i) => {
2886
+ if (Math.abs(node.getComputedHeight() - heights[i]) >= NEAR) {
2887
+ laid.unstretched = heights[i];
2888
+ }
2889
+ });
2890
+ }
2891
+ /**
2892
+ * How far down a row that wraps its lines reach, at the least, in the
2893
+ * layout Yoga made of it with the lines at its start (`unspace`): the most,
2894
+ * over its items, of where the item's line ends at the least. An item is as
2895
+ * tall as its line with its margins where it is stretched across it, and
2896
+ * no taller where it is not, and Yoga ends it at its line's end, or with
2897
+ * its margins at the line's start, or centres it with neither
2898
+ * (`lineMarginFix`). Read with the lines turned back the right way up where
2899
+ * they run bottom to top.
2900
+ */
2901
+ function linesReach(root, items, reverse) {
2902
+ const height = root.getComputedHeight();
2903
+ const lines = root.getAlignItems();
2904
+ let reach = 0;
2905
+ for (const { box, node } of items) {
2906
+ const tall = node.getComputedHeight();
2907
+ const top = reverse
2908
+ ? height - node.getComputedTop() - tall
2909
+ : node.getComputedTop();
2910
+ const style = box.style;
2911
+ const above = style.marginTop === AUTO ? 0 : box.marginTop;
2912
+ const below = style.marginBottom === AUTO ? 0 : box.marginBottom;
2913
+ const own = node.getAlignSelf();
2914
+ const align = own === Y.ALIGN_AUTO ? lines : own;
2915
+ // where Yoga set it at its line's start with no margin above it, the
2916
+ // line runs on past it by both its margins at the least, and where it
2917
+ // centred it, by half of them
2918
+ const lead = align === Y.ALIGN_FLEX_START
2919
+ ? above
2920
+ : align === Y.ALIGN_CENTER
2921
+ ? (above - below) / 2
2922
+ : 0;
2923
+ reach = Math.max(reach, top + tall + below + lead);
2924
+ }
2925
+ return reach;
2926
+ }
2927
+ /**
2928
+ * Whether a row item's height is a percentage of a height its flex box has
2929
+ * none of. That is `auto` to the item's size (CSS 2.1 10.5), and is not
2930
+ * handed to Yoga (`applyItem`), but it is not `auto` to its alignment:
2931
+ * `stretch` stretches an item whose height computes to `auto` (CSS Flexbox
2932
+ * 8.3), and this one's computes to the percentage. Such an item is as tall
2933
+ * as its content, set at its line's start, which is what Yoga is told.
2934
+ */
2935
+ function heightOfNone(style,
2936
+ /** Whether the flex box has a definite height. */
2937
+ own) {
2938
+ return !own && isPct(style.height);
2939
+ }
1587
2940
  /** Whether an item is stretched across its line: `stretch`, its own or its
1588
2941
  * container's, with no height of its own and no `auto` margin across. */
1589
2942
  function stretches(box, container,
@@ -1602,6 +2955,159 @@ row = true) {
1602
2955
  style.marginLeft !== AUTO &&
1603
2956
  style.marginRight !== AUTO;
1604
2957
  }
2958
+ /** Whether a flex box laid out at a width is a column that wraps, whose
2959
+ * lines are as wide as their items and not as the box, and are placed
2960
+ * across it here (`crossLines`). */
2961
+ function wrapsColumn(style, width) {
2962
+ return (Number.isFinite(width) &&
2963
+ !style.flexDirection.startsWith('row') &&
2964
+ style.flexWrap !== 'nowrap');
2965
+ }
2966
+ /**
2967
+ * Place a column that wraps across: its lines across the box, and each
2968
+ * item across its line (CSS Flexbox 9.4, steps 8 to 11, and 9.6, steps 13
2969
+ * to 16). From the layout Yoga made of it with every item at its line's
2970
+ * start (`layoutFlex`), in which each item is as wide as it is before it is
2971
+ * stretched, `fit-content` or its own width within its limits, and each
2972
+ * line starts its gap and a pixel past the one before, which is how the
2973
+ * items of one line are told from the next one's.
2974
+ *
2975
+ * A line is as wide as its widest item, margins and all. `align-content`
2976
+ * (its `start` and `end` the box's own sides, `crossContent`) shares out
2977
+ * what the box has past its lines: `stretch` to each line alike,
2978
+ * where they are short of the box; `center` and `flex-end` whether they are
2979
+ * or not, so that lines wider than the box run past both its sides, or its
2980
+ * start; and the spacing values only where there is room, setting the lines
2981
+ * at their start where there is none, or at the box's (CSS Box Alignment 3,
2982
+ * 4.3: `space-between` falls back to `flex-start`, `space-around` and
2983
+ * `space-evenly` to `safe center`, the box's own start where the lines
2984
+ * overflow, and not theirs where they wrap in reverse). An
2985
+ * item with an `auto` margin across its line takes the room the line has
2986
+ * past it on that side (8.1), and none where there is none; a stretched
2987
+ * item is as wide as its line less its margins, within its limits (9.4,
2988
+ * step 11), and as tall as the column made it; and the rest are where
2989
+ * `align-self` says, a baseline at the line's start, as a column has none
2990
+ * across it to align by.
2991
+ */
2992
+ function crossLines(style, items,
2993
+ /** The flex box's content width. */
2994
+ width,
2995
+ /** The gap between two lines. */
2996
+ gap) {
2997
+ // the lines start at the right where the box runs right to left, or
2998
+ // where they wrap in reverse, and at the left where both are so
2999
+ const fromRight = (style.direction === 'rtl') !== (style.flexWrap === 'wrap-reverse');
3000
+ const lines = [];
3001
+ const sizes = [];
3002
+ // each item's margins across, from its line's start, an `auto` one none
3003
+ const lead = [];
3004
+ const trail = [];
3005
+ let last = NaN;
3006
+ items.forEach(({ box, node, laid }, i) => {
3007
+ const yoga = node.getComputedWidth();
3008
+ const left = node.getComputedLeft();
3009
+ const start = fromRight ? width - left - yoga : left;
3010
+ if (!(Math.abs(start - last) < 0.5))
3011
+ lines.push([]);
3012
+ last = start;
3013
+ lines[lines.length - 1].push(i);
3014
+ sizes.push(meant(yoga, laid.set, laid.stretch, laid.width));
3015
+ const own = box.style;
3016
+ const l = own.marginLeft === AUTO ? 0 : box.marginLeft;
3017
+ const r = own.marginRight === AUTO ? 0 : box.marginRight;
3018
+ lead.push(fromRight ? r : l);
3019
+ trail.push(fromRight ? l : r);
3020
+ });
3021
+ const across = lines.map((line) => line.reduce((most, i) => Math.max(most, sizes[i] + lead[i] + trail[i]), 0));
3022
+ const count = lines.length;
3023
+ let room = width - gap * (count - 1);
3024
+ for (const size of across)
3025
+ room -= size;
3026
+ let at = 0;
3027
+ let between = gap;
3028
+ // Where they are wider than the box, `space-around` and `space-evenly`
3029
+ // set the lines at the box's own start (`safe center`), which is where
3030
+ // their last one ends where they wrap in reverse: they run past its end
3031
+ const reverse = style.flexWrap === 'wrap-reverse';
3032
+ switch (crossContent(style)) {
3033
+ case 'flex-end':
3034
+ at = room;
3035
+ break;
3036
+ case 'center':
3037
+ at = room / 2;
3038
+ break;
3039
+ case 'stretch':
3040
+ if (room > 0)
3041
+ across.forEach((_, k) => (across[k] += room / count));
3042
+ break;
3043
+ case 'space-between':
3044
+ if (room > 0 && count > 1)
3045
+ between += room / (count - 1);
3046
+ break;
3047
+ case 'space-around':
3048
+ if (room > 0) {
3049
+ at = room / count / 2;
3050
+ between += room / count;
3051
+ }
3052
+ else if (reverse)
3053
+ at = room;
3054
+ break;
3055
+ case 'space-evenly':
3056
+ if (room > 0) {
3057
+ at = room / (count + 1);
3058
+ between += room / (count + 1);
3059
+ }
3060
+ else if (reverse)
3061
+ at = room;
3062
+ break;
3063
+ }
3064
+ const placed = new Map();
3065
+ lines.forEach((line, k) => {
3066
+ const size = across[k];
3067
+ for (const i of line) {
3068
+ const { box, node } = items[i];
3069
+ const own = box.style;
3070
+ let itemWidth = sizes[i];
3071
+ let offset = lead[i];
3072
+ const free = size - itemWidth - lead[i] - trail[i];
3073
+ const before = fromRight ? own.marginRight : own.marginLeft;
3074
+ const after = fromRight ? own.marginLeft : own.marginRight;
3075
+ if (before === AUTO || after === AUTO) {
3076
+ if (free > 0 && before === AUTO) {
3077
+ offset += after === AUTO ? free / 2 : free;
3078
+ }
3079
+ }
3080
+ else if (stretches(box, style, false) && !own.widthKeyword) {
3081
+ const most = pointsOf(node.getMaxWidth());
3082
+ const least = pointsOf(node.getMinWidth());
3083
+ itemWidth = size - lead[i] - trail[i];
3084
+ if (itemWidth > most)
3085
+ itemWidth = most;
3086
+ if (itemWidth < least)
3087
+ itemWidth = least;
3088
+ itemWidth = Math.max(itemWidth, box.horizontalExtra);
3089
+ }
3090
+ else {
3091
+ const align = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
3092
+ if (align === 'center')
3093
+ offset += free / 2;
3094
+ else if (align === 'flex-end')
3095
+ offset += free;
3096
+ }
3097
+ const start = at + offset;
3098
+ placed.set(box, {
3099
+ x: fromRight ? width - start - itemWidth : start,
3100
+ width: itemWidth,
3101
+ });
3102
+ }
3103
+ at += size + between;
3104
+ });
3105
+ return placed;
3106
+ }
3107
+ /** A length Yoga holds in points, or NaN where it holds none. */
3108
+ function pointsOf(value) {
3109
+ return value.unit === Y.UNIT_POINT ? value.value : NaN;
3110
+ }
1605
3111
  /** How deep flex boxes are laid out by Yoga, one inside another's measure
1606
3112
  * (`layoutFlex`). */
1607
3113
  const FLEX_DEPTH = 64;
@@ -1660,6 +3166,20 @@ function mainJustify(style) {
1660
3166
  start = (justify === 'left') === (style.direction !== 'rtl');
1661
3167
  return start !== reverse ? 'flex-start' : 'flex-end';
1662
3168
  }
3169
+ /**
3170
+ * An `align-content` as the cross axis takes it (CSS Box Alignment 3,
3171
+ * 5.1): `start` and `end` are the box's own edges, which are its lines'
3172
+ * cross ends turned round where the box wraps in reverse — `end` in a row
3173
+ * `wrap-reverse` is the bottom, where its lines start.
3174
+ */
3175
+ function crossContent(style) {
3176
+ const lines = style.alignContent;
3177
+ if (lines !== 'start' && lines !== 'end')
3178
+ return lines;
3179
+ return (lines === 'start') === (style.flexWrap !== 'wrap-reverse')
3180
+ ? 'flex-start'
3181
+ : 'flex-end';
3182
+ }
1663
3183
  const JUSTIFY = {
1664
3184
  'flex-start': Y?.JUSTIFY_FLEX_START,
1665
3185
  'flex-end': Y?.JUSTIFY_FLEX_END,
@@ -1677,5 +3197,6 @@ const ALIGN = {
1677
3197
  baseline: Y?.ALIGN_BASELINE,
1678
3198
  'space-between': Y?.ALIGN_SPACE_BETWEEN,
1679
3199
  'space-around': Y?.ALIGN_SPACE_AROUND,
3200
+ 'space-evenly': Y?.ALIGN_SPACE_EVENLY,
1680
3201
  };
1681
3202
  //# sourceMappingURL=flex.js.map