@react-x11/components 0.12.0 → 0.14.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 (150) hide show
  1. package/dist/html/controls.d.ts +41 -5
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +85 -57
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +134 -20
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +545 -126
  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 +47 -6
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +336 -30
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/style.d.ts +142 -15
  18. package/dist/html/css/style.d.ts.map +1 -1
  19. package/dist/html/css/style.js +563 -147
  20. package/dist/html/css/style.js.map +1 -1
  21. package/dist/html/css/transform.d.ts +85 -0
  22. package/dist/html/css/transform.d.ts.map +1 -0
  23. package/dist/html/css/transform.js +425 -0
  24. package/dist/html/css/transform.js.map +1 -0
  25. package/dist/html/css/ua.d.ts +11 -1
  26. package/dist/html/css/ua.d.ts.map +1 -1
  27. package/dist/html/css/ua.js +73 -9
  28. package/dist/html/css/ua.js.map +1 -1
  29. package/dist/html/css/values.d.ts +4 -0
  30. package/dist/html/css/values.d.ts.map +1 -1
  31. package/dist/html/css/values.js.map +1 -1
  32. package/dist/html/dom.d.ts.map +1 -1
  33. package/dist/html/dom.js +3 -1
  34. package/dist/html/dom.js.map +1 -1
  35. package/dist/html/fonts.d.ts +78 -4
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +373 -19
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/form.d.ts +200 -0
  40. package/dist/html/form.d.ts.map +1 -0
  41. package/dist/html/form.js +801 -0
  42. package/dist/html/form.js.map +1 -0
  43. package/dist/html/index.d.ts +12 -0
  44. package/dist/html/index.d.ts.map +1 -1
  45. package/dist/html/index.js +30 -301
  46. package/dist/html/index.js.map +1 -1
  47. package/dist/html/layout/axes.d.ts +14 -0
  48. package/dist/html/layout/axes.d.ts.map +1 -0
  49. package/dist/html/layout/axes.js +28 -0
  50. package/dist/html/layout/axes.js.map +1 -0
  51. package/dist/html/layout/block.d.ts +93 -12
  52. package/dist/html/layout/block.d.ts.map +1 -1
  53. package/dist/html/layout/block.js +393 -39
  54. package/dist/html/layout/block.js.map +1 -1
  55. package/dist/html/layout/boxes.d.ts +90 -2
  56. package/dist/html/layout/boxes.d.ts.map +1 -1
  57. package/dist/html/layout/boxes.js +74 -10
  58. package/dist/html/layout/boxes.js.map +1 -1
  59. package/dist/html/layout/cache.d.ts +1 -1
  60. package/dist/html/layout/cache.d.ts.map +1 -1
  61. package/dist/html/layout/cache.js +6 -0
  62. package/dist/html/layout/cache.js.map +1 -1
  63. package/dist/html/layout/css-grid.d.ts.map +1 -1
  64. package/dist/html/layout/css-grid.js +90 -18
  65. package/dist/html/layout/css-grid.js.map +1 -1
  66. package/dist/html/layout/flex.d.ts.map +1 -1
  67. package/dist/html/layout/flex.js +574 -84
  68. package/dist/html/layout/flex.js.map +1 -1
  69. package/dist/html/layout/inline.d.ts +3 -0
  70. package/dist/html/layout/inline.d.ts.map +1 -1
  71. package/dist/html/layout/inline.js +154 -54
  72. package/dist/html/layout/inline.js.map +1 -1
  73. package/dist/html/layout/multicol.d.ts +42 -0
  74. package/dist/html/layout/multicol.d.ts.map +1 -0
  75. package/dist/html/layout/multicol.js +897 -0
  76. package/dist/html/layout/multicol.js.map +1 -0
  77. package/dist/html/layout/shaping.d.ts +7 -0
  78. package/dist/html/layout/shaping.d.ts.map +1 -1
  79. package/dist/html/layout/shaping.js +25 -20
  80. package/dist/html/layout/shaping.js.map +1 -1
  81. package/dist/html/layout/table.d.ts.map +1 -1
  82. package/dist/html/layout/table.js +62 -18
  83. package/dist/html/layout/table.js.map +1 -1
  84. package/dist/html/node.d.ts +90 -15
  85. package/dist/html/node.d.ts.map +1 -1
  86. package/dist/html/node.js +771 -186
  87. package/dist/html/node.js.map +1 -1
  88. package/dist/html/paint.d.ts +101 -5
  89. package/dist/html/paint.d.ts.map +1 -1
  90. package/dist/html/paint.js +1502 -160
  91. package/dist/html/paint.js.map +1 -1
  92. package/dist/html/svg.d.ts +15 -2
  93. package/dist/html/svg.d.ts.map +1 -1
  94. package/dist/html/svg.js +249 -17
  95. package/dist/html/svg.js.map +1 -1
  96. package/dist/html/widgets.d.ts +29 -0
  97. package/dist/html/widgets.d.ts.map +1 -0
  98. package/dist/html/widgets.js +700 -0
  99. package/dist/html/widgets.js.map +1 -0
  100. package/dist/html/woff2.d.ts +14 -0
  101. package/dist/html/woff2.d.ts.map +1 -0
  102. package/dist/html/woff2.js +608 -0
  103. package/dist/html/woff2.js.map +1 -0
  104. package/dist/index.d.ts +1 -1
  105. package/dist/index.d.ts.map +1 -1
  106. package/dist/index.js.map +1 -1
  107. package/dist/maps/controller.d.ts +17 -1
  108. package/dist/maps/controller.d.ts.map +1 -1
  109. package/dist/maps/controller.js +27 -7
  110. package/dist/maps/controller.js.map +1 -1
  111. package/dist/richtext/node.d.ts +8 -1
  112. package/dist/richtext/node.d.ts.map +1 -1
  113. package/dist/richtext/node.js +5 -1
  114. package/dist/richtext/node.js.map +1 -1
  115. package/dist/richtext/runs.d.ts +25 -6
  116. package/dist/richtext/runs.d.ts.map +1 -1
  117. package/dist/richtext/runs.js +240 -24
  118. package/dist/richtext/runs.js.map +1 -1
  119. package/package.json +2 -2
  120. package/src/html/controls.ts +114 -51
  121. package/src/html/css/cascade.ts +614 -140
  122. package/src/html/css/color.ts +29 -0
  123. package/src/html/css/parse.ts +388 -33
  124. package/src/html/css/style.ts +688 -150
  125. package/src/html/css/transform.ts +459 -0
  126. package/src/html/css/ua.ts +77 -10
  127. package/src/html/css/values.ts +4 -0
  128. package/src/html/dom.ts +3 -1
  129. package/src/html/fonts.ts +417 -24
  130. package/src/html/form.ts +962 -0
  131. package/src/html/index.ts +49 -338
  132. package/src/html/layout/axes.ts +62 -0
  133. package/src/html/layout/block.ts +447 -37
  134. package/src/html/layout/boxes.ts +143 -14
  135. package/src/html/layout/cache.ts +6 -0
  136. package/src/html/layout/css-grid.ts +95 -22
  137. package/src/html/layout/flex.ts +675 -78
  138. package/src/html/layout/inline.ts +159 -55
  139. package/src/html/layout/multicol.ts +977 -0
  140. package/src/html/layout/shaping.ts +27 -18
  141. package/src/html/layout/table.ts +62 -19
  142. package/src/html/node.ts +798 -172
  143. package/src/html/paint.ts +1742 -241
  144. package/src/html/svg.ts +277 -19
  145. package/src/html/widgets.ts +821 -0
  146. package/src/html/woff2.ts +612 -0
  147. package/src/index.ts +1 -0
  148. package/src/maps/controller.ts +28 -7
  149. package/src/richtext/node.ts +13 -2
  150. package/src/richtext/runs.ts +313 -35
@@ -32,12 +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,
50
+ CLEARED_FROM,
51
+ COLUMN_PIECES,
41
52
  CUT_BLOCKS,
42
53
  GRID_TRACKS,
43
54
  FIRST_LINE,
@@ -46,6 +57,7 @@ import {
46
57
  LINE_BOX_RAISES,
47
58
  SHIFTED_LINES,
48
59
  TEXT_SHIFTS,
60
+ columned,
49
61
  isBlank,
50
62
  } from './boxes.js';
51
63
  import type {
@@ -60,6 +72,7 @@ import type {
60
72
  import { FloatContext } from './floats.js';
61
73
  import {
62
74
  faceExtentOf,
75
+ isButton,
63
76
  joinsBefore,
64
77
  layoutInline,
65
78
  lineHeightMultiplier,
@@ -113,8 +126,15 @@ export interface LayoutContext {
113
126
  * package's tree-shaking contract forbids exactly that (AGENTS.md, "no side
114
127
  * effects at import time anywhere else"), and a field is cheaper than the
115
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.
116
136
  */
117
- layoutSubtree(box: Box, width: number): void;
137
+ layoutSubtree(box: Box, width: number, containing?: number): void;
118
138
  /** How many flex boxes' Yoga passes are running, one inside the last's
119
139
  * measure (`layoutFlex`). */
120
140
  flexDepth?: number;
@@ -171,7 +191,8 @@ export function layoutDocument(
171
191
  viewportWidth,
172
192
  viewportHeight,
173
193
  positioned: [],
174
- layoutSubtree: (box, width) => layoutSubtree(box, ctx, width),
194
+ layoutSubtree: (box, width, containing) =>
195
+ layoutSubtree(box, ctx, width, containing),
175
196
  firstLine: tree.firstLine,
176
197
  firstLineStyler: tree.firstLineStyler,
177
198
  nestedOutOfLine: tree.nestedOutOfLine,
@@ -927,6 +948,8 @@ function layoutChildren(
927
948
  }
928
949
  first = false;
929
950
  const moved = childY !== at;
951
+ if (moved) CLEARED_FROM.set(child, at);
952
+ else CLEARED_FROM.delete(child);
930
953
  if (!moved && collapsesThrough(child)) {
931
954
  // nothing in it parts its margins: they and the ones either side of
932
955
  // it are one (CSS 2.1 8.3.1), still hanging for what comes next —
@@ -1661,16 +1684,28 @@ export function measureIntrinsicWidth(
1661
1684
  * the shrink-to-fit probe uses. A replaced box is sized rather than laid out,
1662
1685
  * because there is nothing inside it to lay out.
1663
1686
  */
1664
- function layoutSubtree(box: Box, ctx: LayoutContext, width: number): void {
1665
- 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);
1666
1695
  if (box.kind === 'replaced') {
1667
1696
  sizeReplaced(box, width);
1668
1697
  box.x = 0;
1669
1698
  box.y = 0;
1670
1699
  return;
1671
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.
1672
1707
  const borderBox = Number.isFinite(width)
1673
- ? clampWidth(box, width, width)
1708
+ ? clampWidth(box, width, box.kind === 'table-cell' ? NaN : containing)
1674
1709
  : Infinity;
1675
1710
  layoutInternals(box, ctx, borderBox, 0, 0);
1676
1711
  }
@@ -1784,9 +1819,13 @@ function layoutBox(
1784
1819
  return;
1785
1820
  }
1786
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;
1787
1826
  const ownFloats = establishesBFC(box) || !outerFloats;
1788
1827
  const floats = ownFloats
1789
- ? new FloatContext(box.contentX, box.contentX + contentWidth)
1828
+ ? new FloatContext(box.contentX, box.contentX + flowWidth)
1790
1829
  : outerFloats;
1791
1830
  // the line a pass before gave a marker, which this one may not
1792
1831
  if (box.marker) box.lines = null;
@@ -1797,7 +1836,19 @@ function layoutBox(
1797
1836
  if (box.style.listStylePosition === 'inside') MARKER_ASCENT.delete(box);
1798
1837
  else MARKER_ASCENT.set(box, markerAscent(box, box.marker, ctx.fonts));
1799
1838
  }
1800
- 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;
1801
1852
  // A list item with a marker and no line holds one, the marker's, a line
1802
1853
  // of its own face tall: an empty `<li>` is a line tall in a browser, and
1803
1854
  // an inline-block around one sits on its marker's baseline.
@@ -1841,7 +1892,16 @@ function layoutBox(
1841
1892
  floats.bottom === -Infinity ? 0 : floats.bottom - box.contentY,
1842
1893
  )
1843
1894
  : height;
1844
- 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
+ }
1845
1905
  // The margin that escaped through this box's bottom edge becomes part of
1846
1906
  // its own: the parent's flow loop reads `child.marginBottom` for the next
1847
1907
  // sibling's collapse, which is exactly where an escaped margin goes.
@@ -1853,6 +1913,103 @@ function layoutBox(
1853
1913
  box.marginBottom = marginOf(bottom);
1854
1914
  } else box.bottomStrut = null;
1855
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);
1856
2013
  }
1857
2014
 
1858
2015
  /**
@@ -2163,13 +2320,25 @@ function stretchHeight(box: Box): number | null {
2163
2320
  }
2164
2321
 
2165
2322
  /**
2166
- * The content height a flex layout made definite for an item — a stretched
2167
- * item's, a flexed one's in a container of a definite height (CSS Flexbox
2168
- * 9.8) — which what is in it takes its percentages of. Set by `flex.ts`
2169
- * 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. Set by
2328
+ * `flex.ts` and `css-grid.ts` for the item's own layout alone.
2170
2329
  */
2171
2330
  export const FLEXED_HEIGHT = new WeakMap<Box, number>();
2172
2331
 
2332
+ /**
2333
+ * The content height a flex layout gave an item without making it definite:
2334
+ * flexed along a column that has no height of its own, which 9.8 leaves
2335
+ * indefinite. An item that is a flex or grid container lays its own items
2336
+ * out in it all the same — it is its used size (9.7) — and nothing in it
2337
+ * takes a percentage of it. Set by `flex.ts` for the item's own layout
2338
+ * alone.
2339
+ */
2340
+ export const USED_HEIGHT = new WeakMap<Box, number>();
2341
+
2173
2342
  /**
2174
2343
  * Hand a box's children the height their percentages resolve against —
2175
2344
  * through inline boxes, which are no containing block, to the inline-block
@@ -2485,7 +2654,7 @@ function blockWidth(
2485
2654
  ): number {
2486
2655
  const style = box.style;
2487
2656
  const available = containingWidth - box.marginLeft - box.marginRight;
2488
- if (style.width === AUTO) {
2657
+ if (style.width === AUTO || cyclicWidth(style, percentBase)) {
2489
2658
  // a width its height gives it through its ratio
2490
2659
  const fromRatio = ratioWidth(box);
2491
2660
  if (fromRatio !== null) {
@@ -2493,9 +2662,11 @@ function blockWidth(
2493
2662
  return ratioMinimum(box, clamped, percentBase, ctx);
2494
2663
  }
2495
2664
  const room = Math.max(0, available);
2665
+ const keyword =
2666
+ style.widthKeyword ?? (fitsContent(box) ? 'fit-content' : null);
2496
2667
  const width =
2497
- style.widthKeyword && ctx
2498
- ? contentSizedWidth(box, ctx, style.widthKeyword, room, percentBase)
2668
+ keyword && ctx
2669
+ ? contentSizedWidth(box, ctx, keyword, room, percentBase)
2499
2670
  : room;
2500
2671
  return clampWidth(
2501
2672
  box,
@@ -2514,6 +2685,43 @@ function blockWidth(
2514
2685
  return clampWidth(box, borderBox, percentBase, ctx, containingWidth);
2515
2686
  }
2516
2687
 
2688
+ /**
2689
+ * Whether a block-level box's `width: auto` is `fit-content` and not the
2690
+ * room its margins leave: a `<button>`'s, of which HTML's rendering section
2691
+ * says "If the computed value of 'inline-size' is 'auto', then the used
2692
+ * value is the fit-content inline size" (button layout, 15.5.3) — whatever
2693
+ * it is laid out as inside, a block, a flex box or a grid, as Blink leaves
2694
+ * it out of the boxes whose `auto` stretches
2695
+ * (`ShouldBlockContainerChildStretchAutoInlineSize`). Set `display: block`
2696
+ * to stand on a line of its own, a button was as wide as the line, where it
2697
+ * is as wide as its label. Not one a flex box, a grid or a pair of offsets
2698
+ * sizes, which stretch it still.
2699
+ *
2700
+ * Asked of every block: most are told from a button by the length of their
2701
+ * name.
2702
+ */
2703
+ function fitsContent(box: Box): boolean {
2704
+ return box.el !== null && box.el.name.length === 6 && isButton(box);
2705
+ }
2706
+
2707
+ /**
2708
+ * Whether a box's `width` is a percentage of a width that is not there to
2709
+ * take it of: its containing block's, while that is being found from what
2710
+ * it holds — a float's, an inline-block's, a flex item's or a table cell's
2711
+ * content measured at its widest. The percentage is cyclic, and the box is
2712
+ * measured as though its width were `auto` (CSS Sizing 3, 5.2.1); it is a
2713
+ * share of the width that comes to once there is one.
2714
+ *
2715
+ * Taken as a share of nothing, the box was no width at all, and its content
2716
+ * wrapped at every word in it: a float around a `width: 100%` block was as
2717
+ * wide as the block's longest word, and one around a `width: 100%` flex box
2718
+ * of a label that clips — the branch button of a GitHub repository — as
2719
+ * wide as nothing, its label gone.
2720
+ */
2721
+ export function cyclicWidth(style: ComputedStyle, base: number): boolean {
2722
+ return isPct(style.width) && !Number.isFinite(base);
2723
+ }
2724
+
2517
2725
  /**
2518
2726
  * A width a box with a ratio and no height of its own works out for itself
2519
2727
  * within the least and greatest widths its least and greatest heights make
@@ -2713,7 +2921,7 @@ function shrinkToFitWidth(
2713
2921
  offset = 0,
2714
2922
  ): number {
2715
2923
  const style = box.style;
2716
- if (style.width !== AUTO) {
2924
+ if (style.width !== AUTO && !cyclicWidth(style, available)) {
2717
2925
  const specified = Math.max(0, resolve(style.width, available, 0));
2718
2926
  const borderBox =
2719
2927
  style.boxSizing === 'border-box'
@@ -2948,8 +3156,12 @@ function wordBound(box: Box, fonts: FontsLike, measured: boolean): number {
2948
3156
  * was `Infinity`), so its own width says nothing and only what it drew does.
2949
3157
  * Skipping the non-finite ones is what stops the probe from answering
2950
3158
  * `Infinity` for every box that contains a paragraph.
3159
+ *
3160
+ * `seen.cut` is set where a line it measured was cut short by
3161
+ * `text-overflow`, in a box with a width to cut it at: the line is wider
3162
+ * than it was drawn, and the answer is of what was drawn.
2951
3163
  */
2952
- export function intrinsicWidth(box: Box): number {
3164
+ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
2953
3165
  // under size containment, as though it held nothing: the size
2954
3166
  // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
2955
3167
  if (contained(box, CONTAIN_WIDTH)) {
@@ -2988,6 +3200,12 @@ export function intrinsicWidth(box: Box): number {
2988
3200
  widest = Math.max(widest, line.width + (line.floats ?? 0));
2989
3201
  }
2990
3202
  widest = Math.max(widest, box.floatRow);
3203
+ // a line `text-overflow` cut short is wider than this finds it
3204
+ if (seen && !seen.cut && box.style.textOverflow === 'ellipsis') {
3205
+ seen.cut = lines.some((line) =>
3206
+ line.texts.some((text) => text.layout.truncated),
3207
+ );
3208
+ }
2991
3209
  }
2992
3210
  // Floats among blocks stand side by side too, as many as come together,
2993
3211
  // where each block in flow is a line of its own; one with a formatting
@@ -2998,6 +3216,9 @@ export function intrinsicWidth(box: Box): number {
2998
3216
  const sideBySide = !Number.isFinite(box.width);
2999
3217
  let left = 0;
3000
3218
  let right = 0;
3219
+ /** In a multicol container, the widest box that spans its columns. */
3220
+ const spanning = box.style.columns !== 0 && box.kind === 'block';
3221
+ let across = 0;
3001
3222
  for (const child of box.children) {
3002
3223
  if (child.kind === 'text' || child.kind === 'break') continue;
3003
3224
  if (child.outOfFlow) continue;
@@ -3019,14 +3240,35 @@ export function intrinsicWidth(box: Box): number {
3019
3240
  // counts as `auto`, so its content decides.
3020
3241
  if (typeof style.width === 'number') {
3021
3242
  contribution = Math.max(own, min + margins);
3243
+ // And it is that width where the box was laid out at less. In a row
3244
+ // of flex items it was shrunk to fit the width this is measured at,
3245
+ // and what an item gives its flex box's own size is its width (CSS
3246
+ // Flexbox 9.9.3); under a percentage `max-width` it was cut short,
3247
+ // by a percentage of the size being worked out, which is none to
3248
+ // what a box contributes (5.2.1). But a replaced box's is of nothing
3249
+ // at its least and none at its most: as it was laid out.
3250
+ const cyclic = typeof style.maxWidth === 'object';
3251
+ if (cyclic ? child.kind !== 'replaced' : row) {
3252
+ let stated = style.width + contentExtra(child);
3253
+ if (typeof style.maxWidth === 'number') {
3254
+ stated = Math.min(stated, style.maxWidth + contentExtra(child));
3255
+ }
3256
+ stated = Math.max(stated, child.horizontalExtra, min);
3257
+ contribution = Math.max(contribution, stated + margins);
3258
+ }
3022
3259
  } else {
3023
- let inner = intrinsicWidth(child) + child.horizontalExtra;
3260
+ let inner = intrinsicWidth(child, seen) + child.horizontalExtra;
3024
3261
  if (typeof style.maxWidth === 'number') {
3025
3262
  inner = Math.min(inner, style.maxWidth + contentExtra(child));
3026
3263
  }
3027
3264
  if (min > inner) inner = min;
3028
3265
  contribution = Math.max(inner + margins, own);
3029
3266
  }
3267
+ if (spanning && spansColumns(child)) {
3268
+ // across the columns, and no part of what a column is as wide as
3269
+ across = Math.max(across, contribution);
3270
+ continue;
3271
+ }
3030
3272
  if (row) {
3031
3273
  total += contribution;
3032
3274
  items += 1;
@@ -3052,6 +3294,19 @@ export function intrinsicWidth(box: Box): number {
3052
3294
  const gap = gapOf(box.style.columnGap, NaN);
3053
3295
  widest = Math.max(widest, total + gap * (items - 1));
3054
3296
  }
3297
+ // A multicol container is as wide as its columns side by side, each as
3298
+ // wide as its content or as `column-width` where that is wider, and the
3299
+ // gaps between them (as Blink measures one,
3300
+ // `NGColumnLayoutAlgorithm::ComputeMinMaxSizes`)
3301
+ if (box.style.columns && box.kind === 'block') {
3302
+ const count = box.style.columnCount ?? 1;
3303
+ const gap = box.style.columnGapNormal
3304
+ ? box.style.fontSize
3305
+ : gapOf(box.style.columnGap, NaN);
3306
+ widest = Math.max(widest, box.style.columnWidth ?? 0);
3307
+ // and no narrower than a box that spans them
3308
+ widest = Math.max(widest * count + gap * (count - 1), across);
3309
+ }
3055
3310
  return widest;
3056
3311
  }
3057
3312
 
@@ -3316,6 +3571,22 @@ function placeFloat(
3316
3571
  });
3317
3572
  }
3318
3573
 
3574
+ /** Whether a box is a form control's: a replaced box for what draws it,
3575
+ * a widget, and an inline block for how it is sized. */
3576
+ function isControl(box: Box): boolean {
3577
+ switch (box.replaced) {
3578
+ case 'input':
3579
+ case 'textarea':
3580
+ case 'select':
3581
+ case 'button':
3582
+ case 'checkbox':
3583
+ case 'radio':
3584
+ return true;
3585
+ default:
3586
+ return false;
3587
+ }
3588
+ }
3589
+
3319
3590
  /**
3320
3591
  * An absolutely positioned box, against its containing block: a box's
3321
3592
  * padding box, or an inline box's fragments (`inlineContainingBlock`). An
@@ -3437,11 +3708,25 @@ function layoutPositioned(box: Box, containing: Box, ctx: LayoutContext): void {
3437
3708
  top ?? (bottom === null && at ? at.from.y + at.y - cbY : 0),
3438
3709
  );
3439
3710
  }
3711
+ // A form control is an inline block to CSS and no replaced element,
3712
+ // whatever draws it (HTML's rendering section): with both offsets and no
3713
+ // size of its own it fills what they leave, as any box does, where an
3714
+ // image keeps its own (10.3.8, 10.6.5). A page lays an invisible
3715
+ // `<select>` over a picker it draws, `position: absolute; inset: 0`, to
3716
+ // take the press, and it has to cover it.
3717
+ const control = isControl(box);
3440
3718
  if (box.kind === 'replaced') {
3441
3719
  sizeReplaced(box, cbWidth);
3442
3720
  stretchReplaced(
3443
3721
  box,
3444
- style.widthKeyword === 'stretch' ? width : null,
3722
+ style.widthKeyword === 'stretch' ||
3723
+ (control &&
3724
+ style.width === AUTO &&
3725
+ style.widthKeyword === null &&
3726
+ left !== null &&
3727
+ right !== null)
3728
+ ? width
3729
+ : null,
3445
3730
  cbWidth,
3446
3731
  );
3447
3732
  } else layoutOwn(box, ctx, width);
@@ -3510,12 +3795,17 @@ function layoutPositioned(box: Box, containing: Box, ctx: LayoutContext): void {
3510
3795
  );
3511
3796
  // — a box with a ratio has its height from its width
3512
3797
  const stretches =
3513
- style.height === AUTO && box.kind !== 'replaced' && !boxRatio(box);
3798
+ style.height === AUTO &&
3799
+ (box.kind !== 'replaced' || control) &&
3800
+ !boxRatio(box);
3514
3801
  // both offsets and no height: the box fills what they leave, its
3515
3802
  // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or
3516
3803
  // `max-height` moves that, which makes it a height like one set, and
3517
3804
  // the rules run again with it (10.7)
3518
- if (stretches) box.height = clampHeight(box, fill);
3805
+ if (stretches) {
3806
+ box.height = clampHeight(box, fill);
3807
+ centreButton(box);
3808
+ }
3519
3809
  if (!stretches || box.height !== fill) {
3520
3810
  // and a height: the `auto` margins share the rest, and where none
3521
3811
  // is `auto`, `bottom` gives way (10.6.4, 10.6.5)
@@ -4023,11 +4313,84 @@ export function moveTo(box: Box, x: number, y: number): void {
4023
4313
  translate(box, x - box.x, y - box.y);
4024
4314
  }
4025
4315
 
4026
- /** Move what a box holds down, and not the box: a table cell's content
4027
- * sits where its `vertical-align` puts it, in a box that fills the row. */
4316
+ /**
4317
+ * Move what a box holds down, and not the box: a table cell's content
4318
+ * sits where its `vertical-align` puts it, in a box that fills the row, and
4319
+ * a button's in the middle of its own (`centreButton`).
4320
+ *
4321
+ * The static positions the box gave the absolutely positioned boxes in its
4322
+ * flow go down with the content: each is where its box "would have been in
4323
+ * the normal flow" (CSS 2.1 10.6.4), and the flow is what moved. They are
4324
+ * kept from the box's own corner, which stays, so that moving the boxes
4325
+ * alone left them behind: a badge with no offsets in a `vertical-align:
4326
+ * middle` cell stood at the cell's top, over an icon 22px further down.
4327
+ *
4328
+ * It adds to where the content stands, so it is asked for once for each
4329
+ * layout of the box, which puts the content and the static positions back
4330
+ * at the top. A table lays a cell out and moves its content in one pass
4331
+ * over its cells, however many times a pass lays the table out, and one
4332
+ * whose layout is kept (`kept`) is moved whole, the cell with it; a button
4333
+ * counts how far down its content stands (`centreButton`).
4334
+ */
4028
4335
  export function moveContent(box: Box, dy: number): void {
4336
+ if (!dy) return;
4029
4337
  translate(box, 0, dy);
4030
4338
  box.y -= dy;
4339
+ moveStatics(box, box, dy);
4340
+ }
4341
+
4342
+ /** Whether a box is a `<button>`'s own, laid out as a block container: the
4343
+ * one HTML's button layout sets an anonymous button content box in. Not
4344
+ * the text in it, whose element is the button too; not a button that is a
4345
+ * flex box or a grid, which has no such box; nor an `<input type=button>`,
4346
+ * which is a widget of its label's size. */
4347
+ function isButtonBlock(box: Box): boolean {
4348
+ return box.kind === 'block' && box.el !== null && box.el.name === 'button';
4349
+ }
4350
+
4351
+ /** How tall a `<button>`'s content came to, and how far down its content
4352
+ * box it stands for being centred in it (`centreButton`). */
4353
+ const BUTTON_CONTENT = new WeakMap<Box, { height: number; down: number }>();
4354
+
4355
+ /**
4356
+ * Centre a `<button>`'s content down its content box, where the box is the
4357
+ * taller: a button's content is in an anonymous box of its own that is
4358
+ * "centered vertically" where it "does not overflow in the vertical axis"
4359
+ * (HTML 15.5.5, button layout), and is at the top where it does. Blink
4360
+ * moves a button's children down by half the room left, clamped at none
4361
+ * (`AlignBlockContent`); set at the top as any block's is, the two lines of
4362
+ * a 60px sidebar picker on nextjs.org sat 4px high in it.
4363
+ *
4364
+ * Asked for when the button is laid out, and again wherever its height is
4365
+ * given to it afterwards — a flex or a grid item stretched to its line, or
4366
+ * put back to the height it was measured at, and an absolute box between
4367
+ * two offsets: the content moves by the difference from where it was last
4368
+ * put. A button that is a flex box or a grid has no such box, and its
4369
+ * content is where its own alignment puts it.
4370
+ */
4371
+ export function centreButton(box: Box): void {
4372
+ if (!isButtonBlock(box)) return;
4373
+ const content = BUTTON_CONTENT.get(box);
4374
+ if (content === undefined) return;
4375
+ const down = Math.max(0, (box.contentHeight - content.height) / 2);
4376
+ const by = down - content.down;
4377
+ if (by === 0 || !Number.isFinite(by)) return;
4378
+ content.down = down;
4379
+ // and the static positions kept from its corner with it: a menu under a
4380
+ // button's label opens under the label, where the label went
4381
+ moveContent(box, by);
4382
+ }
4383
+
4384
+ /** Move the static positions a block gave the absolutely positioned boxes
4385
+ * in its flow, at any depth of inline box, which are kept from its own
4386
+ * corner and not from its content's (`moveContent`). One from a box
4387
+ * inside it is kept from that box's corner, which moved with the rest. */
4388
+ function moveStatics(parent: Box, block: Box, dy: number): void {
4389
+ for (const child of parent.children) {
4390
+ if (child.outOfFlow) {
4391
+ if (child.staticPosition?.from === block) child.staticPosition.y += dy;
4392
+ } else if (child.kind === 'inline') moveStatics(child, block, dy);
4393
+ }
4031
4394
  }
4032
4395
 
4033
4396
  function translate(box: Box, dx: number, dy: number): void {
@@ -4039,6 +4402,17 @@ function translate(box: Box, dx: number, dy: number): void {
4039
4402
  box.marker.x += dx;
4040
4403
  box.marker.y += dy;
4041
4404
  }
4405
+ // the pieces columns broke it in, which are kept beside it
4406
+ if (columned.any) {
4407
+ const pieces = COLUMN_PIECES.get(box);
4408
+ if (pieces) {
4409
+ for (const piece of pieces) {
4410
+ piece.x += dx;
4411
+ piece.y += dy;
4412
+ piece.wholeY += dy;
4413
+ }
4414
+ }
4415
+ }
4042
4416
  if (box.lines) {
4043
4417
  for (const line of box.lines) {
4044
4418
  line.x += dx;
@@ -4163,6 +4537,10 @@ export function establishesBFC(box: Box): boolean {
4163
4537
  if (style.lineClamp !== null) return true;
4164
4538
  if (style.float !== 'none') return true;
4165
4539
  if (style.position === 'absolute' || style.position === 'fixed') return true;
4540
+ // a multicol container (CSS Multi-column 1, 2), and a box that spans
4541
+ // one's columns (6)
4542
+ if (style.columns && box.kind === 'block') return true;
4543
+ if (style.columnSpan && box.parent?.style.columns) return true;
4166
4544
  if (
4167
4545
  style.display === 'inline-block' ||
4168
4546
  style.display === 'flex' ||
@@ -4177,10 +4555,17 @@ export function establishesBFC(box: Box): boolean {
4177
4555
  ) {
4178
4556
  return true;
4179
4557
  }
4558
+ // a flex item, and a grid's: each an independent formatting context, which
4559
+ // the margins of what it holds stay inside (CSS Flexbox 1, 4; CSS Grid 1,
4560
+ // 6.1)
4561
+ if (box.parent?.kind === 'flex' && !box.outOfFlow) return true;
4180
4562
  // The root element establishes the document's formatting context. Here it
4181
4563
  // is a box below the synthetic initial containing block, so it is named:
4182
4564
  // without it, <html>'s own margin collapsed with <body>'s first block's.
4183
4565
  if (box.el?.name === 'html') return true;
4566
+ // a button's content is in a formatting context of its own, the
4567
+ // anonymous button content box's (HTML 15.5.5), whatever its `display`
4568
+ if (isButtonBlock(box)) return true;
4184
4569
  return box.parent === null;
4185
4570
  }
4186
4571
 
@@ -4221,23 +4606,48 @@ export function applyRelativeOffsets(
4221
4606
  if (blocks) for (const block of blocks) translate(block, dx, dy);
4222
4607
  }
4223
4608
 
4224
- /** Whether a box is transformed, which is what makes it a containing
4225
- * block and a layer as a positioned box is (CSS Transforms 1, 2). */
4226
- export function transformed(style: ComputedStyle): boolean {
4227
- return style.translate !== null || style.transformTranslate !== null;
4228
- }
4609
+ // what makes a box a containing block and a layer, as a positioned box is,
4610
+ // is asked here by the layouts beside this one
4611
+ export { transformed };
4229
4612
 
4230
- /** How far `translate` and a `transform` move a box: a percentage is of
4231
- * its own border box (CSS Transforms 1, 7). */
4613
+ /** How far a box's transform moves it — `translate`, and the translation
4614
+ * its `transform` comes to with its turns and scales: a percentage is of
4615
+ * its own border box (CSS Transforms 1, 7). The rest of the transform is
4616
+ * paint's (`placedMatrix`). */
4232
4617
  function translationOf(box: Box, style = box.style): [number, number] {
4233
- let dx = 0;
4234
- let dy = 0;
4235
- for (const moved of [style.translate, style.transformTranslate]) {
4236
- if (!moved) continue;
4237
- dx += resolve(moved[0], box.width, 0);
4238
- dy += resolve(moved[1], box.height, 0);
4618
+ if (!transformed(style)) return [0, 0];
4619
+ const m = matrixOf(style, box.width, box.height);
4620
+ return [m[4], m[5]];
4621
+ }
4622
+
4623
+ /**
4624
+ * The matrix a box is painted through, in the document's coordinates: what
4625
+ * its transform does besides move it — a turn, a scale, a skew — about its
4626
+ * `transform-origin`, where layout has put it (`applyRelativeOffsets`). Null
4627
+ * for a box that only moves, which is every box but a few, and for an
4628
+ * inline box, which no transform applies to (CSS Transforms 1, 2: a
4629
+ * transformable element is no inline box).
4630
+ */
4631
+ export function placedMatrix(box: Box): Matrix | null {
4632
+ const style = box.style;
4633
+ if (
4634
+ style.transform === null &&
4635
+ style.rotate === null &&
4636
+ style.scale === null
4637
+ ) {
4638
+ return null;
4239
4639
  }
4240
- return [dx, dy];
4640
+ if (box.kind === 'inline' || box.kind === 'text' || box.kind === 'break') {
4641
+ return null;
4642
+ }
4643
+ const linear = linearOf(style);
4644
+ if (!linear) return null;
4645
+ const origin = style.transformOrigin;
4646
+ return about(
4647
+ linear,
4648
+ box.x + resolve(origin[0], box.width, 0),
4649
+ box.y + resolve(origin[1], box.height, 0),
4650
+ );
4241
4651
  }
4242
4652
 
4243
4653
  /**