@react-x11/components 0.16.0 → 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.
@@ -210,6 +210,13 @@ 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
+ // 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'));
213
220
  // An `auto` margin across a row that wraps takes the room its line has
214
221
  // past the item (CSS Flexbox 8.1, 9.6), where Yoga's pass that sets the
215
222
  // items across their lines reads an item's alignment and not its
@@ -224,6 +231,38 @@ export function layoutFlex(box, ctx, contentWidth) {
224
231
  node.setAlignSelf(ALIGN[aligned]);
225
232
  }
226
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
+ }
227
266
  // frozen at its least size on the one line of a box that does not
228
267
  // wrap, by a basis where Yoga takes one along the main axis
229
268
  // (`applyItem`'s `setBasis`)
@@ -242,11 +281,12 @@ export function layoutFlex(box, ctx, contentWidth) {
242
281
  // `align-content` spaces out come out of its layout as tall as their
243
282
  // line and the room after it as well (`unspace`): laid out so that each
244
283
  // is as tall as its line, where the box has room to space them out in —
245
- // a height, or a minimum one
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
246
286
  const spaced = row &&
247
287
  bounded &&
248
288
  !hold.frozen &&
249
- (height !== null || floor > 0) &&
289
+ (height !== null || floor > 0 || ceiling < Infinity) &&
250
290
  spacesLines(box.style);
251
291
  const ask = restretched.length > 0
252
292
  ? () => unstretch(items, restretched, sized, layout)
@@ -357,21 +397,19 @@ export function layoutFlex(box, ctx, contentWidth) {
357
397
  ctx.flexDepth = depth;
358
398
  }
359
399
  }
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);
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);
365
403
  // Items aligned by their baselines, which Yoga cannot see, are aligned
366
404
  // with their baselines known (`baselineLines`)
367
405
  // by the flex layout again, set at their lines' starts with the margins
368
406
  // that put their baselines together, which makes each line as tall as
369
407
  // that does
370
- const lines = row && baselines ? baselineLines(box.style, items) : null;
408
+ const lines = baselines ? baselineLines(box.style, items) : null;
409
+ const started = new Set();
371
410
  if (lines) {
372
411
  // every one of them, and the box's own `baseline` too, which is what
373
412
  // takes Yoga down that pass
374
- const started = new Set();
375
413
  for (const line of lines) {
376
414
  line.forEach(({ box: child, node }, i) => {
377
415
  node.setAlignSelf(Y.ALIGN_FLEX_START);
@@ -382,6 +420,8 @@ export function layoutFlex(box, ctx, contentWidth) {
382
420
  if (box.style.alignItems === 'baseline') {
383
421
  root.setAlignItems(Y.ALIGN_FLEX_START);
384
422
  }
423
+ // so it takes it only where the row wraps
424
+ lined = box.style.flexWrap !== 'nowrap';
385
425
  ctx.flexDepth = depth + 1;
386
426
  try {
387
427
  calculate();
@@ -389,7 +429,7 @@ export function layoutFlex(box, ctx, contentWidth) {
389
429
  finally {
390
430
  ctx.flexDepth = depth;
391
431
  }
392
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap', contentWidth, started);
432
+ bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started);
393
433
  }
394
434
  // A replaced item with a ratio that is stretched across a line of a
395
435
  // definite size takes its flex base size from the stretched one through
@@ -407,7 +447,7 @@ export function layoutFlex(box, ctx, contentWidth) {
407
447
  finally {
408
448
  ctx.flexDepth = depth;
409
449
  }
410
- bottom = placeItems(box, ctx, items, row, own, box.style.flexWrap !== 'nowrap' || baselines, contentWidth);
450
+ bottom = placeItems(box, ctx, items, row, own, lined, contentWidth, started);
411
451
  }
412
452
  const contentHeight = root.getComputedHeight();
413
453
  root.freeRecursive();
@@ -581,7 +621,7 @@ crossed) {
581
621
  for (const { box: child, node, laid } of items) {
582
622
  const placed = crossed?.get(child);
583
623
  const across = lined && !placed
584
- ? lineMarginFix(box.style, child, node, row, started?.has(child))
624
+ ? lineMarginFix(box.style, child, node, row, own, started?.has(child))
585
625
  : 0;
586
626
  const itemHeight = row ? heightOf(node, laid) : laid.main;
587
627
  const left = box.contentX +
@@ -771,6 +811,14 @@ weighed) {
771
811
  node.setFlexShrink(style.flexShrink);
772
812
  if (style.alignSelf !== AUTO)
773
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
+ }
774
822
  // Yoga's sizes are border boxes, as `box-sizing: border-box` has them;
775
823
  // a `content-box` length is the content's, and the item's padding and
776
824
  // border go on top of it
@@ -2462,19 +2510,22 @@ function baselineLines(style, items) {
2462
2510
  line = [];
2463
2511
  }
2464
2512
  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) {
2513
+ if (byBaseline(style, box.style))
2471
2514
  line.push(item);
2472
- }
2473
2515
  }
2474
2516
  close();
2475
2517
  // one alone on its line is at its start already
2476
2518
  return several ? out : null;
2477
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
+ }
2478
2529
  /**
2479
2530
  * Whether Yoga made a line of a row that wraps the row's height, where its
2480
2531
  * items make it another (CSS Flexbox 9.4, steps 7 and 8; `layoutFlex`).
@@ -2587,9 +2638,12 @@ function autoAcross(item, reverse) {
2587
2638
  * top one; and one centred, half the difference. A column's, whose cross
2588
2639
  * axis runs right to left, or that wraps in reverse, is left as Yoga has
2589
2640
  * it, and so is an item an `auto` margin aligns in a row that does not
2590
- * wrap.
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`).
2591
2643
  */
2592
- function lineMarginFix(container, item, node, row, started = false) {
2644
+ function lineMarginFix(container, item, node, row,
2645
+ /** Whether the flex box has a definite height. */
2646
+ own, started = false) {
2593
2647
  const reverse = container.flexWrap === 'wrap-reverse';
2594
2648
  if (!row && (reverse || container.direction === 'rtl'))
2595
2649
  return 0;
@@ -2609,6 +2663,11 @@ function lineMarginFix(container, item, node, row, started = false) {
2609
2663
  }
2610
2664
  else {
2611
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
+ }
2612
2671
  }
2613
2672
  const leading = node.getComputedMargin(row ? Y.EDGE_TOP : Y.EDGE_LEFT);
2614
2673
  const trailing = node.getComputedMargin(row ? Y.EDGE_BOTTOM : Y.EDGE_RIGHT);
@@ -2625,12 +2684,15 @@ function lineMarginFix(container, item, node, row, started = false) {
2625
2684
  }
2626
2685
  /** An item's first baseline, down from its border edge: its content's
2627
2686
  * first line's, or where it has none, or is replaced, one synthesized
2628
- * 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. */
2629
2691
  function itemBaseline(item) {
2630
2692
  if (item.kind !== 'replaced') {
2631
2693
  const found = firstBaselineIn(item);
2632
2694
  if (found !== null)
2633
- return Math.min(found - item.y, item.height);
2695
+ return found - item.y;
2634
2696
  }
2635
2697
  return item.height;
2636
2698
  }
@@ -2765,12 +2827,31 @@ function spacesLines(style) {
2765
2827
  * makes too, and is kept. Where they do, Yoga is asked again as the row
2766
2828
  * is, for where the lines and the items are, and the heights are kept from
2767
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.
2768
2840
  */
2769
2841
  function unspace(root, items,
2770
2842
  /** Whether the lines run bottom to top. */
2771
2843
  reverse, layout) {
2772
2844
  for (const { laid } of items)
2773
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
+ };
2774
2855
  // each item Yoga stretches: `stretch`, with no height of its own
2775
2856
  const align = root.getAlignItems();
2776
2857
  const stretched = items.filter(({ node }) => {
@@ -2782,15 +2863,23 @@ reverse, layout) {
2782
2863
  });
2783
2864
  if (stretched.length === 0) {
2784
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
+ }
2785
2871
  return;
2786
2872
  }
2787
- const lines = root.getAlignContent();
2788
2873
  root.setAlignContent(Y.ALIGN_FLEX_START);
2789
2874
  layout();
2790
2875
  root.setAlignContent(lines);
2791
- if (!(root.getComputedHeight() - linesReach(root, items, reverse) > NEAR)) {
2876
+ const room = root.getComputedHeight() - linesReach(root, items, reverse);
2877
+ if (safe && room < -NEAR) {
2878
+ atEnd();
2792
2879
  return;
2793
2880
  }
2881
+ if (!(room > NEAR))
2882
+ return;
2794
2883
  const heights = stretched.map(({ node }) => node.getComputedHeight());
2795
2884
  layout();
2796
2885
  stretched.forEach(({ node, laid }, i) => {
@@ -2835,6 +2924,19 @@ function linesReach(root, items, reverse) {
2835
2924
  }
2836
2925
  return reach;
2837
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
+ }
2838
2940
  /** Whether an item is stretched across its line: `stretch`, its own or its
2839
2941
  * container's, with no height of its own and no `auto` margin across. */
2840
2942
  function stretches(box, container,
@@ -2876,9 +2978,10 @@ function wrapsColumn(style, width) {
2876
2978
  * where they are short of the box; `center` and `flex-end` whether they are
2877
2979
  * or not, so that lines wider than the box run past both its sides, or its
2878
2980
  * 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
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
2882
2985
  * item with an `auto` margin across its line takes the room the line has
2883
2986
  * past it on that side (8.1), and none where there is none; a stretched
2884
2987
  * item is as wide as its line less its margins, within its limits (9.4,
@@ -2922,6 +3025,10 @@ gap) {
2922
3025
  room -= size;
2923
3026
  let at = 0;
2924
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';
2925
3032
  switch (crossContent(style)) {
2926
3033
  case 'flex-end':
2927
3034
  at = room;
@@ -2942,12 +3049,16 @@ gap) {
2942
3049
  at = room / count / 2;
2943
3050
  between += room / count;
2944
3051
  }
3052
+ else if (reverse)
3053
+ at = room;
2945
3054
  break;
2946
3055
  case 'space-evenly':
2947
3056
  if (room > 0) {
2948
3057
  at = room / (count + 1);
2949
3058
  between += room / (count + 1);
2950
3059
  }
3060
+ else if (reverse)
3061
+ at = room;
2951
3062
  break;
2952
3063
  }
2953
3064
  const placed = new Map();