@react-x11/components 0.13.0 → 0.14.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 (134) hide show
  1. package/dist/html/controls.d.ts +29 -2
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +70 -2
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +42 -1
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +336 -16
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +18 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +27 -0
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/parse.d.ts +18 -4
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +66 -28
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/shapes.d.ts +44 -0
  18. package/dist/html/css/shapes.d.ts.map +1 -0
  19. package/dist/html/css/shapes.js +194 -0
  20. package/dist/html/css/shapes.js.map +1 -0
  21. package/dist/html/css/style.d.ts +128 -15
  22. package/dist/html/css/style.d.ts.map +1 -1
  23. package/dist/html/css/style.js +547 -144
  24. package/dist/html/css/style.js.map +1 -1
  25. package/dist/html/css/transform.d.ts +85 -0
  26. package/dist/html/css/transform.d.ts.map +1 -0
  27. package/dist/html/css/transform.js +425 -0
  28. package/dist/html/css/transform.js.map +1 -0
  29. package/dist/html/css/ua.d.ts.map +1 -1
  30. package/dist/html/css/ua.js +14 -2
  31. package/dist/html/css/ua.js.map +1 -1
  32. package/dist/html/css/values.d.ts +4 -0
  33. package/dist/html/css/values.d.ts.map +1 -1
  34. package/dist/html/css/values.js.map +1 -1
  35. package/dist/html/fonts.d.ts +55 -5
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +284 -23
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/layout/axes.d.ts +14 -0
  40. package/dist/html/layout/axes.d.ts.map +1 -0
  41. package/dist/html/layout/axes.js +28 -0
  42. package/dist/html/layout/axes.js.map +1 -0
  43. package/dist/html/layout/block.d.ts +104 -12
  44. package/dist/html/layout/block.d.ts.map +1 -1
  45. package/dist/html/layout/block.js +410 -39
  46. package/dist/html/layout/block.js.map +1 -1
  47. package/dist/html/layout/boxes.d.ts +72 -1
  48. package/dist/html/layout/boxes.d.ts.map +1 -1
  49. package/dist/html/layout/boxes.js +40 -2
  50. package/dist/html/layout/boxes.js.map +1 -1
  51. package/dist/html/layout/cache.d.ts +1 -1
  52. package/dist/html/layout/cache.d.ts.map +1 -1
  53. package/dist/html/layout/cache.js +6 -0
  54. package/dist/html/layout/cache.js.map +1 -1
  55. package/dist/html/layout/css-grid.d.ts.map +1 -1
  56. package/dist/html/layout/css-grid.js +144 -24
  57. package/dist/html/layout/css-grid.js.map +1 -1
  58. package/dist/html/layout/flex.d.ts.map +1 -1
  59. package/dist/html/layout/flex.js +585 -85
  60. package/dist/html/layout/flex.js.map +1 -1
  61. package/dist/html/layout/inline.d.ts +3 -0
  62. package/dist/html/layout/inline.d.ts.map +1 -1
  63. package/dist/html/layout/inline.js +144 -54
  64. package/dist/html/layout/inline.js.map +1 -1
  65. package/dist/html/layout/multicol.d.ts +42 -0
  66. package/dist/html/layout/multicol.d.ts.map +1 -0
  67. package/dist/html/layout/multicol.js +897 -0
  68. package/dist/html/layout/multicol.js.map +1 -0
  69. package/dist/html/layout/shaping.d.ts +7 -0
  70. package/dist/html/layout/shaping.d.ts.map +1 -1
  71. package/dist/html/layout/shaping.js +25 -20
  72. package/dist/html/layout/shaping.js.map +1 -1
  73. package/dist/html/layout/table.d.ts.map +1 -1
  74. package/dist/html/layout/table.js +97 -31
  75. package/dist/html/layout/table.js.map +1 -1
  76. package/dist/html/node.d.ts +4 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +319 -47
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +83 -5
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +1474 -217
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/svg.d.ts +39 -9
  85. package/dist/html/svg.d.ts.map +1 -1
  86. package/dist/html/svg.js +482 -37
  87. package/dist/html/svg.js.map +1 -1
  88. package/dist/html/widgets.js +87 -7
  89. package/dist/html/widgets.js.map +1 -1
  90. package/dist/html/woff2.d.ts +14 -0
  91. package/dist/html/woff2.d.ts.map +1 -0
  92. package/dist/html/woff2.js +608 -0
  93. package/dist/html/woff2.js.map +1 -0
  94. package/dist/maps/controller.d.ts +17 -1
  95. package/dist/maps/controller.d.ts.map +1 -1
  96. package/dist/maps/controller.js +27 -7
  97. package/dist/maps/controller.js.map +1 -1
  98. package/dist/richtext/node.d.ts +8 -1
  99. package/dist/richtext/node.d.ts.map +1 -1
  100. package/dist/richtext/node.js +5 -1
  101. package/dist/richtext/node.js.map +1 -1
  102. package/dist/richtext/runs.d.ts +25 -6
  103. package/dist/richtext/runs.d.ts.map +1 -1
  104. package/dist/richtext/runs.js +240 -24
  105. package/dist/richtext/runs.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/html/controls.ts +85 -2
  108. package/src/html/css/cascade.ts +366 -8
  109. package/src/html/css/color.ts +29 -0
  110. package/src/html/css/parse.ts +71 -31
  111. package/src/html/css/shapes.ts +239 -0
  112. package/src/html/css/style.ts +652 -147
  113. package/src/html/css/transform.ts +459 -0
  114. package/src/html/css/ua.ts +15 -2
  115. package/src/html/css/values.ts +4 -0
  116. package/src/html/fonts.ts +312 -29
  117. package/src/html/layout/axes.ts +62 -0
  118. package/src/html/layout/block.ts +466 -37
  119. package/src/html/layout/boxes.ts +95 -3
  120. package/src/html/layout/cache.ts +6 -0
  121. package/src/html/layout/css-grid.ts +169 -32
  122. package/src/html/layout/flex.ts +686 -79
  123. package/src/html/layout/inline.ts +149 -55
  124. package/src/html/layout/multicol.ts +977 -0
  125. package/src/html/layout/shaping.ts +27 -18
  126. package/src/html/layout/table.ts +105 -35
  127. package/src/html/node.ts +354 -48
  128. package/src/html/paint.ts +1804 -315
  129. package/src/html/svg.ts +573 -36
  130. package/src/html/widgets.ts +95 -9
  131. package/src/html/woff2.ts +612 -0
  132. package/src/maps/controller.ts +28 -7
  133. package/src/richtext/node.ts +13 -2
  134. package/src/richtext/runs.ts +313 -35
@@ -12,10 +12,12 @@
12
12
  // formatting context it is inside.
13
13
  import { AUTO, gapOf, isPct, isTransparent, resolve, resolveOrNull, } from '../css/values.js';
14
14
  import { CONTAIN_INLINE_SIZE, CONTAIN_LAYOUT, CONTAIN_PAINT, CONTAIN_SIZE, scrolls, } from '../css/style.js';
15
+ import { about, linearOf, matrixOf, transformed } from '../css/transform.js';
15
16
  import { lineOf, spanToName } from './grid-lines.js';
16
- import { BOX_RAISES, Box, CLAMPED, CLEARED_FROM, CUT_BLOCKS, GRID_TRACKS, FIRST_LINE, INLINE_OFFSETS, MOVED_OFF_LINES, LINE_BOX_RAISES, SHIFTED_LINES, TEXT_SHIFTS, isBlank, } from './boxes.js';
17
+ import { columnsOf, forgetColumns, layoutColumns, spansColumns, } from './multicol.js';
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';
17
19
  import { FloatContext } from './floats.js';
18
- import { faceExtentOf, joinsBefore, layoutInline, lineHeightMultiplier, spaceBreaksOnly, strutOf, widestWord, } from './inline.js';
20
+ import { faceExtentOf, isButton, joinsBefore, layoutInline, lineHeightMultiplier, spaceBreaksOnly, strutOf, widestWord, } from './inline.js';
19
21
  import { layoutFlex } from './flex.js';
20
22
  import { finishCaptions, layoutTable } from './table.js';
21
23
  import { collapseEdges } from './collapse.js';
@@ -27,7 +29,7 @@ export function layoutDocument(tree, fonts, viewportWidth, viewportHeight) {
27
29
  viewportWidth,
28
30
  viewportHeight,
29
31
  positioned: [],
30
- layoutSubtree: (box, width) => layoutSubtree(box, ctx, width),
32
+ layoutSubtree: (box, width, containing) => layoutSubtree(box, ctx, width, containing),
31
33
  firstLine: tree.firstLine,
32
34
  firstLineStyler: tree.firstLineStyler,
33
35
  nestedOutOfLine: tree.nestedOutOfLine,
@@ -1338,16 +1340,24 @@ export function measureIntrinsicWidth(box, ctx, available, content = false) {
1338
1340
  * the shrink-to-fit probe uses. A replaced box is sized rather than laid out,
1339
1341
  * because there is nothing inside it to lay out.
1340
1342
  */
1341
- function layoutSubtree(box, ctx, width) {
1342
- resolveEdges(box, Number.isFinite(width) ? width : 0);
1343
+ function layoutSubtree(box, ctx, width,
1344
+ /** Its containing block's width, where `width` is the box's own. */
1345
+ containing = width) {
1346
+ resolveEdges(box, Number.isFinite(containing) ? containing : 0);
1343
1347
  if (box.kind === 'replaced') {
1344
1348
  sizeReplaced(box, width);
1345
1349
  box.x = 0;
1346
1350
  box.y = 0;
1347
1351
  return;
1348
1352
  }
1353
+ // within its limits, a percentage among them of its containing block's
1354
+ // 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.
1349
1359
  const borderBox = Number.isFinite(width)
1350
- ? clampWidth(box, width, width)
1360
+ ? clampWidth(box, width, box.kind === 'table-cell' ? NaN : containing)
1351
1361
  : Infinity;
1352
1362
  layoutInternals(box, ctx, borderBox, 0, 0);
1353
1363
  }
@@ -1434,9 +1444,13 @@ function layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats) {
1434
1444
  finishCaptions(box);
1435
1445
  return;
1436
1446
  }
1447
+ // A multicol container's content is laid out as one column, all of it,
1448
+ // and then set in the columns there are (`layoutColumns`)
1449
+ const columns = columnsOf(box, contentWidth);
1450
+ const flowWidth = columns ? columns.width : contentWidth;
1437
1451
  const ownFloats = establishesBFC(box) || !outerFloats;
1438
1452
  const floats = ownFloats
1439
- ? new FloatContext(box.contentX, box.contentX + contentWidth)
1453
+ ? new FloatContext(box.contentX, box.contentX + flowWidth)
1440
1454
  : outerFloats;
1441
1455
  // the line a pass before gave a marker, which this one may not
1442
1456
  if (box.marker)
@@ -1452,7 +1466,18 @@ function layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats) {
1452
1466
  else
1453
1467
  MARKER_ASCENT.set(box, markerAscent(box, box.marker, ctx.fonts));
1454
1468
  }
1455
- const flow = layoutChildren(box, ctx, floats, box.contentY, contentWidth);
1469
+ // a column wide while its content is laid out, for what reads the box
1470
+ // it is in rather than the width it is handed
1471
+ // and about a box that spans them, in rows of columns (`layoutRows`)
1472
+ const rows = columns && columns.count > 1 && box.children.some(spansColumns)
1473
+ ? layoutRows(box, ctx, columns, contentWidth, borderBoxWidth)
1474
+ : null;
1475
+ if (columns)
1476
+ box.width = columns.width + box.horizontalExtra;
1477
+ const flow = rows === null
1478
+ ? layoutChildren(box, ctx, floats, box.contentY, flowWidth)
1479
+ : { height: rows, hanging: NO_MARGIN };
1480
+ box.width = borderBoxWidth;
1456
1481
  // A list item with a marker and no line holds one, the marker's, a line
1457
1482
  // of its own face tall: an empty `<li>` is a line tall in a browser, and
1458
1483
  // an inline-block around one sits on its marker's baseline.
@@ -1492,7 +1517,16 @@ function layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats) {
1492
1517
  const withFloats = ownFloats && !clamped
1493
1518
  ? Math.max(height, floats.bottom === -Infinity ? 0 : floats.bottom - box.contentY)
1494
1519
  : height;
1495
- finishHeight(box, withFloats);
1520
+ if (rows !== null)
1521
+ finishHeight(box, rows);
1522
+ else if (columns) {
1523
+ finishHeight(box, layoutColumns(box, columns, withFloats, columnLimit(box)));
1524
+ }
1525
+ else {
1526
+ if (box.style.columns)
1527
+ forgetColumns(box);
1528
+ finishHeight(box, withFloats);
1529
+ }
1496
1530
  // The margin that escaped through this box's bottom edge becomes part of
1497
1531
  // its own: the parent's flow loop reads `child.marginBottom` for the next
1498
1532
  // sibling's collapse, which is exactly where an escaped margin goes.
@@ -1507,6 +1541,76 @@ function layoutBox(box, ctx, borderBoxWidth, x, y, outerFloats) {
1507
1541
  box.bottomStrut = null;
1508
1542
  if (box.marker)
1509
1543
  layoutMarker(box, box.marker, ctx);
1544
+ if (isButtonBlock(box)) {
1545
+ BUTTON_CONTENT.set(box, { height: withFloats, down: 0 });
1546
+ centreButton(box);
1547
+ }
1548
+ }
1549
+ /**
1550
+ * A multicol container's content about the boxes that span its columns
1551
+ * (`column-span: all`, CSS Multi-column 1, 6): what comes before one is set
1552
+ * in columns of its own, balanced, the spanner under them across the whole
1553
+ * container, and what comes after in columns under that. A spanner is a
1554
+ * formatting context of its own and its margins collapse with no column
1555
+ * content's, only with the spanner's next to it. Answers the content's
1556
+ * height.
1557
+ */
1558
+ function layoutRows(box, ctx, columns, contentWidth, borderBoxWidth) {
1559
+ const all = box.children;
1560
+ const limit = columnLimit(box);
1561
+ const top = box.contentY;
1562
+ let y = top;
1563
+ /** The bottom margin of the spanner just before, which the next thing
1564
+ * comes under: all of it, or what the next spanner's does not cover. */
1565
+ let margin = null;
1566
+ let from = 0;
1567
+ try {
1568
+ for (let i = 0; i <= all.length; i += 1) {
1569
+ const child = all[i];
1570
+ if (child && !spansColumns(child))
1571
+ continue;
1572
+ const row = all.slice(from, i);
1573
+ from = i + 1;
1574
+ if (row.length) {
1575
+ const set = row.some((in_) => in_.kind !== 'text' && in_.kind !== 'break' && !in_.outOfFlow);
1576
+ if (set && margin !== null) {
1577
+ y += margin;
1578
+ margin = null;
1579
+ }
1580
+ // the row's content as a strip a column wide, and then in columns,
1581
+ // no taller than the container has left
1582
+ box.children = row;
1583
+ box.width = columns.width + box.horizontalExtra;
1584
+ const floats = new FloatContext(box.contentX, box.contentX + columns.width);
1585
+ const flow = layoutChildren(box, ctx, floats, y, columns.width);
1586
+ box.width = borderBoxWidth;
1587
+ const strip = Math.max(flow.height, floats.bottom === -Infinity ? 0 : floats.bottom - y);
1588
+ y += layoutColumns(box, columns, strip, Math.max(0, limit - (y - top)), y);
1589
+ }
1590
+ if (!child)
1591
+ break;
1592
+ resolveEdges(child, contentWidth);
1593
+ y +=
1594
+ margin === null ? child.marginTop : Math.max(margin, child.marginTop);
1595
+ layoutBlockLevel(child, ctx, new FloatContext(box.contentX, box.contentX + contentWidth), box.contentX, y, contentWidth);
1596
+ y = child.y + child.height;
1597
+ margin = child.marginBottom;
1598
+ }
1599
+ }
1600
+ finally {
1601
+ box.children = all;
1602
+ box.width = borderBoxWidth;
1603
+ }
1604
+ return y + (margin ?? 0) - top;
1605
+ }
1606
+ /** The tallest a multicol container's columns may be: its own height where
1607
+ * it has one, and its `max-height`; no limit where it has neither. */
1608
+ function columnLimit(box) {
1609
+ const height = box.height;
1610
+ finishHeight(box, specifiedHeight(box) === null ? Infinity : 0);
1611
+ const limit = box.height - box.verticalExtra;
1612
+ box.height = height;
1613
+ return Math.max(0, limit);
1510
1614
  }
1511
1615
  /**
1512
1616
  * The room a list item's marker takes about its first line's baseline,
@@ -1795,12 +1899,34 @@ function stretchHeight(box) {
1795
1899
  return Math.max(box.verticalExtra, base - top - bottom);
1796
1900
  }
1797
1901
  /**
1798
- * The content height a flex layout made definite for an item — a stretched
1799
- * item's, a flexed one's in a container of a definite height (CSS Flexbox
1800
- * 9.8) — which what is in it takes its percentages of. Set by `flex.ts`
1801
- * for the item's own layout alone.
1902
+ * The content height a flex or grid layout made definite for an item — a
1903
+ * stretched item's, a flexed one's in a container of a definite height (CSS
1904
+ * Flexbox 9.8), a grid item's stretched down its area (CSS Grid 1, 11.1) —
1905
+ * which what is in it takes its percentages of, and which an item that is
1906
+ * a flex or grid container itself lays its own items out in, and a table
1907
+ * shares out among its rows. Set by `flex.ts` and `css-grid.ts` for the
1908
+ * item's own layout alone.
1802
1909
  */
1803
1910
  export const FLEXED_HEIGHT = new WeakMap();
1911
+ /**
1912
+ * The content height a flex layout gave an item without making it definite:
1913
+ * flexed along a column that has no height of its own, which 9.8 leaves
1914
+ * indefinite. An item that is a flex or grid container lays its own items
1915
+ * out in it all the same — it is its used size (9.7) — as a table shares
1916
+ * it among its rows, and nothing in it takes a percentage of it. Set by
1917
+ * `flex.ts` for the item's own layout alone.
1918
+ */
1919
+ export const USED_HEIGHT = new WeakMap();
1920
+ /**
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.
1928
+ */
1929
+ export const STRETCHED_ACROSS = new WeakSet();
1804
1930
  /**
1805
1931
  * Hand a box's children the height their percentages resolve against —
1806
1932
  * through inline boxes, which are no containing block, to the inline-block
@@ -2071,7 +2197,7 @@ function placeBlock(box, contentLeft, y, containingWidth) {
2071
2197
  function blockWidth(box, containingWidth, percentBase = containingWidth, ctx) {
2072
2198
  const style = box.style;
2073
2199
  const available = containingWidth - box.marginLeft - box.marginRight;
2074
- if (style.width === AUTO) {
2200
+ if (style.width === AUTO || cyclicWidth(style, percentBase)) {
2075
2201
  // a width its height gives it through its ratio
2076
2202
  const fromRatio = ratioWidth(box);
2077
2203
  if (fromRatio !== null) {
@@ -2079,8 +2205,9 @@ function blockWidth(box, containingWidth, percentBase = containingWidth, ctx) {
2079
2205
  return ratioMinimum(box, clamped, percentBase, ctx);
2080
2206
  }
2081
2207
  const room = Math.max(0, available);
2082
- const width = style.widthKeyword && ctx
2083
- ? contentSizedWidth(box, ctx, style.widthKeyword, room, percentBase)
2208
+ const keyword = style.widthKeyword ?? (fitsContent(box) ? 'fit-content' : null);
2209
+ const width = keyword && ctx
2210
+ ? contentSizedWidth(box, ctx, keyword, room, percentBase)
2084
2211
  : room;
2085
2212
  return clampWidth(box, transferredWidth(box, width), percentBase, ctx, containingWidth);
2086
2213
  }
@@ -2091,6 +2218,41 @@ function blockWidth(box, containingWidth, percentBase = containingWidth, ctx) {
2091
2218
  : specified + box.horizontalExtra;
2092
2219
  return clampWidth(box, borderBox, percentBase, ctx, containingWidth);
2093
2220
  }
2221
+ /**
2222
+ * Whether a block-level box's `width: auto` is `fit-content` and not the
2223
+ * room its margins leave: a `<button>`'s, of which HTML's rendering section
2224
+ * says "If the computed value of 'inline-size' is 'auto', then the used
2225
+ * value is the fit-content inline size" (button layout, 15.5.3) — whatever
2226
+ * it is laid out as inside, a block, a flex box or a grid, as Blink leaves
2227
+ * it out of the boxes whose `auto` stretches
2228
+ * (`ShouldBlockContainerChildStretchAutoInlineSize`). Set `display: block`
2229
+ * to stand on a line of its own, a button was as wide as the line, where it
2230
+ * is as wide as its label. Not one a flex box, a grid or a pair of offsets
2231
+ * sizes, which stretch it still.
2232
+ *
2233
+ * Asked of every block: most are told from a button by the length of their
2234
+ * name.
2235
+ */
2236
+ function fitsContent(box) {
2237
+ return box.el !== null && box.el.name.length === 6 && isButton(box);
2238
+ }
2239
+ /**
2240
+ * Whether a box's `width` is a percentage of a width that is not there to
2241
+ * take it of: its containing block's, while that is being found from what
2242
+ * it holds — a float's, an inline-block's, a flex item's or a table cell's
2243
+ * content measured at its widest. The percentage is cyclic, and the box is
2244
+ * measured as though its width were `auto` (CSS Sizing 3, 5.2.1); it is a
2245
+ * share of the width that comes to once there is one.
2246
+ *
2247
+ * Taken as a share of nothing, the box was no width at all, and its content
2248
+ * wrapped at every word in it: a float around a `width: 100%` block was as
2249
+ * wide as the block's longest word, and one around a `width: 100%` flex box
2250
+ * of a label that clips — the branch button of a GitHub repository — as
2251
+ * wide as nothing, its label gone.
2252
+ */
2253
+ export function cyclicWidth(style, base) {
2254
+ return isPct(style.width) && !Number.isFinite(base);
2255
+ }
2094
2256
  /**
2095
2257
  * A width a box with a ratio and no height of its own works out for itself
2096
2258
  * within the least and greatest widths its least and greatest heights make
@@ -2251,7 +2413,7 @@ function shrinkToFitWidth(box, ctx, available,
2251
2413
  * absolute box's offset, or its static position. */
2252
2414
  offset = 0) {
2253
2415
  const style = box.style;
2254
- if (style.width !== AUTO) {
2416
+ if (style.width !== AUTO && !cyclicWidth(style, available)) {
2255
2417
  const specified = Math.max(0, resolve(style.width, available, 0));
2256
2418
  const borderBox = style.boxSizing === 'border-box'
2257
2419
  ? Math.max(specified, box.horizontalExtra)
@@ -2477,13 +2639,27 @@ function wordBound(box, fonts, measured) {
2477
2639
  * was `Infinity`), so its own width says nothing and only what it drew does.
2478
2640
  * Skipping the non-finite ones is what stops the probe from answering
2479
2641
  * `Infinity` for every box that contains a paragraph.
2642
+ *
2643
+ * `seen.cut` is set where a line it measured was cut short by
2644
+ * `text-overflow`, in a box with a width to cut it at: the line is wider
2645
+ * than it was drawn, and the answer is of what was drawn.
2480
2646
  */
2481
- export function intrinsicWidth(box) {
2647
+ export function intrinsicWidth(box, seen) {
2482
2648
  // under size containment, as though it held nothing: the size
2483
2649
  // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
2484
2650
  if (contained(box, CONTAIN_WIDTH)) {
2485
2651
  return box.style.containIntrinsicWidth ?? 0;
2486
2652
  }
2653
+ // a table is as wide as its layout made it: its columns, the spacing
2654
+ // either side of them and between, and its captions' least (`layoutTable`)
2655
+ // — at no width limit its widest, and at none its narrowest. What it
2656
+ // holds says less: its rows run from the first column to the last, the
2657
+ // spacing either side outside them, and measured by them a table in a
2658
+ // flex row, a grid, a float or an `inline-table` came to that much too
2659
+ // narrow, and its columns gave it up
2660
+ if (box.kind === 'table' && Number.isFinite(box.width)) {
2661
+ return Math.max(0, box.width - box.horizontalExtra);
2662
+ }
2487
2663
  // a grid laid out at no width limit has its columns at their widest, and
2488
2664
  // is as wide as they are — an item that runs past its column makes it no
2489
2665
  // wider — or, where it has none, as where its items end
@@ -2515,6 +2691,10 @@ export function intrinsicWidth(box) {
2515
2691
  widest = Math.max(widest, line.width + (line.floats ?? 0));
2516
2692
  }
2517
2693
  widest = Math.max(widest, box.floatRow);
2694
+ // a line `text-overflow` cut short is wider than this finds it
2695
+ if (seen && !seen.cut && box.style.textOverflow === 'ellipsis') {
2696
+ seen.cut = lines.some((line) => line.texts.some((text) => text.layout.truncated));
2697
+ }
2518
2698
  }
2519
2699
  // Floats among blocks stand side by side too, as many as come together,
2520
2700
  // where each block in flow is a line of its own; one with a formatting
@@ -2525,6 +2705,9 @@ export function intrinsicWidth(box) {
2525
2705
  const sideBySide = !Number.isFinite(box.width);
2526
2706
  let left = 0;
2527
2707
  let right = 0;
2708
+ /** In a multicol container, the widest box that spans its columns. */
2709
+ const spanning = box.style.columns !== 0 && box.kind === 'block';
2710
+ let across = 0;
2528
2711
  for (const child of box.children) {
2529
2712
  if (child.kind === 'text' || child.kind === 'break')
2530
2713
  continue;
@@ -2548,9 +2731,25 @@ export function intrinsicWidth(box) {
2548
2731
  // counts as `auto`, so its content decides.
2549
2732
  if (typeof style.width === 'number') {
2550
2733
  contribution = Math.max(own, min + margins);
2734
+ // And it is that width where the box was laid out at less. In a row
2735
+ // of flex items it was shrunk to fit the width this is measured at,
2736
+ // and what an item gives its flex box's own size is its width (CSS
2737
+ // Flexbox 9.9.3); under a percentage `max-width` it was cut short,
2738
+ // by a percentage of the size being worked out, which is none to
2739
+ // what a box contributes (5.2.1). But a replaced box's is of nothing
2740
+ // at its least and none at its most: as it was laid out.
2741
+ const cyclic = typeof style.maxWidth === 'object';
2742
+ if (cyclic ? child.kind !== 'replaced' : row) {
2743
+ let stated = style.width + contentExtra(child);
2744
+ if (typeof style.maxWidth === 'number') {
2745
+ stated = Math.min(stated, style.maxWidth + contentExtra(child));
2746
+ }
2747
+ stated = Math.max(stated, child.horizontalExtra, min);
2748
+ contribution = Math.max(contribution, stated + margins);
2749
+ }
2551
2750
  }
2552
2751
  else {
2553
- let inner = intrinsicWidth(child) + child.horizontalExtra;
2752
+ let inner = intrinsicWidth(child, seen) + child.horizontalExtra;
2554
2753
  if (typeof style.maxWidth === 'number') {
2555
2754
  inner = Math.min(inner, style.maxWidth + contentExtra(child));
2556
2755
  }
@@ -2558,6 +2757,11 @@ export function intrinsicWidth(box) {
2558
2757
  inner = min;
2559
2758
  contribution = Math.max(inner + margins, own);
2560
2759
  }
2760
+ if (spanning && spansColumns(child)) {
2761
+ // across the columns, and no part of what a column is as wide as
2762
+ across = Math.max(across, contribution);
2763
+ continue;
2764
+ }
2561
2765
  if (row) {
2562
2766
  total += contribution;
2563
2767
  items += 1;
@@ -2590,6 +2794,19 @@ export function intrinsicWidth(box) {
2590
2794
  const gap = gapOf(box.style.columnGap, NaN);
2591
2795
  widest = Math.max(widest, total + gap * (items - 1));
2592
2796
  }
2797
+ // A multicol container is as wide as its columns side by side, each as
2798
+ // wide as its content or as `column-width` where that is wider, and the
2799
+ // gaps between them (as Blink measures one,
2800
+ // `NGColumnLayoutAlgorithm::ComputeMinMaxSizes`)
2801
+ if (box.style.columns && box.kind === 'block') {
2802
+ const count = box.style.columnCount ?? 1;
2803
+ const gap = box.style.columnGapNormal
2804
+ ? box.style.fontSize
2805
+ : gapOf(box.style.columnGap, NaN);
2806
+ widest = Math.max(widest, box.style.columnWidth ?? 0);
2807
+ // and no narrower than a box that spans them
2808
+ widest = Math.max(widest * count + gap * (count - 1), across);
2809
+ }
2593
2810
  return widest;
2594
2811
  }
2595
2812
  /** What a box's own width leaves out of its border box: its padding and
@@ -2810,6 +3027,21 @@ function placeFloat(box, floats, y, containingLeft, containingWidth) {
2810
3027
  side: box.style.float === 'right' ? 'right' : 'left',
2811
3028
  });
2812
3029
  }
3030
+ /** Whether a box is a form control's: a replaced box for what draws it,
3031
+ * a widget, and an inline block for how it is sized. */
3032
+ function isControl(box) {
3033
+ switch (box.replaced) {
3034
+ case 'input':
3035
+ case 'textarea':
3036
+ case 'select':
3037
+ case 'button':
3038
+ case 'checkbox':
3039
+ case 'radio':
3040
+ return true;
3041
+ default:
3042
+ return false;
3043
+ }
3044
+ }
2813
3045
  /**
2814
3046
  * An absolutely positioned box, against its containing block: a box's
2815
3047
  * padding box, or an inline box's fragments (`inlineContainingBlock`). An
@@ -2905,9 +3137,23 @@ function layoutPositioned(box, containing, ctx) {
2905
3137
  style.minHeightKeyword === 'stretch') {
2906
3138
  STRETCH_TOP.set(box, top ?? (bottom === null && at ? at.from.y + at.y - cbY : 0));
2907
3139
  }
3140
+ // A form control is an inline block to CSS and no replaced element,
3141
+ // whatever draws it (HTML's rendering section): with both offsets and no
3142
+ // size of its own it fills what they leave, as any box does, where an
3143
+ // image keeps its own (10.3.8, 10.6.5). A page lays an invisible
3144
+ // `<select>` over a picker it draws, `position: absolute; inset: 0`, to
3145
+ // take the press, and it has to cover it.
3146
+ const control = isControl(box);
2908
3147
  if (box.kind === 'replaced') {
2909
3148
  sizeReplaced(box, cbWidth);
2910
- stretchReplaced(box, style.widthKeyword === 'stretch' ? width : null, cbWidth);
3149
+ stretchReplaced(box, style.widthKeyword === 'stretch' ||
3150
+ (control &&
3151
+ style.width === AUTO &&
3152
+ style.widthKeyword === null &&
3153
+ left !== null &&
3154
+ right !== null)
3155
+ ? width
3156
+ : null, cbWidth);
2911
3157
  }
2912
3158
  else
2913
3159
  layoutOwn(box, ctx, width);
@@ -2968,13 +3214,17 @@ function layoutPositioned(box, containing, ctx) {
2968
3214
  if (top !== null && bottom !== null) {
2969
3215
  const fill = Math.max(0, cbHeight - top - bottom - box.marginTop - box.marginBottom);
2970
3216
  // — a box with a ratio has its height from its width
2971
- const stretches = style.height === AUTO && box.kind !== 'replaced' && !boxRatio(box);
3217
+ const stretches = style.height === AUTO &&
3218
+ (box.kind !== 'replaced' || control) &&
3219
+ !boxRatio(box);
2972
3220
  // both offsets and no height: the box fills what they leave, its
2973
3221
  // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or
2974
3222
  // `max-height` moves that, which makes it a height like one set, and
2975
3223
  // the rules run again with it (10.7)
2976
- if (stretches)
3224
+ if (stretches) {
2977
3225
  box.height = clampHeight(box, fill);
3226
+ centreButton(box);
3227
+ }
2978
3228
  if (!stretches || box.height !== fill) {
2979
3229
  // and a height: the `auto` margins share the rest, and where none
2980
3230
  // is `auto`, `bottom` gives way (10.6.4, 10.6.5)
@@ -3367,11 +3617,87 @@ export function moveTo(box, x, y) {
3367
3617
  return;
3368
3618
  translate(box, x - box.x, y - box.y);
3369
3619
  }
3370
- /** Move what a box holds down, and not the box: a table cell's content
3371
- * sits where its `vertical-align` puts it, in a box that fills the row. */
3620
+ /**
3621
+ * Move what a box holds down, and not the box: a table cell's content
3622
+ * sits where its `vertical-align` puts it, in a box that fills the row, and
3623
+ * a button's in the middle of its own (`centreButton`).
3624
+ *
3625
+ * The static positions the box gave the absolutely positioned boxes in its
3626
+ * flow go down with the content: each is where its box "would have been in
3627
+ * the normal flow" (CSS 2.1 10.6.4), and the flow is what moved. They are
3628
+ * kept from the box's own corner, which stays, so that moving the boxes
3629
+ * alone left them behind: a badge with no offsets in a `vertical-align:
3630
+ * middle` cell stood at the cell's top, over an icon 22px further down.
3631
+ *
3632
+ * It adds to where the content stands, so it is asked for once for each
3633
+ * layout of the box, which puts the content and the static positions back
3634
+ * at the top. A table lays a cell out and moves its content in one pass
3635
+ * over its cells, however many times a pass lays the table out, and one
3636
+ * whose layout is kept (`kept`) is moved whole, the cell with it; a button
3637
+ * counts how far down its content stands (`centreButton`).
3638
+ */
3372
3639
  export function moveContent(box, dy) {
3640
+ if (!dy)
3641
+ return;
3373
3642
  translate(box, 0, dy);
3374
3643
  box.y -= dy;
3644
+ moveStatics(box, box, dy);
3645
+ }
3646
+ /** Whether a box is a `<button>`'s own, laid out as a block container: the
3647
+ * one HTML's button layout sets an anonymous button content box in. Not
3648
+ * the text in it, whose element is the button too; not a button that is a
3649
+ * flex box or a grid, which has no such box; nor an `<input type=button>`,
3650
+ * which is a widget of its label's size. */
3651
+ function isButtonBlock(box) {
3652
+ return box.kind === 'block' && box.el !== null && box.el.name === 'button';
3653
+ }
3654
+ /** How tall a `<button>`'s content came to, and how far down its content
3655
+ * box it stands for being centred in it (`centreButton`). */
3656
+ const BUTTON_CONTENT = new WeakMap();
3657
+ /**
3658
+ * Centre a `<button>`'s content down its content box, where the box is the
3659
+ * taller: a button's content is in an anonymous box of its own that is
3660
+ * "centered vertically" where it "does not overflow in the vertical axis"
3661
+ * (HTML 15.5.5, button layout), and is at the top where it does. Blink
3662
+ * moves a button's children down by half the room left, clamped at none
3663
+ * (`AlignBlockContent`); set at the top as any block's is, the two lines of
3664
+ * a 60px sidebar picker on nextjs.org sat 4px high in it.
3665
+ *
3666
+ * Asked for when the button is laid out, and again wherever its height is
3667
+ * given to it afterwards — a flex or a grid item stretched to its line, or
3668
+ * put back to the height it was measured at, and an absolute box between
3669
+ * two offsets: the content moves by the difference from where it was last
3670
+ * put. A button that is a flex box or a grid has no such box, and its
3671
+ * content is where its own alignment puts it.
3672
+ */
3673
+ export function centreButton(box) {
3674
+ if (!isButtonBlock(box))
3675
+ return;
3676
+ const content = BUTTON_CONTENT.get(box);
3677
+ if (content === undefined)
3678
+ return;
3679
+ const down = Math.max(0, (box.contentHeight - content.height) / 2);
3680
+ const by = down - content.down;
3681
+ if (by === 0 || !Number.isFinite(by))
3682
+ return;
3683
+ content.down = down;
3684
+ // and the static positions kept from its corner with it: a menu under a
3685
+ // button's label opens under the label, where the label went
3686
+ moveContent(box, by);
3687
+ }
3688
+ /** Move the static positions a block gave the absolutely positioned boxes
3689
+ * in its flow, at any depth of inline box, which are kept from its own
3690
+ * corner and not from its content's (`moveContent`). One from a box
3691
+ * inside it is kept from that box's corner, which moved with the rest. */
3692
+ function moveStatics(parent, block, dy) {
3693
+ for (const child of parent.children) {
3694
+ if (child.outOfFlow) {
3695
+ if (child.staticPosition?.from === block)
3696
+ child.staticPosition.y += dy;
3697
+ }
3698
+ else if (child.kind === 'inline')
3699
+ moveStatics(child, block, dy);
3700
+ }
3375
3701
  }
3376
3702
  function translate(box, dx, dy) {
3377
3703
  if (!dx && !dy)
@@ -3383,6 +3709,17 @@ function translate(box, dx, dy) {
3383
3709
  box.marker.x += dx;
3384
3710
  box.marker.y += dy;
3385
3711
  }
3712
+ // the pieces columns broke it in, which are kept beside it
3713
+ if (columned.any) {
3714
+ const pieces = COLUMN_PIECES.get(box);
3715
+ if (pieces) {
3716
+ for (const piece of pieces) {
3717
+ piece.x += dx;
3718
+ piece.y += dy;
3719
+ piece.wholeY += dy;
3720
+ }
3721
+ }
3722
+ }
3386
3723
  if (box.lines) {
3387
3724
  for (const line of box.lines) {
3388
3725
  line.x += dx;
@@ -3512,6 +3849,12 @@ export function establishesBFC(box) {
3512
3849
  return true;
3513
3850
  if (style.position === 'absolute' || style.position === 'fixed')
3514
3851
  return true;
3852
+ // a multicol container (CSS Multi-column 1, 2), and a box that spans
3853
+ // one's columns (6)
3854
+ if (style.columns && box.kind === 'block')
3855
+ return true;
3856
+ if (style.columnSpan && box.parent?.style.columns)
3857
+ return true;
3515
3858
  if (style.display === 'inline-block' ||
3516
3859
  style.display === 'flex' ||
3517
3860
  style.display === 'inline-flex') {
@@ -3522,11 +3865,20 @@ export function establishesBFC(box) {
3522
3865
  box.kind === 'table') {
3523
3866
  return true;
3524
3867
  }
3868
+ // a flex item, and a grid's: each an independent formatting context, which
3869
+ // the margins of what it holds stay inside (CSS Flexbox 1, 4; CSS Grid 1,
3870
+ // 6.1)
3871
+ if (box.parent?.kind === 'flex' && !box.outOfFlow)
3872
+ return true;
3525
3873
  // The root element establishes the document's formatting context. Here it
3526
3874
  // is a box below the synthetic initial containing block, so it is named:
3527
3875
  // without it, <html>'s own margin collapsed with <body>'s first block's.
3528
3876
  if (box.el?.name === 'html')
3529
3877
  return true;
3878
+ // a button's content is in a formatting context of its own, the
3879
+ // anonymous button content box's (HTML 15.5.5), whatever its `display`
3880
+ if (isButtonBlock(box))
3881
+ return true;
3530
3882
  return box.parent === null;
3531
3883
  }
3532
3884
  /** What a box's `position: relative` offset moves it by, applied after
@@ -3564,23 +3916,42 @@ export function applyRelativeOffsets(box, viewportWidth, viewportHeight) {
3564
3916
  for (const block of blocks)
3565
3917
  translate(block, dx, dy);
3566
3918
  }
3567
- /** Whether a box is transformed, which is what makes it a containing
3568
- * block and a layer as a positioned box is (CSS Transforms 1, 2). */
3569
- export function transformed(style) {
3570
- return style.translate !== null || style.transformTranslate !== null;
3571
- }
3572
- /** How far `translate` and a `transform` move a box: a percentage is of
3573
- * its own border box (CSS Transforms 1, 7). */
3919
+ // what makes a box a containing block and a layer, as a positioned box is,
3920
+ // is asked here by the layouts beside this one
3921
+ export { transformed };
3922
+ /** How far a box's transform moves it — `translate`, and the translation
3923
+ * its `transform` comes to with its turns and scales: a percentage is of
3924
+ * its own border box (CSS Transforms 1, 7). The rest of the transform is
3925
+ * paint's (`placedMatrix`). */
3574
3926
  function translationOf(box, style = box.style) {
3575
- let dx = 0;
3576
- let dy = 0;
3577
- for (const moved of [style.translate, style.transformTranslate]) {
3578
- if (!moved)
3579
- continue;
3580
- dx += resolve(moved[0], box.width, 0);
3581
- dy += resolve(moved[1], box.height, 0);
3927
+ if (!transformed(style))
3928
+ return [0, 0];
3929
+ const m = matrixOf(style, box.width, box.height);
3930
+ return [m[4], m[5]];
3931
+ }
3932
+ /**
3933
+ * The matrix a box is painted through, in the document's coordinates: what
3934
+ * its transform does besides move it — a turn, a scale, a skew — about its
3935
+ * `transform-origin`, where layout has put it (`applyRelativeOffsets`). Null
3936
+ * for a box that only moves, which is every box but a few, and for an
3937
+ * inline box, which no transform applies to (CSS Transforms 1, 2: a
3938
+ * transformable element is no inline box).
3939
+ */
3940
+ export function placedMatrix(box) {
3941
+ const style = box.style;
3942
+ if (style.transform === null &&
3943
+ style.rotate === null &&
3944
+ style.scale === null) {
3945
+ return null;
3582
3946
  }
3583
- return [dx, dy];
3947
+ if (box.kind === 'inline' || box.kind === 'text' || box.kind === 'break') {
3948
+ return null;
3949
+ }
3950
+ const linear = linearOf(style);
3951
+ if (!linear)
3952
+ return null;
3953
+ const origin = style.transformOrigin;
3954
+ return about(linear, box.x + resolve(origin[0], box.width, 0), box.y + resolve(origin[1], box.height, 0));
3584
3955
  }
3585
3956
  /**
3586
3957
  * Move a laid-out box, and everything in it, by how far its translation