@react-x11/components 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/html/css/animation.d.ts +91 -0
  2. package/dist/html/css/animation.d.ts.map +1 -0
  3. package/dist/html/css/animation.js +430 -0
  4. package/dist/html/css/animation.js.map +1 -0
  5. package/dist/html/css/cascade.d.ts +27 -4
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +94 -6
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/parse.d.ts +35 -0
  10. package/dist/html/css/parse.d.ts.map +1 -1
  11. package/dist/html/css/parse.js +115 -2
  12. package/dist/html/css/parse.js.map +1 -1
  13. package/dist/html/css/style.d.ts +5 -0
  14. package/dist/html/css/style.d.ts.map +1 -1
  15. package/dist/html/css/style.js +57 -7
  16. package/dist/html/css/style.js.map +1 -1
  17. package/dist/html/css/ua.js +12 -8
  18. package/dist/html/css/ua.js.map +1 -1
  19. package/dist/html/layout/boxes.d.ts +13 -0
  20. package/dist/html/layout/boxes.d.ts.map +1 -1
  21. package/dist/html/layout/boxes.js +28 -0
  22. package/dist/html/layout/boxes.js.map +1 -1
  23. package/dist/html/layout/flex.d.ts.map +1 -1
  24. package/dist/html/layout/flex.js +237 -67
  25. package/dist/html/layout/flex.js.map +1 -1
  26. package/dist/html/layout/inline.d.ts.map +1 -1
  27. package/dist/html/layout/inline.js +41 -11
  28. package/dist/html/layout/inline.js.map +1 -1
  29. package/dist/html/node.d.ts.map +1 -1
  30. package/dist/html/node.js +12 -0
  31. package/dist/html/node.js.map +1 -1
  32. package/package.json +1 -1
  33. package/src/html/css/animation.ts +466 -0
  34. package/src/html/css/cascade.ts +130 -7
  35. package/src/html/css/parse.ts +151 -2
  36. package/src/html/css/style.ts +69 -7
  37. package/src/html/css/ua.ts +12 -8
  38. package/src/html/layout/boxes.ts +40 -0
  39. package/src/html/layout/flex.ts +251 -75
  40. package/src/html/layout/inline.ts +43 -9
  41. package/src/html/node.ts +12 -0
@@ -210,6 +210,9 @@ export function layoutFlex(box, ctx, contentWidth) {
210
210
  root.setWidth(contentWidth);
211
211
  const direction = box.style.direction === 'rtl' ? Y.DIRECTION_RTL : Y.DIRECTION_LTR;
212
212
  const row = box.style.flexDirection.startsWith('row');
213
+ const baselines = row &&
214
+ (box.style.alignItems === 'baseline' ||
215
+ items.some(({ box: child }) => child.style.alignSelf === 'baseline'));
213
216
  // An `auto` margin across a row that wraps takes the room its line has
214
217
  // past the item (CSS Flexbox 8.1, 9.6), where Yoga's pass that sets the
215
218
  // items across their lines reads an item's alignment and not its
@@ -224,6 +227,46 @@ export function layoutFlex(box, ctx, contentWidth) {
224
227
  node.setAlignSelf(ALIGN[aligned]);
225
228
  }
226
229
  }
230
+ else if (baselines) {
231
+ // Yoga takes a row aligned by baselines through that pass whether it
232
+ // wraps or not, and sets an item with an `auto` margin there by its
233
+ // baseline: at the top, where the margin puts it at the bottom, and
234
+ // every item beside it as far down as its height reached. That item
235
+ // takes no part in aligning the line by baselines (9.4 step 8), and
236
+ // Yoga is told the alignment its margins come to.
237
+ for (const { box: child, node } of items) {
238
+ const aligned = autoAcross(child.style, false);
239
+ if (aligned)
240
+ node.setAlignSelf(ALIGN[aligned]);
241
+ }
242
+ }
243
+ // Items aligned by their baselines, which Yoga cannot see, are told they
244
+ // are at their lines' starts, which is where one is that shares its line
245
+ // with no other (8.3); those that do share one are lined up by their
246
+ // baselines once those are known (`baselineLines`), and the row laid out
247
+ // again. Told an item is aligned by its baseline, Yoga sizes each line
248
+ // of the row as the most any item on it reaches down to its bottom edge
249
+ // — a baseline, to Yoga — and the largest bottom margin besides,
250
+ // whichever items those are, and stretches the items on the line to
251
+ // that: a row of no height of its own was as tall as both, and
252
+ // `align-content` had that much less room to share out. And it takes a
253
+ // row that does not wrap through its pass for lines, which sets the one
254
+ // line there as tall as its items and not the row: an item centred or at
255
+ // the end of a row of a height was centred or ended in the items'
256
+ // height. Told none is, it takes neither. A row whose lines wrap in
257
+ // reverse is left to Yoga.
258
+ const started = new Set();
259
+ if (baselines && box.style.flexWrap !== 'wrap-reverse') {
260
+ for (const { box: child, node } of items) {
261
+ if (!byBaseline(box.style, child.style))
262
+ continue;
263
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
264
+ started.add(child);
265
+ }
266
+ if (box.style.alignItems === 'baseline') {
267
+ root.setAlignItems(Y.ALIGN_FLEX_START);
268
+ }
269
+ }
227
270
  // frozen at its least size on the one line of a box that does not
228
271
  // wrap, by a basis where Yoga takes one along the main axis
229
272
  // (`applyItem`'s `setBasis`)
@@ -242,11 +285,12 @@ export function layoutFlex(box, ctx, contentWidth) {
242
285
  // `align-content` spaces out come out of its layout as tall as their
243
286
  // line and the room after it as well (`unspace`): laid out so that each
244
287
  // is as tall as its line, where the box has room to space them out in —
245
- // a height, or a minimum one
288
+ // a height, or a minimum one — and with the lines at its start where it
289
+ // may be too short for them, a height or a maximum one
246
290
  const spaced = row &&
247
291
  bounded &&
248
292
  !hold.frozen &&
249
- (height !== null || floor > 0) &&
293
+ (height !== null || floor > 0 || ceiling < Infinity) &&
250
294
  spacesLines(box.style);
251
295
  const ask = restretched.length > 0
252
296
  ? () => unstretch(items, restretched, sized, layout)
@@ -357,31 +401,23 @@ export function layoutFlex(box, ctx, contentWidth) {
357
401
  ctx.flexDepth = depth;
358
402
  }
359
403
  }
360
- // Yoga aligns the lines of a box that wraps, or aligns by baselines, in
361
- // one pass of its own, which drops a margin (`lineMarginFix`)
362
- const baselines = box.style.alignItems === 'baseline' ||
363
- items.some(({ box: child }) => child.style.alignSelf === 'baseline');
364
- let bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap' || baselines, contentWidth, undefined, crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined);
365
- // Items aligned by their baselines, which Yoga cannot see, are aligned
366
- // with their baselines known (`baselineLines`)
367
- // by the flex layout again, set at their lines' starts with the margins
368
- // that put their baselines together, which makes each line as tall as
369
- // that does
370
- const lines = row && baselines ? baselineLines(box.style, items) : null;
404
+ // Yoga aligns the lines of a box that wraps in one pass of its own, which
405
+ // drops a margin (`lineMarginFix`), and keeps the margins of one that
406
+ // does not, which it takes through that pass only where it is told an
407
+ // item is aligned by its baseline: here, never
408
+ const lined = box.style.flexWrap !== 'nowrap';
409
+ let bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started, crossed ? crossLines(box.style, items, contentWidth, columnGap) : undefined);
410
+ // Items that share a line and are aligned by their baselines are laid
411
+ // out by the flex layout again, set at their lines' starts with the
412
+ // margins that put their baselines together (`baselineLines`), which
413
+ // makes each line as tall as that does
414
+ const lines = started.size > 1 ? baselineLines(box.style, items) : null;
371
415
  if (lines) {
372
- // every one of them, and the box's own `baseline` too, which is what
373
- // takes Yoga down that pass
374
- const started = new Set();
375
416
  for (const line of lines) {
376
- line.forEach(({ box: child, node }, i) => {
377
- node.setAlignSelf(Y.ALIGN_FLEX_START);
417
+ line.forEach(({ node }, i) => {
378
418
  node.setMargin(Y.EDGE_TOP, line.margins[i]);
379
- started.add(child);
380
419
  });
381
420
  }
382
- if (box.style.alignItems === 'baseline') {
383
- root.setAlignItems(Y.ALIGN_FLEX_START);
384
- }
385
421
  ctx.flexDepth = depth + 1;
386
422
  try {
387
423
  calculate();
@@ -389,7 +425,7 @@ export function layoutFlex(box, ctx, contentWidth) {
389
425
  finally {
390
426
  ctx.flexDepth = depth;
391
427
  }
392
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap', contentWidth, started);
428
+ bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started);
393
429
  }
394
430
  // A replaced item with a ratio that is stretched across a line of a
395
431
  // definite size takes its flex base size from the stretched one through
@@ -407,7 +443,7 @@ export function layoutFlex(box, ctx, contentWidth) {
407
443
  finally {
408
444
  ctx.flexDepth = depth;
409
445
  }
410
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap' || baselines, contentWidth);
446
+ bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started);
411
447
  }
412
448
  const contentHeight = root.getComputedHeight();
413
449
  root.freeRecursive();
@@ -578,14 +614,23 @@ crossed) {
578
614
  let bottom = 0;
579
615
  // the lines of a row that wraps in reverse are turned round to run up it
580
616
  const reverse = row && box.style.flexWrap === 'wrap-reverse';
617
+ // the left edge a column's items aligned by baselines share, where it
618
+ // does not wrap (`baselineEdge`); a column that wraps is `crossed`
619
+ const edge = row || box.style.flexWrap !== 'nowrap'
620
+ ? NaN
621
+ : baselineEdge(box.style, items);
581
622
  for (const { box: child, node, laid } of items) {
582
623
  const placed = crossed?.get(child);
583
624
  const across = lined && !placed
584
- ? lineMarginFix(box.style, child, node, row, started?.has(child))
625
+ ? lineMarginFix(box.style, child, node, row, own, started?.has(child))
585
626
  : 0;
586
627
  const itemHeight = row ? heightOf(node, laid) : laid.main;
587
628
  const left = box.contentX +
588
- (placed ? placed.x : node.getComputedLeft() + (row ? 0 : across));
629
+ (placed
630
+ ? placed.x
631
+ : !Number.isNaN(edge) && byBaseline(box.style, child.style, false)
632
+ ? edge
633
+ : node.getComputedLeft() + (row ? 0 : across));
589
634
  // which Yoga turns an item round in by the height it laid it out at, and
590
635
  // not the one it is (`unspace`)
591
636
  const top = box.contentY +
@@ -771,6 +816,14 @@ weighed) {
771
816
  node.setFlexShrink(style.flexShrink);
772
817
  if (style.alignSelf !== AUTO)
773
818
  node.setAlignSelf(ALIGN[style.alignSelf] ?? Y.ALIGN_AUTO);
819
+ // `stretch` sets an item whose height is a percentage of none at its
820
+ // line's start, as tall as its content (`heightOfNone`), where Yoga,
821
+ // which is handed no height for it, stretched it across the line
822
+ if (row && box.parent && heightOfNone(style, own)) {
823
+ const align = style.alignSelf === AUTO ? box.parent.style.alignItems : style.alignSelf;
824
+ if (align === 'stretch')
825
+ node.setAlignSelf(Y.ALIGN_FLEX_START);
826
+ }
774
827
  // Yoga's sizes are border boxes, as `box-sizing: border-box` has them;
775
828
  // a `content-box` length is the content's, and the item's padding and
776
829
  // border go on top of it
@@ -2427,15 +2480,15 @@ function contentBottom(box) {
2427
2480
  * its bottom edge, so it lined them up by their bottoms. With the items laid
2428
2481
  * out, each line's, and for each the top margin that sets it at the line's
2429
2482
  * start with its first baseline where the lowest-reaching one's is — or
2430
- * null where no line has two to align.
2483
+ * null where no line has two to align. Not for a row whose lines wrap in
2484
+ * reverse, which grow from their ends and are left to Yoga (`layoutFlex`).
2431
2485
  */
2432
2486
  function baselineLines(style, items) {
2433
- // a line that grows from its end is aligned from there, and left to Yoga
2434
- if (style.flexWrap === 'wrap-reverse')
2435
- return null;
2436
2487
  const wraps = style.flexWrap !== 'nowrap';
2437
- // along a line the items go the way the main axis does, and the next
2438
- // line starts where one does not
2488
+ // along a line the items go the way the main axis does, each margin box
2489
+ // after the last one's end, and the next line starts before it (9.3): at
2490
+ // the line's start, where an item with a margin before it was further
2491
+ // along than one alone on the line before
2439
2492
  const back = (style.flexDirection === 'row-reverse') !== (style.direction === 'rtl');
2440
2493
  const out = [];
2441
2494
  let line = [];
@@ -2456,25 +2509,64 @@ function baselineLines(style, items) {
2456
2509
  out.push(Object.assign(line, { margins }));
2457
2510
  };
2458
2511
  for (const item of items) {
2459
- const box = item.box;
2460
- if (wraps && line.length && (back ? box.x >= last : box.x <= last)) {
2512
+ const { box, node } = item;
2513
+ const left = node.getComputedLeft() - node.getComputedMargin(Y.EDGE_LEFT);
2514
+ const right = node.getComputedLeft() +
2515
+ node.getComputedWidth() +
2516
+ node.getComputedMargin(Y.EDGE_RIGHT);
2517
+ if (wraps &&
2518
+ line.length &&
2519
+ (back ? right > last + 0.01 : left < last - 0.01)) {
2461
2520
  close();
2462
2521
  line = [];
2463
2522
  }
2464
- last = box.x;
2465
- const own = box.style;
2466
- const aligned = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
2467
- // an `auto` margin across the line takes it over alignment
2468
- if (aligned === 'baseline' &&
2469
- own.marginTop !== AUTO &&
2470
- own.marginBottom !== AUTO) {
2523
+ last = back ? left : right;
2524
+ if (byBaseline(style, box.style))
2471
2525
  line.push(item);
2472
- }
2473
2526
  }
2474
2527
  close();
2475
2528
  // one alone on its line is at its start already
2476
2529
  return several ? out : null;
2477
2530
  }
2531
+ /** Whether an item takes part in aligning its line by baselines (CSS
2532
+ * Flexbox 8.3, 9.4 step 8): aligned by its baseline, with no `auto`
2533
+ * margin across the line, which takes it over its alignment. */
2534
+ function byBaseline(container, item,
2535
+ /** Whether the line is a row's, across which its margins are its top
2536
+ * and bottom ones; a column's are its left and right ones. */
2537
+ row = true) {
2538
+ const aligned = item.alignSelf === AUTO ? container.alignItems : item.alignSelf;
2539
+ return (aligned === 'baseline' &&
2540
+ (row ? item.marginTop : item.marginLeft) !== AUTO &&
2541
+ (row ? item.marginBottom : item.marginRight) !== AUTO);
2542
+ }
2543
+ /**
2544
+ * Where the items a column that does not wrap aligns by baselines have
2545
+ * their left edges, from its content box's: NaN where it has none.
2546
+ *
2547
+ * A horizontal item has no baseline across a column, and one is made from
2548
+ * its border box — the line-under edge of the vertical writing mode it is
2549
+ * taken to have, its left, whichever way its text runs (CSS Box Alignment
2550
+ * 3, 9.1). So the items share their left edges, and are set together at
2551
+ * the line's cross start, the column's left or, where it runs right to
2552
+ * left, its right (9.3; CSS Flexbox 8.3), as Chrome sets them: as far from
2553
+ * there as the most any of them reaches past its edge on that side. Yoga
2554
+ * never aligns a column by baselines, and sets each at its line's start
2555
+ * with its margin there, so that is the furthest any of them is from the
2556
+ * start: the rightmost left edge, or from the right the leftmost.
2557
+ */
2558
+ function baselineEdge(container, items) {
2559
+ const rtl = container.direction === 'rtl';
2560
+ let edge = NaN;
2561
+ for (const { box, node } of items) {
2562
+ if (!byBaseline(container, box.style, false))
2563
+ continue;
2564
+ const left = node.getComputedLeft();
2565
+ if (!(rtl ? left >= edge : left <= edge))
2566
+ edge = left;
2567
+ }
2568
+ return edge;
2569
+ }
2478
2570
  /**
2479
2571
  * Whether Yoga made a line of a row that wraps the row's height, where its
2480
2572
  * items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
@@ -2571,13 +2663,13 @@ function autoAcross(item, reverse) {
2571
2663
  }
2572
2664
  /**
2573
2665
  * How far an item is from where it belongs across its line, where Yoga
2574
- * (3.2.1) aligned the lines in the pass it takes for a box that wraps or
2575
- * aligns by baselines: it sets an item aligned to its line's start there
2576
- * as though it had no margin at that side, and one centred as though it had
2577
- * none at either — `items-start` in a wrapping row put every card's top
2578
- * margin under it. By the margin at the start of the line's cross axis, and
2579
- * by half what it exceeds the one at the end by, as Yoga holds them: for an
2580
- * item set at its line's start (`started`), its baseline's.
2666
+ * (3.2.1) aligned the lines in the pass it takes for a box that wraps: it
2667
+ * sets an item aligned to its line's start there as though it had no
2668
+ * margin at that side, and one centred as though it had none at either —
2669
+ * `items-start` in a wrapping row put every card's top margin under it. By
2670
+ * the margin at the start of the line's cross axis, and by half what it
2671
+ * exceeds the one at the end by, as Yoga holds them: for an item set at
2672
+ * its line's start (`started`), its baseline's.
2581
2673
  *
2582
2674
  * A row whose lines wrap in reverse Yoga lays out as though they wrapped
2583
2675
  * forward, and then turns each item over across the box, its margins with
@@ -2586,10 +2678,11 @@ function autoAcross(item, reverse) {
2586
2678
  * stretched, is its bottom margin down from its line's top where it is its
2587
2679
  * top one; and one centred, half the difference. A column's, whose cross
2588
2680
  * axis runs right to left, or that wraps in reverse, is left as Yoga has
2589
- * it, and so is an item an `auto` margin aligns in a row that does not
2590
- * wrap.
2681
+ * it, and so is an item an `auto` margin aligns across a column.
2591
2682
  */
2592
- function lineMarginFix(container, item, node, row, started = false) {
2683
+ function lineMarginFix(container, item, node, row,
2684
+ /** Whether the flex box has a definite height. */
2685
+ own, started = false) {
2593
2686
  const reverse = container.flexWrap === 'wrap-reverse';
2594
2687
  if (!row && (reverse || container.direction === 'rtl'))
2595
2688
  return 0;
@@ -2602,13 +2695,15 @@ function lineMarginFix(container, item, node, row, started = false) {
2602
2695
  if (started)
2603
2696
  align = 'flex-start';
2604
2697
  else if (lead === AUTO || trail === AUTO) {
2605
- align =
2606
- row && container.flexWrap !== 'nowrap'
2607
- ? autoAcross(style, reverse)
2608
- : null;
2698
+ align = row ? autoAcross(style, reverse) : null;
2609
2699
  }
2610
2700
  else {
2611
2701
  align = style.alignSelf === AUTO ? container.alignItems : style.alignSelf;
2702
+ // which sets one whose height is a percentage of none at its line's
2703
+ // start, as Yoga is told it does (`applyItem`)
2704
+ if (align === 'stretch' && row && heightOfNone(style, own)) {
2705
+ align = 'flex-start';
2706
+ }
2612
2707
  }
2613
2708
  const leading = node.getComputedMargin(row ? Y.EDGE_TOP : Y.EDGE_LEFT);
2614
2709
  const trailing = node.getComputedMargin(row ? Y.EDGE_BOTTOM : Y.EDGE_RIGHT);
@@ -2625,12 +2720,15 @@ function lineMarginFix(container, item, node, row, started = false) {
2625
2720
  }
2626
2721
  /** An item's first baseline, down from its border edge: its content's
2627
2722
  * first line's, or where it has none, or is replaced, one synthesized
2628
- * from its border box — its bottom edge. */
2723
+ * from its border box — its bottom edge. Past that edge where the line
2724
+ * is: only a scroll container's is held to its box (`firstBaselineIn`),
2725
+ * and an item shorter than its text, or whose padding puts the text
2726
+ * under it, sits on its text where it had sat on its bottom. */
2629
2727
  function itemBaseline(item) {
2630
2728
  if (item.kind !== 'replaced') {
2631
2729
  const found = firstBaselineIn(item);
2632
2730
  if (found !== null)
2633
- return Math.min(found - item.y, item.height);
2731
+ return found - item.y;
2634
2732
  }
2635
2733
  return item.height;
2636
2734
  }
@@ -2765,12 +2863,31 @@ function spacesLines(style) {
2765
2863
  * makes too, and is kept. Where they do, Yoga is asked again as the row
2766
2864
  * is, for where the lines and the items are, and the heights are kept from
2767
2865
  * the first layout.
2866
+ *
2867
+ * And where the lines are taller than the row, `space-around` and
2868
+ * `space-evenly` fall back to `safe center`, which sets them at the row's
2869
+ * own start, its top (CSS Box Alignment 3, 4.3 and 5.1; CSS Flexbox 8.4).
2870
+ * Yoga falls back to `flex-start` for both, which is the bottom where the
2871
+ * lines run bottom to top, and ran them out past the row's top. It sets
2872
+ * the lines of a row that wraps in reverse top to bottom and turns them
2873
+ * over once they are set, so it is asked for them at `flex-end` there,
2874
+ * which comes out at the top — where the lines it set at their start, or
2875
+ * spaced, reach past the row's bottom.
2768
2876
  */
2769
2877
  function unspace(root, items,
2770
2878
  /** Whether the lines run bottom to top. */
2771
2879
  reverse, layout) {
2772
2880
  for (const { laid } of items)
2773
2881
  laid.unstretched = NaN;
2882
+ const lines = root.getAlignContent();
2883
+ // where the lines start at the row's bottom, its start is their end
2884
+ const safe = reverse &&
2885
+ (lines === Y.ALIGN_SPACE_AROUND || lines === Y.ALIGN_SPACE_EVENLY);
2886
+ const atEnd = () => {
2887
+ root.setAlignContent(Y.ALIGN_FLEX_END);
2888
+ layout();
2889
+ root.setAlignContent(lines);
2890
+ };
2774
2891
  // each item Yoga stretches: `stretch`, with no height of its own
2775
2892
  const align = root.getAlignItems();
2776
2893
  const stretched = items.filter(({ node }) => {
@@ -2782,15 +2899,23 @@ reverse, layout) {
2782
2899
  });
2783
2900
  if (stretched.length === 0) {
2784
2901
  layout();
2902
+ // spaced where there is room, and else at their start, past the bottom
2903
+ if (safe &&
2904
+ linesReach(root, items, reverse) - root.getComputedHeight() > NEAR) {
2905
+ atEnd();
2906
+ }
2785
2907
  return;
2786
2908
  }
2787
- const lines = root.getAlignContent();
2788
2909
  root.setAlignContent(Y.ALIGN_FLEX_START);
2789
2910
  layout();
2790
2911
  root.setAlignContent(lines);
2791
- if (!(root.getComputedHeight() - linesReach(root, items, reverse) > NEAR)) {
2912
+ const room = root.getComputedHeight() - linesReach(root, items, reverse);
2913
+ if (safe && room < -NEAR) {
2914
+ atEnd();
2792
2915
  return;
2793
2916
  }
2917
+ if (!(room > NEAR))
2918
+ return;
2794
2919
  const heights = stretched.map(({ node }) => node.getComputedHeight());
2795
2920
  layout();
2796
2921
  stretched.forEach(({ node, laid }, i) => {
@@ -2835,6 +2960,19 @@ function linesReach(root, items, reverse) {
2835
2960
  }
2836
2961
  return reach;
2837
2962
  }
2963
+ /**
2964
+ * Whether a row item's height is a percentage of a height its flex box has
2965
+ * none of. That is `auto` to the item's size (CSS 2.1 10.5), and is not
2966
+ * handed to Yoga (`applyItem`), but it is not `auto` to its alignment:
2967
+ * `stretch` stretches an item whose height computes to `auto` (CSS Flexbox
2968
+ * 8.3), and this one's computes to the percentage. Such an item is as tall
2969
+ * as its content, set at its line's start, which is what Yoga is told.
2970
+ */
2971
+ function heightOfNone(style,
2972
+ /** Whether the flex box has a definite height. */
2973
+ own) {
2974
+ return !own && isPct(style.height);
2975
+ }
2838
2976
  /** Whether an item is stretched across its line: `stretch`, its own or its
2839
2977
  * container's, with no height of its own and no `auto` margin across. */
2840
2978
  function stretches(box, container,
@@ -2876,15 +3014,17 @@ function wrapsColumn(style, width) {
2876
3014
  * where they are short of the box; `center` and `flex-end` whether they are
2877
3015
  * or not, so that lines wider than the box run past both its sides, or its
2878
3016
  * start; and the spacing values only where there is room, setting the lines
2879
- * at the box's start where there is none (CSS Box Alignment 3, 4.3:
2880
- * `space-between` falls back to `flex-start`, `space-around` and
2881
- * `space-evenly` to `safe center`, the start where the lines overflow). An
3017
+ * at their start where there is none, or at the box's (CSS Box Alignment 3,
3018
+ * 4.3: `space-between` falls back to `flex-start`, `space-around` and
3019
+ * `space-evenly` to `safe center`, the box's own start where the lines
3020
+ * overflow, and not theirs where they wrap in reverse). An
2882
3021
  * item with an `auto` margin across its line takes the room the line has
2883
3022
  * past it on that side (8.1), and none where there is none; a stretched
2884
3023
  * item is as wide as its line less its margins, within its limits (9.4,
2885
- * step 11), and as tall as the column made it; and the rest are where
2886
- * `align-self` says, a baseline at the line's start, as a column has none
2887
- * across it to align by.
3024
+ * step 11), and as tall as the column made it; those aligned by baselines
3025
+ * share their left edges, set together at the line's start, where a column
3026
+ * that does not wrap sets them (`baselineEdge`); and the rest are where
3027
+ * `align-self` says.
2888
3028
  */
2889
3029
  function crossLines(style, items,
2890
3030
  /** The flex box's content width. */
@@ -2915,13 +3055,36 @@ gap) {
2915
3055
  lead.push(fromRight ? r : l);
2916
3056
  trail.push(fromRight ? l : r);
2917
3057
  });
2918
- const across = lines.map((line) => line.reduce((most, i) => Math.max(most, sizes[i] + lead[i] + trail[i]), 0));
3058
+ // A line is as wide as its widest item, margins and all, and as the
3059
+ // items it aligns by baselines come to, sharing their left edges: the
3060
+ // most any reaches from the line's start to that edge, and the most any
3061
+ // reaches past it (`baselineEdge`; 9.4 step 8). `reach` is each line's
3062
+ // first, where those items' left edges are from its start.
3063
+ const reach = [];
3064
+ const across = lines.map((line) => {
3065
+ let most = 0;
3066
+ let before = -Infinity;
3067
+ let after = -Infinity;
3068
+ for (const i of line) {
3069
+ most = Math.max(most, sizes[i] + lead[i] + trail[i]);
3070
+ if (!byBaseline(style, items[i].box.style, false))
3071
+ continue;
3072
+ before = Math.max(before, lead[i] + (fromRight ? sizes[i] : 0));
3073
+ after = Math.max(after, trail[i] + (fromRight ? 0 : sizes[i]));
3074
+ }
3075
+ reach.push(before);
3076
+ return Math.max(most, before + after);
3077
+ });
2919
3078
  const count = lines.length;
2920
3079
  let room = width - gap * (count - 1);
2921
3080
  for (const size of across)
2922
3081
  room -= size;
2923
3082
  let at = 0;
2924
3083
  let between = gap;
3084
+ // Where they are wider than the box, `space-around` and `space-evenly`
3085
+ // set the lines at the box's own start (`safe center`), which is where
3086
+ // their last one ends where they wrap in reverse: they run past its end
3087
+ const reverse = style.flexWrap === 'wrap-reverse';
2925
3088
  switch (crossContent(style)) {
2926
3089
  case 'flex-end':
2927
3090
  at = room;
@@ -2942,12 +3105,16 @@ gap) {
2942
3105
  at = room / count / 2;
2943
3106
  between += room / count;
2944
3107
  }
3108
+ else if (reverse)
3109
+ at = room;
2945
3110
  break;
2946
3111
  case 'space-evenly':
2947
3112
  if (room > 0) {
2948
3113
  at = room / (count + 1);
2949
3114
  between += room / (count + 1);
2950
3115
  }
3116
+ else if (reverse)
3117
+ at = room;
2951
3118
  break;
2952
3119
  }
2953
3120
  const placed = new Map();
@@ -2976,6 +3143,9 @@ gap) {
2976
3143
  itemWidth = least;
2977
3144
  itemWidth = Math.max(itemWidth, box.horizontalExtra);
2978
3145
  }
3146
+ else if (byBaseline(style, own, false)) {
3147
+ offset = reach[k] - (fromRight ? itemWidth : 0);
3148
+ }
2979
3149
  else {
2980
3150
  const align = own.alignSelf === AUTO ? style.alignItems : own.alignSelf;
2981
3151
  if (align === 'center')