@react-x11/components 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +171 -26
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +616 -216
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +18 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -1
  22. package/dist/html/css/shapes.js +21 -1
  23. package/dist/html/css/shapes.js.map +1 -1
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +74 -12
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +15 -0
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +29 -9
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +141 -52
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +16 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +144 -23
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +11 -9
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1631 -221
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +23 -8
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +886 -324
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +35 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +118 -26
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts.map +1 -1
  64. package/dist/html/paint.js +558 -178
  65. package/dist/html/paint.js.map +1 -1
  66. package/dist/html/resources.js +14 -1
  67. package/dist/html/resources.js.map +1 -1
  68. package/dist/html/svg.d.ts +23 -7
  69. package/dist/html/svg.d.ts.map +1 -1
  70. package/dist/html/svg.js +264 -59
  71. package/dist/html/svg.js.map +1 -1
  72. package/dist/html/widgets.d.ts.map +1 -1
  73. package/dist/html/widgets.js +57 -4
  74. package/dist/html/widgets.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/code-editor/node.ts +24 -5
  77. package/src/html/css/cascade.ts +697 -221
  78. package/src/html/css/color.ts +345 -18
  79. package/src/html/css/content.ts +173 -15
  80. package/src/html/css/parse.ts +60 -8
  81. package/src/html/css/shapes.ts +35 -1
  82. package/src/html/css/style.ts +122 -17
  83. package/src/html/css/ua.ts +15 -0
  84. package/src/html/css/values.ts +23 -9
  85. package/src/html/index.ts +7 -0
  86. package/src/html/layout/block.ts +149 -58
  87. package/src/html/layout/boxes.ts +171 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1843 -212
  90. package/src/html/layout/inline.ts +27 -7
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +151 -27
  93. package/src/html/paint.ts +692 -234
  94. package/src/html/resources.ts +13 -1
  95. package/src/html/svg.ts +299 -56
  96. package/src/html/widgets.ts +61 -4
@@ -11,15 +11,15 @@
11
11
  // inline pass ask "how wide is the line at this y" without knowing whose
12
12
  // formatting context it is inside.
13
13
  import { AUTO, gapOf, isPct, isTransparent, resolve, resolveOrNull, } from '../css/values.js';
14
- import { CONTAIN_INLINE_SIZE, CONTAIN_LAYOUT, CONTAIN_PAINT, CONTAIN_SIZE, scrolls, } from '../css/style.js';
14
+ import { CONTAIN_INLINE_SIZE, CONTAIN_LAYOUT, CONTAIN_PAINT, CONTAIN_SIZE, INLINE_BEFORE_ABSOLUTE, scrolls, } from '../css/style.js';
15
15
  import { about, linearOf, matrixOf, transformed } from '../css/transform.js';
16
16
  import { lineOf, spanToName } from './grid-lines.js';
17
17
  import { columnsOf, forgetColumns, layoutColumns, spansColumns, } from './multicol.js';
18
18
  import { BOX_RAISES, Box, CLAMPED, CLEARED_FROM, COLUMN_PIECES, CUT_BLOCKS, GRID_TRACKS, FIRST_LINE, INLINE_OFFSETS, MOVED_OFF_LINES, LINE_BOX_RAISES, SHIFTED_LINES, TEXT_SHIFTS, columned, isBlank, } from './boxes.js';
19
19
  import { FloatContext } from './floats.js';
20
- import { faceExtentOf, isButton, joinsBefore, layoutInline, lineHeightMultiplier, spaceBreaksOnly, strutOf, widestWord, } from './inline.js';
20
+ import { emptyLinePoint, faceExtentOf, isButton, joinsBefore, layoutInline, lineHeightMultiplier, spaceBreaksOnly, strutOf, widestWord, } from './inline.js';
21
21
  import { layoutFlex } from './flex.js';
22
- import { finishCaptions, layoutTable } from './table.js';
22
+ import { finishCaptions, givenHeight, layoutTable } from './table.js';
23
23
  import { collapseEdges } from './collapse.js';
24
24
  import { clipsFor, computePaintBounds, FIXED_BOXES, fixedToViewport, hoistNegative, OUT_OF_FLOW_REACH, stackLayers, } from '../paint.js';
25
25
  /** Lay the whole document out at a width. */
@@ -622,7 +622,16 @@ function layoutChildren(box, ctx, floats, contentTop, contentWidth, leading = 0)
622
622
  marked = null;
623
623
  }
624
624
  if (child.outOfFlow) {
625
- placeStatic(child, box, contentLeft, contentWidth, y + marginOf(pendingMargin));
625
+ const top = y + marginOf(pendingMargin);
626
+ if (INLINE_BEFORE_ABSOLUTE.has(child.style)) {
627
+ // one that was inline-level is on a line of its own, beside the
628
+ // floats there, where the line's alignment puts what it holds
629
+ const band = floats.bandAt(top, 1, contentLeft, contentLeft + contentWidth);
630
+ const at = emptyLinePoint(box.style, band.left, band.right);
631
+ placeStatic(child, box, at, 0, top);
632
+ }
633
+ else
634
+ placeStatic(child, box, contentLeft, contentWidth, top);
626
635
  ctx.positioned.push({
627
636
  box: child,
628
637
  containing: containingBlockFor(child) ?? box,
@@ -1327,38 +1336,61 @@ export const MIN_CONTENT_PROBE = 0;
1327
1336
  * a cell set to 10% measured no wider than its padding.
1328
1337
  */
1329
1338
  export function measureIntrinsicWidth(box, ctx, available, content = false) {
1330
- ctx.layoutSubtree(box, available);
1339
+ // not through `ctx.layoutSubtree`, which is this and one frame more on
1340
+ // the stack for every level of a document that measures what it holds
1341
+ // (`layoutTable`)
1342
+ layoutSubtree(box, ctx, available, undefined, true);
1331
1343
  const specified = box.style.width;
1332
1344
  if (!content && specified !== AUTO && Number.isFinite(box.width)) {
1333
1345
  return box.width;
1334
1346
  }
1335
- return intrinsicWidth(box) + box.horizontalExtra;
1347
+ return (intrinsicWidth(box, undefined, available === MIN_CONTENT_PROBE) +
1348
+ box.horizontalExtra);
1336
1349
  }
1337
1350
  /**
1338
1351
  * Lay a box and everything under it out at a width, at the origin — what
1339
- * `LayoutContext.layoutSubtree` hands to `flex.ts` and `table.ts`, and what
1340
- * the shrink-to-fit probe uses. A replaced box is sized rather than laid out,
1341
- * because there is nothing inside it to lay out.
1352
+ * `LayoutContext.layoutSubtree` hands to `flex.ts` and `css-grid.ts`, and
1353
+ * what the shrink-to-fit probe and a table's cells use. A replaced box is
1354
+ * sized rather than laid out, because there is nothing inside it to lay out.
1342
1355
  */
1343
- function layoutSubtree(box, ctx, width,
1356
+ export function layoutSubtree(box, ctx, width,
1344
1357
  /** Its containing block's width, where `width` is the box's own. */
1345
- containing = width) {
1346
- resolveEdges(box, Number.isFinite(containing) ? containing : 0);
1358
+ containing,
1359
+ /** Whether this lays it out to measure it (`measureIntrinsicWidth`). */
1360
+ measuring = false) {
1361
+ const base = containing ?? width;
1362
+ resolveEdges(box, Number.isFinite(base) ? base : 0);
1347
1363
  if (box.kind === 'replaced') {
1348
1364
  sizeReplaced(box, width);
1349
1365
  box.x = 0;
1350
1366
  box.y = 0;
1351
1367
  return;
1352
1368
  }
1353
- // within its limits, a percentage among them of its containing block's
1369
+ // Within its limits, a percentage among them of its containing block's
1354
1370
  // width and not of its own: a flex item `max-width: 50%` that the flex
1355
- // layout made half its row was laid out at a quarter of it. A table
1356
- // cell's is of nothing, and ignored: what a percentage limit does to a
1357
- // cell it does where its column is sized (CSS Tables 3, 3.8.2), and a
1358
- // cell is laid out in the width its columns came to.
1359
- const borderBox = Number.isFinite(width)
1360
- ? clampWidth(box, width, box.kind === 'table-cell' ? NaN : containing)
1361
- : Infinity;
1371
+ // layout made half its row was laid out at a quarter of it. An
1372
+ // intrinsic limit is among them, as it is in a block's flow: without it,
1373
+ // a flex or grid item `min-width: max-content` that its layout made as
1374
+ // wide as its content was laid out at a smaller `max-width`, where the
1375
+ // minimum wins (CSS Sizing 3, 3.1). Not where the box is laid out to be
1376
+ // measured, since that is what an intrinsic limit is measured from.
1377
+ //
1378
+ // A table cell laid out in its columns, which hand it its row's width,
1379
+ // is as wide as they are (CSS Tables 3, 3.10.2): its limits are theirs
1380
+ // to weigh (3.8.2), and have been. Clamped again, `td { max-width:
1381
+ // 50px }` in a column 200 wide set its text in 50 of it. Probed for
1382
+ // what it asks of them, at none, it keeps within its length limits as
1383
+ // any box does; a percentage one is of nothing and does not hold it.
1384
+ let borderBox = Infinity;
1385
+ if (Number.isFinite(width)) {
1386
+ if (box.kind !== 'table-cell') {
1387
+ borderBox = clampWidth(box, width, base, measuring ? undefined : ctx);
1388
+ }
1389
+ else if (containing !== undefined)
1390
+ borderBox = width;
1391
+ else
1392
+ borderBox = clampWidth(box, width, NaN);
1393
+ }
1362
1394
  layoutInternals(box, ctx, borderBox, 0, 0);
1363
1395
  }
1364
1396
  /**
@@ -1704,12 +1736,12 @@ function layoutMarker(box, marker, ctx) {
1704
1736
  const style = box.style;
1705
1737
  if (marker.image) {
1706
1738
  // an image: its bottom on the first line's baseline, as an inline
1707
- // image's is, and the gap a bullet has before the content
1739
+ // image's is, and its own gap before the content (`MARKER_IMAGE_GAP`)
1708
1740
  const first = firstLineIn(box);
1709
1741
  marker.y = first
1710
1742
  ? first.y + first.baseline - marker.image.height
1711
1743
  : box.contentY;
1712
- const gap = Math.round(style.fontSize * 0.4);
1744
+ const { gap } = marker.image;
1713
1745
  // at the start of the line, which is its right in a right-to-left item
1714
1746
  marker.x =
1715
1747
  style.direction === 'rtl'
@@ -1743,8 +1775,13 @@ function layoutMarker(box, marker, ctx) {
1743
1775
  });
1744
1776
  marker.layout = layout;
1745
1777
  // a space between it and the content — the one its style's suffix ends
1746
- // in, or its own text's — in its own face: a line measures without it
1747
- const gap = marker.flush && !/\s$/.test(marker.text) ? 0 : markerGap(fonts, face);
1778
+ // in, or its own text's — in its own face: a line measures without the
1779
+ // spaces it ends in, and with a no-break space, which `content: "1.\a0"`
1780
+ // writes and which was a second gap on top of its own
1781
+ const trailing = marker.flush
1782
+ ? (/[ \t\n\r\f]*$/.exec(marker.text)?.[0].length ?? 0)
1783
+ : 1;
1784
+ const gap = trailing ? trailing * markerGap(fonts, face) : 0;
1748
1785
  // The marker sits on the first line of the item's *content*, which is not
1749
1786
  // always the item's own: an `<li>` holding a paragraph, or one holding text
1750
1787
  // and a nested list, has its inline content in an anonymous block. Looking
@@ -1918,13 +1955,16 @@ export const FLEXED_HEIGHT = new WeakMap();
1918
1955
  */
1919
1956
  export const USED_HEIGHT = new WeakMap();
1920
1957
  /**
1921
- * A table a grid stretches across its area, whose `auto` width is then the
1922
- * one it is laid out at. `normal` stretches any grid item but a replaced
1923
- * one (CSS Grid 1, 6.2), a table among them, where in a block's flow a
1924
- * table's `auto` width is its columns' (CSS 2.1 17.5.2; Blink leaves tables
1925
- * out of the boxes a block stretches, `space_utils.cc`, and stretches them
1926
- * in a grid, `grid_item.cc`). Set by `css-grid.ts` for the item's own
1927
- * layout alone, and never while its widths are measured.
1958
+ * A table a grid or a flex layout gives its width, whose `auto` width is
1959
+ * then the one it is laid out at. `normal` stretches any grid item but a
1960
+ * replaced one across its area (CSS Grid 1, 6.2), and a flex item across a
1961
+ * column's line (CSS Flexbox 9.4, step 11), a table among them; and a flex
1962
+ * item along a row is the size the line flexed it to (9.7) — where in a
1963
+ * block's flow a table's `auto` width is its columns' (CSS 2.1 17.5.2;
1964
+ * Blink leaves tables out of the boxes a block stretches, `space_utils.cc`,
1965
+ * and stretches them in a grid, `grid_item.cc`, and a flex box). Set by
1966
+ * `css-grid.ts` and `flex.ts` for the item's own layout alone, and never
1967
+ * while its widths are measured.
1928
1968
  */
1929
1969
  export const STRETCHED_ACROSS = new WeakSet();
1930
1970
  /**
@@ -2092,6 +2132,14 @@ function finishHeight(box, contentHeight) {
2092
2132
  if (box.kind === 'table-cell') {
2093
2133
  CELL_CONTENT.set(box, contentHeight + box.verticalExtra);
2094
2134
  }
2135
+ // A table a flex or grid layout gave a height is as tall as that, or as
2136
+ // its rows where they need more, which `layoutTable` has shared it out
2137
+ // among — whatever height it has of its own, which the flex layout
2138
+ // started from (`layoutTable`)
2139
+ if (box.kind === 'table' && Number.isFinite(givenHeight(box))) {
2140
+ box.height = contentHeight + box.verticalExtra;
2141
+ return;
2142
+ }
2095
2143
  const set = specifiedHeight(box);
2096
2144
  // at least zero: a `calc()` may come to less
2097
2145
  const specified = set === null ? null : Math.max(0, set);
@@ -2146,7 +2194,9 @@ export function clampHeight(box, height) {
2146
2194
  out = Math.min(out, max + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra));
2147
2195
  if (min !== null)
2148
2196
  out = Math.max(out, min + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra));
2149
- return Math.max(0, out);
2197
+ // no shorter than its padding and borders, as `clampWidth` is no
2198
+ // narrower
2199
+ return Math.max(box.verticalExtra, out);
2150
2200
  }
2151
2201
  /** Place a block-level box horizontally, honouring `margin: auto`. */
2152
2202
  function placeBlock(box, contentLeft, y, containingWidth) {
@@ -2335,7 +2385,10 @@ room = containingWidth) {
2335
2385
  if (style.minWidthKeyword && ctx) {
2336
2386
  out = Math.max(out, keywordWidth(box, ctx, style.minWidthKeyword, containingWidth, room));
2337
2387
  }
2338
- return Math.max(0, out);
2388
+ // and no narrower than its padding and borders: a `border-box` length
2389
+ // less than them leaves its content box none wide, and not less (CSS
2390
+ // Sizing 3, 3.3)
2391
+ return Math.max(box.horizontalExtra, out);
2339
2392
  }
2340
2393
  /** An intrinsic `min-width` or `max-width` in the room a box's margins
2341
2394
  * leave it. Apart from `clampWidth`, which every block is sized through,
@@ -2365,21 +2418,25 @@ export function contentSizedWidth(box, ctx, size, available, percentBase) {
2365
2418
  // the content's sizes, whatever width the box has of its own: a
2366
2419
  // `min-width: max-content` beside a `width` is its content's widest,
2367
2420
  // and a probe of the box at no width answered the probe's width
2368
- let probed = false;
2369
2421
  if (box.intrinsicMaxContent < 0) {
2370
2422
  box.intrinsicMaxContent = measureIntrinsicWidth(box, ctx, Infinity, true);
2371
- probed = true;
2372
2423
  }
2373
2424
  if (size !== 'max-content' && box.intrinsicMinContent < 0) {
2374
2425
  box.intrinsicMinContent = measureIntrinsicWidth(box, ctx, MIN_CONTENT_PROBE, true);
2375
- probed = true;
2376
2426
  }
2377
- if (probed)
2378
- resolveEdges(box, percentBase);
2379
- const max = box.intrinsicMaxContent;
2427
+ // The probes resolved a percentage of the box's padding or border
2428
+ // against nothing, which is how the sizes are kept, whatever the width
2429
+ // around the box: what one comes to against `percentBase` goes on top.
2430
+ // Left out, `width: max-content; padding-left: 10%` was as wide as its
2431
+ // content, the padding inside it.
2432
+ resolveEdges(box, 0);
2433
+ const bare = box.horizontalExtra;
2434
+ resolveEdges(box, Number.isFinite(percentBase) ? percentBase : 0);
2435
+ const extra = box.horizontalExtra - bare;
2436
+ const max = box.intrinsicMaxContent + extra;
2380
2437
  if (size === 'max-content')
2381
2438
  return max;
2382
- const min = box.intrinsicMinContent;
2439
+ const min = box.intrinsicMinContent + extra;
2383
2440
  if (size === 'min-content')
2384
2441
  return min;
2385
2442
  return Math.min(max, Math.max(min, fitRoom(box, size, available, percentBase)));
@@ -2506,6 +2563,11 @@ const PREFERRED = new WeakMap();
2506
2563
  * or is indented, a block with widths of its own.
2507
2564
  */
2508
2565
  export function exactMinContent(box, fonts) {
2566
+ // a flex row's is its items' side by side, a grid's its columns', and a
2567
+ // replaced element has no words: read as its widest word, a row of two
2568
+ // in a flex column's item was as narrow as the wider of them
2569
+ if (box.kind !== 'block' && box.kind !== 'table-cell')
2570
+ return null;
2509
2571
  if (hasWidths(box.style) || contained(box, CONTAIN_WIDTH))
2510
2572
  return null;
2511
2573
  const inner = exactWords(box, fonts);
@@ -2643,8 +2705,13 @@ function wordBound(box, fonts, measured) {
2643
2705
  * `seen.cut` is set where a line it measured was cut short by
2644
2706
  * `text-overflow`, in a box with a width to cut it at: the line is wider
2645
2707
  * than it was drawn, and the answer is of what was drawn.
2708
+ *
2709
+ * `narrowest` says the box was laid out by the min-content probe
2710
+ * (`MIN_CONTENT_PROBE`), and the answer is its min-content width: the
2711
+ * probe lays a box out at no width, but one with a least width of its own
2712
+ * at that, and nothing in the layout tells the two apart.
2646
2713
  */
2647
- export function intrinsicWidth(box, seen) {
2714
+ export function intrinsicWidth(box, seen, narrowest = false) {
2648
2715
  // under size containment, as though it held nothing: the size
2649
2716
  // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
2650
2717
  if (contained(box, CONTAIN_WIDTH)) {
@@ -2677,10 +2744,16 @@ export function intrinsicWidth(box, seen) {
2677
2744
  }
2678
2745
  let widest = 0;
2679
2746
  // A row of flex items is as wide as all of them side by side, and gaps
2680
- // between them; any other box is as wide as the widest thing in it
2747
+ // between them; any other box is as wide as the widest thing in it. So
2748
+ // is a row that wraps, at its narrowest: each of its items may be a line
2749
+ // of its own, and it is as wide as the widest of them (CSS Flexbox
2750
+ // 9.9.1). Summed, a wrapping row of three 30px items made a
2751
+ // `width: min-content` box around it 90 wide, the items side by side,
2752
+ // where Chrome has 30, one under another
2681
2753
  const display = box.style.display;
2682
2754
  const row = (display === 'flex' || display === 'inline-flex') &&
2683
2755
  box.style.flexDirection.startsWith('row');
2756
+ const wraps = row && narrowest && box.style.flexWrap !== 'nowrap';
2684
2757
  let total = 0;
2685
2758
  let items = 0;
2686
2759
  const lines = box.lines;
@@ -2749,7 +2822,7 @@ export function intrinsicWidth(box, seen) {
2749
2822
  }
2750
2823
  }
2751
2824
  else {
2752
- let inner = intrinsicWidth(child, seen) + child.horizontalExtra;
2825
+ let inner = intrinsicWidth(child, seen, narrowest) + child.horizontalExtra;
2753
2826
  if (typeof style.maxWidth === 'number') {
2754
2827
  inner = Math.min(inner, style.maxWidth + contentExtra(child));
2755
2828
  }
@@ -2762,11 +2835,11 @@ export function intrinsicWidth(box, seen) {
2762
2835
  across = Math.max(across, contribution);
2763
2836
  continue;
2764
2837
  }
2765
- if (row) {
2838
+ if (row && !wraps) {
2766
2839
  total += contribution;
2767
2840
  items += 1;
2768
2841
  }
2769
- else if (!sideBySide)
2842
+ else if (wraps || !sideBySide)
2770
2843
  widest = Math.max(widest, contribution);
2771
2844
  else if (child.isFloat) {
2772
2845
  const clear = style.clear;
@@ -2886,7 +2959,17 @@ stretchRoom = containingWidth) {
2886
2959
  const ih = own.missing & 2 ? null : own.height;
2887
2960
  // `aspect-ratio` over its own, unless written `auto` and it has one
2888
2961
  const aspect = style.aspectRatio;
2889
- const ratio = aspect && !(aspect.auto && own.ratio > 0) ? aspect.ratio : own.ratio;
2962
+ const pages = aspect !== null && !(aspect.auto && own.ratio > 0);
2963
+ const ratio = pages ? aspect.ratio : own.ratio;
2964
+ // A ratio of the page's is of the box `box-sizing` names (CSS Sizing 4,
2965
+ // 5.1): one axis's content size to the other's goes through the border
2966
+ // box there. Written with `auto`, it is of the content box, as the
2967
+ // replaced element's own is, whichever it is.
2968
+ const outer = pages && !aspect.auto && style.boxSizing === 'border-box';
2969
+ const ox = outer ? box.horizontalExtra : 0;
2970
+ const oy = outer ? box.verticalExtra : 0;
2971
+ const widthOf = (h) => Math.max(0, (h + oy) * ratio - ox);
2972
+ const heightOf = (w) => Math.max(0, (w + ox) / ratio - oy);
2890
2973
  // the width a block would have here: what a ratio with no size fills, and
2891
2974
  // an `hr`, whose whole appearance is its border across the line
2892
2975
  const room = () => {
@@ -2900,18 +2983,18 @@ stretchRoom = containingWidth) {
2900
2983
  // which moves both axes together
2901
2984
  if (iw !== null) {
2902
2985
  w = iw;
2903
- h = ih ?? iw / ratio;
2986
+ h = ih ?? heightOf(iw);
2904
2987
  }
2905
2988
  else if (ih !== null) {
2906
2989
  h = ih;
2907
- w = ih * ratio;
2990
+ w = widthOf(ih);
2908
2991
  }
2909
2992
  else {
2910
2993
  // a ratio and no size: CSS 2.1 leaves it undefined and suggests the
2911
2994
  // width a block would have, which is what browsers do — or, where the
2912
2995
  // containing block waits on this box, the default object size
2913
2996
  w = Number.isFinite(containingWidth) ? room() : own.width;
2914
- h = w / ratio;
2997
+ h = heightOf(w);
2915
2998
  }
2916
2999
  [w, h] = constrained(w, h, minW, maxW, minH, maxH);
2917
3000
  }
@@ -2923,7 +3006,7 @@ stretchRoom = containingWidth) {
2923
3006
  if (width !== null)
2924
3007
  w = width;
2925
3008
  else if (usedHeight !== null && ratio > 0)
2926
- w = usedHeight * ratio;
3009
+ w = widthOf(usedHeight);
2927
3010
  else if (box.replaced === 'hr')
2928
3011
  w = room();
2929
3012
  else
@@ -2932,7 +3015,7 @@ stretchRoom = containingWidth) {
2932
3015
  if (usedHeight !== null)
2933
3016
  h = usedHeight;
2934
3017
  else if (ratio > 0)
2935
- h = clamp(w / ratio, minH, maxH);
3018
+ h = clamp(heightOf(w), minH, maxH);
2936
3019
  else
2937
3020
  h = clamp(own.height, minH, maxH);
2938
3021
  }
@@ -3213,9 +3296,15 @@ function layoutPositioned(box, containing, ctx) {
3213
3296
  let y;
3214
3297
  if (top !== null && bottom !== null) {
3215
3298
  const fill = Math.max(0, cbHeight - top - bottom - box.marginTop - box.marginBottom);
3216
- // — a box with a ratio has its height from its width
3299
+ // — a box with a ratio has its height from its width, and a table is
3300
+ // as tall as its rows, as it is as wide as its columns between two
3301
+ // offsets: `normal` stretches neither (Blink: "Replaced/tables don't
3302
+ // stretch in abspos", `out_of_flow_layout_part.cc`), and a table taken
3303
+ // to the height its offsets leave was that tall around rows that were
3304
+ // not
3217
3305
  const stretches = style.height === AUTO &&
3218
3306
  (box.kind !== 'replaced' || control) &&
3307
+ box.kind !== 'table' &&
3219
3308
  !boxRatio(box);
3220
3309
  // both offsets and no height: the box fills what they leave, its
3221
3310
  // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or