@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
@@ -32,13 +32,23 @@ import {
32
32
  CONTAIN_SIZE,
33
33
  scrolls,
34
34
  } from '../css/style.js';
35
+ import { about, linearOf, matrixOf, transformed } from '../css/transform.js';
36
+ import type { Matrix } from '../css/transform.js';
35
37
  import { lineOf, spanToName } from './grid-lines.js';
36
38
  import type { GridLines } from './grid-lines.js';
39
+ import {
40
+ columnsOf,
41
+ forgetColumns,
42
+ layoutColumns,
43
+ spansColumns,
44
+ } from './multicol.js';
45
+ import type { Columns } from './multicol.js';
37
46
  import {
38
47
  BOX_RAISES,
39
48
  Box,
40
49
  CLAMPED,
41
50
  CLEARED_FROM,
51
+ COLUMN_PIECES,
42
52
  CUT_BLOCKS,
43
53
  GRID_TRACKS,
44
54
  FIRST_LINE,
@@ -47,6 +57,7 @@ import {
47
57
  LINE_BOX_RAISES,
48
58
  SHIFTED_LINES,
49
59
  TEXT_SHIFTS,
60
+ columned,
50
61
  isBlank,
51
62
  } from './boxes.js';
52
63
  import type {
@@ -61,6 +72,7 @@ import type {
61
72
  import { FloatContext } from './floats.js';
62
73
  import {
63
74
  faceExtentOf,
75
+ isButton,
64
76
  joinsBefore,
65
77
  layoutInline,
66
78
  lineHeightMultiplier,
@@ -114,8 +126,15 @@ export interface LayoutContext {
114
126
  * package's tree-shaking contract forbids exactly that (AGENTS.md, "no side
115
127
  * effects at import time anywhere else"), and a field is cheaper than the
116
128
  * exception would be.
129
+ *
130
+ * `containing` is the width of the box's containing block where `width`
131
+ * is the box's own, as a flex layout sized it: what the percentages in
132
+ * its padding, its margins and its width limits are of (CSS 2.1 8.3,
133
+ * 8.4, 10.4). Left out, `width` is both — a box probed at the room there
134
+ * is for it. For a table cell it is the width of its row, which its
135
+ * padding's percentages are of.
117
136
  */
118
- layoutSubtree(box: Box, width: number): void;
137
+ layoutSubtree(box: Box, width: number, containing?: number): void;
119
138
  /** How many flex boxes' Yoga passes are running, one inside the last's
120
139
  * measure (`layoutFlex`). */
121
140
  flexDepth?: number;
@@ -172,7 +191,8 @@ export function layoutDocument(
172
191
  viewportWidth,
173
192
  viewportHeight,
174
193
  positioned: [],
175
- layoutSubtree: (box, width) => layoutSubtree(box, ctx, width),
194
+ layoutSubtree: (box, width, containing) =>
195
+ layoutSubtree(box, ctx, width, containing),
176
196
  firstLine: tree.firstLine,
177
197
  firstLineStyler: tree.firstLineStyler,
178
198
  nestedOutOfLine: tree.nestedOutOfLine,
@@ -1664,16 +1684,28 @@ export function measureIntrinsicWidth(
1664
1684
  * the shrink-to-fit probe uses. A replaced box is sized rather than laid out,
1665
1685
  * because there is nothing inside it to lay out.
1666
1686
  */
1667
- function layoutSubtree(box: Box, ctx: LayoutContext, width: number): void {
1668
- resolveEdges(box, Number.isFinite(width) ? width : 0);
1687
+ function layoutSubtree(
1688
+ box: Box,
1689
+ ctx: LayoutContext,
1690
+ width: number,
1691
+ /** Its containing block's width, where `width` is the box's own. */
1692
+ containing = width,
1693
+ ): void {
1694
+ resolveEdges(box, Number.isFinite(containing) ? containing : 0);
1669
1695
  if (box.kind === 'replaced') {
1670
1696
  sizeReplaced(box, width);
1671
1697
  box.x = 0;
1672
1698
  box.y = 0;
1673
1699
  return;
1674
1700
  }
1701
+ // within its limits, a percentage among them of its containing block's
1702
+ // width and not of its own: a flex item `max-width: 50%` that the flex
1703
+ // layout made half its row was laid out at a quarter of it. A table
1704
+ // cell's is of nothing, and ignored: what a percentage limit does to a
1705
+ // cell it does where its column is sized (CSS Tables 3, 3.8.2), and a
1706
+ // cell is laid out in the width its columns came to.
1675
1707
  const borderBox = Number.isFinite(width)
1676
- ? clampWidth(box, width, width)
1708
+ ? clampWidth(box, width, box.kind === 'table-cell' ? NaN : containing)
1677
1709
  : Infinity;
1678
1710
  layoutInternals(box, ctx, borderBox, 0, 0);
1679
1711
  }
@@ -1787,9 +1819,13 @@ function layoutBox(
1787
1819
  return;
1788
1820
  }
1789
1821
 
1822
+ // A multicol container's content is laid out as one column, all of it,
1823
+ // and then set in the columns there are (`layoutColumns`)
1824
+ const columns = columnsOf(box, contentWidth);
1825
+ const flowWidth = columns ? columns.width : contentWidth;
1790
1826
  const ownFloats = establishesBFC(box) || !outerFloats;
1791
1827
  const floats = ownFloats
1792
- ? new FloatContext(box.contentX, box.contentX + contentWidth)
1828
+ ? new FloatContext(box.contentX, box.contentX + flowWidth)
1793
1829
  : outerFloats;
1794
1830
  // the line a pass before gave a marker, which this one may not
1795
1831
  if (box.marker) box.lines = null;
@@ -1800,7 +1836,19 @@ function layoutBox(
1800
1836
  if (box.style.listStylePosition === 'inside') MARKER_ASCENT.delete(box);
1801
1837
  else MARKER_ASCENT.set(box, markerAscent(box, box.marker, ctx.fonts));
1802
1838
  }
1803
- const flow = layoutChildren(box, ctx, floats, box.contentY, contentWidth);
1839
+ // a column wide while its content is laid out, for what reads the box
1840
+ // it is in rather than the width it is handed
1841
+ // and about a box that spans them, in rows of columns (`layoutRows`)
1842
+ const rows =
1843
+ columns && columns.count > 1 && box.children.some(spansColumns)
1844
+ ? layoutRows(box, ctx, columns, contentWidth, borderBoxWidth)
1845
+ : null;
1846
+ if (columns) box.width = columns.width + box.horizontalExtra;
1847
+ const flow =
1848
+ rows === null
1849
+ ? layoutChildren(box, ctx, floats, box.contentY, flowWidth)
1850
+ : { height: rows, hanging: NO_MARGIN };
1851
+ box.width = borderBoxWidth;
1804
1852
  // A list item with a marker and no line holds one, the marker's, a line
1805
1853
  // of its own face tall: an empty `<li>` is a line tall in a browser, and
1806
1854
  // an inline-block around one sits on its marker's baseline.
@@ -1844,7 +1892,16 @@ function layoutBox(
1844
1892
  floats.bottom === -Infinity ? 0 : floats.bottom - box.contentY,
1845
1893
  )
1846
1894
  : height;
1847
- finishHeight(box, withFloats);
1895
+ if (rows !== null) finishHeight(box, rows);
1896
+ else if (columns) {
1897
+ finishHeight(
1898
+ box,
1899
+ layoutColumns(box, columns, withFloats, columnLimit(box)),
1900
+ );
1901
+ } else {
1902
+ if (box.style.columns) forgetColumns(box);
1903
+ finishHeight(box, withFloats);
1904
+ }
1848
1905
  // The margin that escaped through this box's bottom edge becomes part of
1849
1906
  // its own: the parent's flow loop reads `child.marginBottom` for the next
1850
1907
  // sibling's collapse, which is exactly where an escaped margin goes.
@@ -1856,6 +1913,103 @@ function layoutBox(
1856
1913
  box.marginBottom = marginOf(bottom);
1857
1914
  } else box.bottomStrut = null;
1858
1915
  if (box.marker) layoutMarker(box, box.marker, ctx);
1916
+ if (isButtonBlock(box)) {
1917
+ BUTTON_CONTENT.set(box, { height: withFloats, down: 0 });
1918
+ centreButton(box);
1919
+ }
1920
+ }
1921
+
1922
+ /**
1923
+ * A multicol container's content about the boxes that span its columns
1924
+ * (`column-span: all`, CSS Multi-column 1, 6): what comes before one is set
1925
+ * in columns of its own, balanced, the spanner under them across the whole
1926
+ * container, and what comes after in columns under that. A spanner is a
1927
+ * formatting context of its own and its margins collapse with no column
1928
+ * content's, only with the spanner's next to it. Answers the content's
1929
+ * height.
1930
+ */
1931
+ function layoutRows(
1932
+ box: Box,
1933
+ ctx: LayoutContext,
1934
+ columns: Columns,
1935
+ contentWidth: number,
1936
+ borderBoxWidth: number,
1937
+ ): number {
1938
+ const all = box.children;
1939
+ const limit = columnLimit(box);
1940
+ const top = box.contentY;
1941
+ let y = top;
1942
+ /** The bottom margin of the spanner just before, which the next thing
1943
+ * comes under: all of it, or what the next spanner's does not cover. */
1944
+ let margin: number | null = null;
1945
+ let from = 0;
1946
+ try {
1947
+ for (let i = 0; i <= all.length; i += 1) {
1948
+ const child = all[i];
1949
+ if (child && !spansColumns(child)) continue;
1950
+ const row = all.slice(from, i);
1951
+ from = i + 1;
1952
+ if (row.length) {
1953
+ const set = row.some(
1954
+ (in_) =>
1955
+ in_.kind !== 'text' && in_.kind !== 'break' && !in_.outOfFlow,
1956
+ );
1957
+ if (set && margin !== null) {
1958
+ y += margin;
1959
+ margin = null;
1960
+ }
1961
+ // the row's content as a strip a column wide, and then in columns,
1962
+ // no taller than the container has left
1963
+ box.children = row;
1964
+ box.width = columns.width + box.horizontalExtra;
1965
+ const floats = new FloatContext(
1966
+ box.contentX,
1967
+ box.contentX + columns.width,
1968
+ );
1969
+ const flow = layoutChildren(box, ctx, floats, y, columns.width);
1970
+ box.width = borderBoxWidth;
1971
+ const strip = Math.max(
1972
+ flow.height,
1973
+ floats.bottom === -Infinity ? 0 : floats.bottom - y,
1974
+ );
1975
+ y += layoutColumns(
1976
+ box,
1977
+ columns,
1978
+ strip,
1979
+ Math.max(0, limit - (y - top)),
1980
+ y,
1981
+ );
1982
+ }
1983
+ if (!child) break;
1984
+ resolveEdges(child, contentWidth);
1985
+ y +=
1986
+ margin === null ? child.marginTop : Math.max(margin, child.marginTop);
1987
+ layoutBlockLevel(
1988
+ child,
1989
+ ctx,
1990
+ new FloatContext(box.contentX, box.contentX + contentWidth),
1991
+ box.contentX,
1992
+ y,
1993
+ contentWidth,
1994
+ );
1995
+ y = child.y + child.height;
1996
+ margin = child.marginBottom;
1997
+ }
1998
+ } finally {
1999
+ box.children = all;
2000
+ box.width = borderBoxWidth;
2001
+ }
2002
+ return y + (margin ?? 0) - top;
2003
+ }
2004
+
2005
+ /** The tallest a multicol container's columns may be: its own height where
2006
+ * it has one, and its `max-height`; no limit where it has neither. */
2007
+ function columnLimit(box: Box): number {
2008
+ const height = box.height;
2009
+ finishHeight(box, specifiedHeight(box) === null ? Infinity : 0);
2010
+ const limit = box.height - box.verticalExtra;
2011
+ box.height = height;
2012
+ return Math.max(0, limit);
1859
2013
  }
1860
2014
 
1861
2015
  /**
@@ -2166,13 +2320,37 @@ function stretchHeight(box: Box): number | null {
2166
2320
  }
2167
2321
 
2168
2322
  /**
2169
- * The content height a flex layout made definite for an item — a stretched
2170
- * item's, a flexed one's in a container of a definite height (CSS Flexbox
2171
- * 9.8) — which what is in it takes its percentages of. Set by `flex.ts`
2172
- * for the item's own layout alone.
2323
+ * The content height a flex or grid layout made definite for an item — a
2324
+ * stretched item's, a flexed one's in a container of a definite height (CSS
2325
+ * Flexbox 9.8), a grid item's stretched down its area (CSS Grid 1, 11.1) —
2326
+ * which what is in it takes its percentages of, and which an item that is
2327
+ * a flex or grid container itself lays its own items out in, and a table
2328
+ * shares out among its rows. Set by `flex.ts` and `css-grid.ts` for the
2329
+ * item's own layout alone.
2173
2330
  */
2174
2331
  export const FLEXED_HEIGHT = new WeakMap<Box, number>();
2175
2332
 
2333
+ /**
2334
+ * The content height a flex layout gave an item without making it definite:
2335
+ * flexed along a column that has no height of its own, which 9.8 leaves
2336
+ * indefinite. An item that is a flex or grid container lays its own items
2337
+ * out in it all the same — it is its used size (9.7) — as a table shares
2338
+ * it among its rows, and nothing in it takes a percentage of it. Set by
2339
+ * `flex.ts` for the item's own layout alone.
2340
+ */
2341
+ export const USED_HEIGHT = new WeakMap<Box, number>();
2342
+
2343
+ /**
2344
+ * A table a grid stretches across its area, whose `auto` width is then the
2345
+ * one it is laid out at. `normal` stretches any grid item but a replaced
2346
+ * one (CSS Grid 1, 6.2), a table among them, where in a block's flow a
2347
+ * table's `auto` width is its columns' (CSS 2.1 17.5.2; Blink leaves tables
2348
+ * out of the boxes a block stretches, `space_utils.cc`, and stretches them
2349
+ * in a grid, `grid_item.cc`). Set by `css-grid.ts` for the item's own
2350
+ * layout alone, and never while its widths are measured.
2351
+ */
2352
+ export const STRETCHED_ACROSS = new WeakSet<Box>();
2353
+
2176
2354
  /**
2177
2355
  * Hand a box's children the height their percentages resolve against —
2178
2356
  * through inline boxes, which are no containing block, to the inline-block
@@ -2488,7 +2666,7 @@ function blockWidth(
2488
2666
  ): number {
2489
2667
  const style = box.style;
2490
2668
  const available = containingWidth - box.marginLeft - box.marginRight;
2491
- if (style.width === AUTO) {
2669
+ if (style.width === AUTO || cyclicWidth(style, percentBase)) {
2492
2670
  // a width its height gives it through its ratio
2493
2671
  const fromRatio = ratioWidth(box);
2494
2672
  if (fromRatio !== null) {
@@ -2496,9 +2674,11 @@ function blockWidth(
2496
2674
  return ratioMinimum(box, clamped, percentBase, ctx);
2497
2675
  }
2498
2676
  const room = Math.max(0, available);
2677
+ const keyword =
2678
+ style.widthKeyword ?? (fitsContent(box) ? 'fit-content' : null);
2499
2679
  const width =
2500
- style.widthKeyword && ctx
2501
- ? contentSizedWidth(box, ctx, style.widthKeyword, room, percentBase)
2680
+ keyword && ctx
2681
+ ? contentSizedWidth(box, ctx, keyword, room, percentBase)
2502
2682
  : room;
2503
2683
  return clampWidth(
2504
2684
  box,
@@ -2517,6 +2697,43 @@ function blockWidth(
2517
2697
  return clampWidth(box, borderBox, percentBase, ctx, containingWidth);
2518
2698
  }
2519
2699
 
2700
+ /**
2701
+ * Whether a block-level box's `width: auto` is `fit-content` and not the
2702
+ * room its margins leave: a `<button>`'s, of which HTML's rendering section
2703
+ * says "If the computed value of 'inline-size' is 'auto', then the used
2704
+ * value is the fit-content inline size" (button layout, 15.5.3) — whatever
2705
+ * it is laid out as inside, a block, a flex box or a grid, as Blink leaves
2706
+ * it out of the boxes whose `auto` stretches
2707
+ * (`ShouldBlockContainerChildStretchAutoInlineSize`). Set `display: block`
2708
+ * to stand on a line of its own, a button was as wide as the line, where it
2709
+ * is as wide as its label. Not one a flex box, a grid or a pair of offsets
2710
+ * sizes, which stretch it still.
2711
+ *
2712
+ * Asked of every block: most are told from a button by the length of their
2713
+ * name.
2714
+ */
2715
+ function fitsContent(box: Box): boolean {
2716
+ return box.el !== null && box.el.name.length === 6 && isButton(box);
2717
+ }
2718
+
2719
+ /**
2720
+ * Whether a box's `width` is a percentage of a width that is not there to
2721
+ * take it of: its containing block's, while that is being found from what
2722
+ * it holds — a float's, an inline-block's, a flex item's or a table cell's
2723
+ * content measured at its widest. The percentage is cyclic, and the box is
2724
+ * measured as though its width were `auto` (CSS Sizing 3, 5.2.1); it is a
2725
+ * share of the width that comes to once there is one.
2726
+ *
2727
+ * Taken as a share of nothing, the box was no width at all, and its content
2728
+ * wrapped at every word in it: a float around a `width: 100%` block was as
2729
+ * wide as the block's longest word, and one around a `width: 100%` flex box
2730
+ * of a label that clips — the branch button of a GitHub repository — as
2731
+ * wide as nothing, its label gone.
2732
+ */
2733
+ export function cyclicWidth(style: ComputedStyle, base: number): boolean {
2734
+ return isPct(style.width) && !Number.isFinite(base);
2735
+ }
2736
+
2520
2737
  /**
2521
2738
  * A width a box with a ratio and no height of its own works out for itself
2522
2739
  * within the least and greatest widths its least and greatest heights make
@@ -2716,7 +2933,7 @@ function shrinkToFitWidth(
2716
2933
  offset = 0,
2717
2934
  ): number {
2718
2935
  const style = box.style;
2719
- if (style.width !== AUTO) {
2936
+ if (style.width !== AUTO && !cyclicWidth(style, available)) {
2720
2937
  const specified = Math.max(0, resolve(style.width, available, 0));
2721
2938
  const borderBox =
2722
2939
  style.boxSizing === 'border-box'
@@ -2951,13 +3168,27 @@ function wordBound(box: Box, fonts: FontsLike, measured: boolean): number {
2951
3168
  * was `Infinity`), so its own width says nothing and only what it drew does.
2952
3169
  * Skipping the non-finite ones is what stops the probe from answering
2953
3170
  * `Infinity` for every box that contains a paragraph.
3171
+ *
3172
+ * `seen.cut` is set where a line it measured was cut short by
3173
+ * `text-overflow`, in a box with a width to cut it at: the line is wider
3174
+ * than it was drawn, and the answer is of what was drawn.
2954
3175
  */
2955
- export function intrinsicWidth(box: Box): number {
3176
+ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
2956
3177
  // under size containment, as though it held nothing: the size
2957
3178
  // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
2958
3179
  if (contained(box, CONTAIN_WIDTH)) {
2959
3180
  return box.style.containIntrinsicWidth ?? 0;
2960
3181
  }
3182
+ // a table is as wide as its layout made it: its columns, the spacing
3183
+ // either side of them and between, and its captions' least (`layoutTable`)
3184
+ // — at no width limit its widest, and at none its narrowest. What it
3185
+ // holds says less: its rows run from the first column to the last, the
3186
+ // spacing either side outside them, and measured by them a table in a
3187
+ // flex row, a grid, a float or an `inline-table` came to that much too
3188
+ // narrow, and its columns gave it up
3189
+ if (box.kind === 'table' && Number.isFinite(box.width)) {
3190
+ return Math.max(0, box.width - box.horizontalExtra);
3191
+ }
2961
3192
  // a grid laid out at no width limit has its columns at their widest, and
2962
3193
  // is as wide as they are — an item that runs past its column makes it no
2963
3194
  // wider — or, where it has none, as where its items end
@@ -2991,6 +3222,12 @@ export function intrinsicWidth(box: Box): number {
2991
3222
  widest = Math.max(widest, line.width + (line.floats ?? 0));
2992
3223
  }
2993
3224
  widest = Math.max(widest, box.floatRow);
3225
+ // a line `text-overflow` cut short is wider than this finds it
3226
+ if (seen && !seen.cut && box.style.textOverflow === 'ellipsis') {
3227
+ seen.cut = lines.some((line) =>
3228
+ line.texts.some((text) => text.layout.truncated),
3229
+ );
3230
+ }
2994
3231
  }
2995
3232
  // Floats among blocks stand side by side too, as many as come together,
2996
3233
  // where each block in flow is a line of its own; one with a formatting
@@ -3001,6 +3238,9 @@ export function intrinsicWidth(box: Box): number {
3001
3238
  const sideBySide = !Number.isFinite(box.width);
3002
3239
  let left = 0;
3003
3240
  let right = 0;
3241
+ /** In a multicol container, the widest box that spans its columns. */
3242
+ const spanning = box.style.columns !== 0 && box.kind === 'block';
3243
+ let across = 0;
3004
3244
  for (const child of box.children) {
3005
3245
  if (child.kind === 'text' || child.kind === 'break') continue;
3006
3246
  if (child.outOfFlow) continue;
@@ -3022,14 +3262,35 @@ export function intrinsicWidth(box: Box): number {
3022
3262
  // counts as `auto`, so its content decides.
3023
3263
  if (typeof style.width === 'number') {
3024
3264
  contribution = Math.max(own, min + margins);
3265
+ // And it is that width where the box was laid out at less. In a row
3266
+ // of flex items it was shrunk to fit the width this is measured at,
3267
+ // and what an item gives its flex box's own size is its width (CSS
3268
+ // Flexbox 9.9.3); under a percentage `max-width` it was cut short,
3269
+ // by a percentage of the size being worked out, which is none to
3270
+ // what a box contributes (5.2.1). But a replaced box's is of nothing
3271
+ // at its least and none at its most: as it was laid out.
3272
+ const cyclic = typeof style.maxWidth === 'object';
3273
+ if (cyclic ? child.kind !== 'replaced' : row) {
3274
+ let stated = style.width + contentExtra(child);
3275
+ if (typeof style.maxWidth === 'number') {
3276
+ stated = Math.min(stated, style.maxWidth + contentExtra(child));
3277
+ }
3278
+ stated = Math.max(stated, child.horizontalExtra, min);
3279
+ contribution = Math.max(contribution, stated + margins);
3280
+ }
3025
3281
  } else {
3026
- let inner = intrinsicWidth(child) + child.horizontalExtra;
3282
+ let inner = intrinsicWidth(child, seen) + child.horizontalExtra;
3027
3283
  if (typeof style.maxWidth === 'number') {
3028
3284
  inner = Math.min(inner, style.maxWidth + contentExtra(child));
3029
3285
  }
3030
3286
  if (min > inner) inner = min;
3031
3287
  contribution = Math.max(inner + margins, own);
3032
3288
  }
3289
+ if (spanning && spansColumns(child)) {
3290
+ // across the columns, and no part of what a column is as wide as
3291
+ across = Math.max(across, contribution);
3292
+ continue;
3293
+ }
3033
3294
  if (row) {
3034
3295
  total += contribution;
3035
3296
  items += 1;
@@ -3055,6 +3316,19 @@ export function intrinsicWidth(box: Box): number {
3055
3316
  const gap = gapOf(box.style.columnGap, NaN);
3056
3317
  widest = Math.max(widest, total + gap * (items - 1));
3057
3318
  }
3319
+ // A multicol container is as wide as its columns side by side, each as
3320
+ // wide as its content or as `column-width` where that is wider, and the
3321
+ // gaps between them (as Blink measures one,
3322
+ // `NGColumnLayoutAlgorithm::ComputeMinMaxSizes`)
3323
+ if (box.style.columns && box.kind === 'block') {
3324
+ const count = box.style.columnCount ?? 1;
3325
+ const gap = box.style.columnGapNormal
3326
+ ? box.style.fontSize
3327
+ : gapOf(box.style.columnGap, NaN);
3328
+ widest = Math.max(widest, box.style.columnWidth ?? 0);
3329
+ // and no narrower than a box that spans them
3330
+ widest = Math.max(widest * count + gap * (count - 1), across);
3331
+ }
3058
3332
  return widest;
3059
3333
  }
3060
3334
 
@@ -3319,6 +3593,22 @@ function placeFloat(
3319
3593
  });
3320
3594
  }
3321
3595
 
3596
+ /** Whether a box is a form control's: a replaced box for what draws it,
3597
+ * a widget, and an inline block for how it is sized. */
3598
+ function isControl(box: Box): boolean {
3599
+ switch (box.replaced) {
3600
+ case 'input':
3601
+ case 'textarea':
3602
+ case 'select':
3603
+ case 'button':
3604
+ case 'checkbox':
3605
+ case 'radio':
3606
+ return true;
3607
+ default:
3608
+ return false;
3609
+ }
3610
+ }
3611
+
3322
3612
  /**
3323
3613
  * An absolutely positioned box, against its containing block: a box's
3324
3614
  * padding box, or an inline box's fragments (`inlineContainingBlock`). An
@@ -3440,11 +3730,25 @@ function layoutPositioned(box: Box, containing: Box, ctx: LayoutContext): void {
3440
3730
  top ?? (bottom === null && at ? at.from.y + at.y - cbY : 0),
3441
3731
  );
3442
3732
  }
3733
+ // A form control is an inline block to CSS and no replaced element,
3734
+ // whatever draws it (HTML's rendering section): with both offsets and no
3735
+ // size of its own it fills what they leave, as any box does, where an
3736
+ // image keeps its own (10.3.8, 10.6.5). A page lays an invisible
3737
+ // `<select>` over a picker it draws, `position: absolute; inset: 0`, to
3738
+ // take the press, and it has to cover it.
3739
+ const control = isControl(box);
3443
3740
  if (box.kind === 'replaced') {
3444
3741
  sizeReplaced(box, cbWidth);
3445
3742
  stretchReplaced(
3446
3743
  box,
3447
- style.widthKeyword === 'stretch' ? width : null,
3744
+ style.widthKeyword === 'stretch' ||
3745
+ (control &&
3746
+ style.width === AUTO &&
3747
+ style.widthKeyword === null &&
3748
+ left !== null &&
3749
+ right !== null)
3750
+ ? width
3751
+ : null,
3448
3752
  cbWidth,
3449
3753
  );
3450
3754
  } else layoutOwn(box, ctx, width);
@@ -3513,12 +3817,17 @@ function layoutPositioned(box: Box, containing: Box, ctx: LayoutContext): void {
3513
3817
  );
3514
3818
  // — a box with a ratio has its height from its width
3515
3819
  const stretches =
3516
- style.height === AUTO && box.kind !== 'replaced' && !boxRatio(box);
3820
+ style.height === AUTO &&
3821
+ (box.kind !== 'replaced' || control) &&
3822
+ !boxRatio(box);
3517
3823
  // both offsets and no height: the box fills what they leave, its
3518
3824
  // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or
3519
3825
  // `max-height` moves that, which makes it a height like one set, and
3520
3826
  // the rules run again with it (10.7)
3521
- if (stretches) box.height = clampHeight(box, fill);
3827
+ if (stretches) {
3828
+ box.height = clampHeight(box, fill);
3829
+ centreButton(box);
3830
+ }
3522
3831
  if (!stretches || box.height !== fill) {
3523
3832
  // and a height: the `auto` margins share the rest, and where none
3524
3833
  // is `auto`, `bottom` gives way (10.6.4, 10.6.5)
@@ -4026,11 +4335,84 @@ export function moveTo(box: Box, x: number, y: number): void {
4026
4335
  translate(box, x - box.x, y - box.y);
4027
4336
  }
4028
4337
 
4029
- /** Move what a box holds down, and not the box: a table cell's content
4030
- * sits where its `vertical-align` puts it, in a box that fills the row. */
4338
+ /**
4339
+ * Move what a box holds down, and not the box: a table cell's content
4340
+ * sits where its `vertical-align` puts it, in a box that fills the row, and
4341
+ * a button's in the middle of its own (`centreButton`).
4342
+ *
4343
+ * The static positions the box gave the absolutely positioned boxes in its
4344
+ * flow go down with the content: each is where its box "would have been in
4345
+ * the normal flow" (CSS 2.1 10.6.4), and the flow is what moved. They are
4346
+ * kept from the box's own corner, which stays, so that moving the boxes
4347
+ * alone left them behind: a badge with no offsets in a `vertical-align:
4348
+ * middle` cell stood at the cell's top, over an icon 22px further down.
4349
+ *
4350
+ * It adds to where the content stands, so it is asked for once for each
4351
+ * layout of the box, which puts the content and the static positions back
4352
+ * at the top. A table lays a cell out and moves its content in one pass
4353
+ * over its cells, however many times a pass lays the table out, and one
4354
+ * whose layout is kept (`kept`) is moved whole, the cell with it; a button
4355
+ * counts how far down its content stands (`centreButton`).
4356
+ */
4031
4357
  export function moveContent(box: Box, dy: number): void {
4358
+ if (!dy) return;
4032
4359
  translate(box, 0, dy);
4033
4360
  box.y -= dy;
4361
+ moveStatics(box, box, dy);
4362
+ }
4363
+
4364
+ /** Whether a box is a `<button>`'s own, laid out as a block container: the
4365
+ * one HTML's button layout sets an anonymous button content box in. Not
4366
+ * the text in it, whose element is the button too; not a button that is a
4367
+ * flex box or a grid, which has no such box; nor an `<input type=button>`,
4368
+ * which is a widget of its label's size. */
4369
+ function isButtonBlock(box: Box): boolean {
4370
+ return box.kind === 'block' && box.el !== null && box.el.name === 'button';
4371
+ }
4372
+
4373
+ /** How tall a `<button>`'s content came to, and how far down its content
4374
+ * box it stands for being centred in it (`centreButton`). */
4375
+ const BUTTON_CONTENT = new WeakMap<Box, { height: number; down: number }>();
4376
+
4377
+ /**
4378
+ * Centre a `<button>`'s content down its content box, where the box is the
4379
+ * taller: a button's content is in an anonymous box of its own that is
4380
+ * "centered vertically" where it "does not overflow in the vertical axis"
4381
+ * (HTML 15.5.5, button layout), and is at the top where it does. Blink
4382
+ * moves a button's children down by half the room left, clamped at none
4383
+ * (`AlignBlockContent`); set at the top as any block's is, the two lines of
4384
+ * a 60px sidebar picker on nextjs.org sat 4px high in it.
4385
+ *
4386
+ * Asked for when the button is laid out, and again wherever its height is
4387
+ * given to it afterwards — a flex or a grid item stretched to its line, or
4388
+ * put back to the height it was measured at, and an absolute box between
4389
+ * two offsets: the content moves by the difference from where it was last
4390
+ * put. A button that is a flex box or a grid has no such box, and its
4391
+ * content is where its own alignment puts it.
4392
+ */
4393
+ export function centreButton(box: Box): void {
4394
+ if (!isButtonBlock(box)) return;
4395
+ const content = BUTTON_CONTENT.get(box);
4396
+ if (content === undefined) return;
4397
+ const down = Math.max(0, (box.contentHeight - content.height) / 2);
4398
+ const by = down - content.down;
4399
+ if (by === 0 || !Number.isFinite(by)) return;
4400
+ content.down = down;
4401
+ // and the static positions kept from its corner with it: a menu under a
4402
+ // button's label opens under the label, where the label went
4403
+ moveContent(box, by);
4404
+ }
4405
+
4406
+ /** Move the static positions a block gave the absolutely positioned boxes
4407
+ * in its flow, at any depth of inline box, which are kept from its own
4408
+ * corner and not from its content's (`moveContent`). One from a box
4409
+ * inside it is kept from that box's corner, which moved with the rest. */
4410
+ function moveStatics(parent: Box, block: Box, dy: number): void {
4411
+ for (const child of parent.children) {
4412
+ if (child.outOfFlow) {
4413
+ if (child.staticPosition?.from === block) child.staticPosition.y += dy;
4414
+ } else if (child.kind === 'inline') moveStatics(child, block, dy);
4415
+ }
4034
4416
  }
4035
4417
 
4036
4418
  function translate(box: Box, dx: number, dy: number): void {
@@ -4042,6 +4424,17 @@ function translate(box: Box, dx: number, dy: number): void {
4042
4424
  box.marker.x += dx;
4043
4425
  box.marker.y += dy;
4044
4426
  }
4427
+ // the pieces columns broke it in, which are kept beside it
4428
+ if (columned.any) {
4429
+ const pieces = COLUMN_PIECES.get(box);
4430
+ if (pieces) {
4431
+ for (const piece of pieces) {
4432
+ piece.x += dx;
4433
+ piece.y += dy;
4434
+ piece.wholeY += dy;
4435
+ }
4436
+ }
4437
+ }
4045
4438
  if (box.lines) {
4046
4439
  for (const line of box.lines) {
4047
4440
  line.x += dx;
@@ -4166,6 +4559,10 @@ export function establishesBFC(box: Box): boolean {
4166
4559
  if (style.lineClamp !== null) return true;
4167
4560
  if (style.float !== 'none') return true;
4168
4561
  if (style.position === 'absolute' || style.position === 'fixed') return true;
4562
+ // a multicol container (CSS Multi-column 1, 2), and a box that spans
4563
+ // one's columns (6)
4564
+ if (style.columns && box.kind === 'block') return true;
4565
+ if (style.columnSpan && box.parent?.style.columns) return true;
4169
4566
  if (
4170
4567
  style.display === 'inline-block' ||
4171
4568
  style.display === 'flex' ||
@@ -4180,10 +4577,17 @@ export function establishesBFC(box: Box): boolean {
4180
4577
  ) {
4181
4578
  return true;
4182
4579
  }
4580
+ // a flex item, and a grid's: each an independent formatting context, which
4581
+ // the margins of what it holds stay inside (CSS Flexbox 1, 4; CSS Grid 1,
4582
+ // 6.1)
4583
+ if (box.parent?.kind === 'flex' && !box.outOfFlow) return true;
4183
4584
  // The root element establishes the document's formatting context. Here it
4184
4585
  // is a box below the synthetic initial containing block, so it is named:
4185
4586
  // without it, <html>'s own margin collapsed with <body>'s first block's.
4186
4587
  if (box.el?.name === 'html') return true;
4588
+ // a button's content is in a formatting context of its own, the
4589
+ // anonymous button content box's (HTML 15.5.5), whatever its `display`
4590
+ if (isButtonBlock(box)) return true;
4187
4591
  return box.parent === null;
4188
4592
  }
4189
4593
 
@@ -4224,23 +4628,48 @@ export function applyRelativeOffsets(
4224
4628
  if (blocks) for (const block of blocks) translate(block, dx, dy);
4225
4629
  }
4226
4630
 
4227
- /** Whether a box is transformed, which is what makes it a containing
4228
- * block and a layer as a positioned box is (CSS Transforms 1, 2). */
4229
- export function transformed(style: ComputedStyle): boolean {
4230
- return style.translate !== null || style.transformTranslate !== null;
4231
- }
4631
+ // what makes a box a containing block and a layer, as a positioned box is,
4632
+ // is asked here by the layouts beside this one
4633
+ export { transformed };
4232
4634
 
4233
- /** How far `translate` and a `transform` move a box: a percentage is of
4234
- * its own border box (CSS Transforms 1, 7). */
4635
+ /** How far a box's transform moves it — `translate`, and the translation
4636
+ * its `transform` comes to with its turns and scales: a percentage is of
4637
+ * its own border box (CSS Transforms 1, 7). The rest of the transform is
4638
+ * paint's (`placedMatrix`). */
4235
4639
  function translationOf(box: Box, style = box.style): [number, number] {
4236
- let dx = 0;
4237
- let dy = 0;
4238
- for (const moved of [style.translate, style.transformTranslate]) {
4239
- if (!moved) continue;
4240
- dx += resolve(moved[0], box.width, 0);
4241
- dy += resolve(moved[1], box.height, 0);
4640
+ if (!transformed(style)) return [0, 0];
4641
+ const m = matrixOf(style, box.width, box.height);
4642
+ return [m[4], m[5]];
4643
+ }
4644
+
4645
+ /**
4646
+ * The matrix a box is painted through, in the document's coordinates: what
4647
+ * its transform does besides move it — a turn, a scale, a skew — about its
4648
+ * `transform-origin`, where layout has put it (`applyRelativeOffsets`). Null
4649
+ * for a box that only moves, which is every box but a few, and for an
4650
+ * inline box, which no transform applies to (CSS Transforms 1, 2: a
4651
+ * transformable element is no inline box).
4652
+ */
4653
+ export function placedMatrix(box: Box): Matrix | null {
4654
+ const style = box.style;
4655
+ if (
4656
+ style.transform === null &&
4657
+ style.rotate === null &&
4658
+ style.scale === null
4659
+ ) {
4660
+ return null;
4242
4661
  }
4243
- return [dx, dy];
4662
+ if (box.kind === 'inline' || box.kind === 'text' || box.kind === 'break') {
4663
+ return null;
4664
+ }
4665
+ const linear = linearOf(style);
4666
+ if (!linear) return null;
4667
+ const origin = style.transformOrigin;
4668
+ return about(
4669
+ linear,
4670
+ box.x + resolve(origin[0], box.width, 0),
4671
+ box.y + resolve(origin[1], box.height, 0),
4672
+ );
4244
4673
  }
4245
4674
 
4246
4675
  /**