@react-x11/components 0.14.1 → 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/dist/code-editor/node.d.ts +13 -0
  2. package/dist/code-editor/node.d.ts.map +1 -1
  3. package/dist/code-editor/node.js +23 -5
  4. package/dist/code-editor/node.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +171 -26
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +616 -216
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +25 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +307 -19
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/content.d.ts +11 -6
  14. package/dist/html/css/content.d.ts.map +1 -1
  15. package/dist/html/css/content.js +171 -12
  16. package/dist/html/css/content.js.map +1 -1
  17. package/dist/html/css/parse.d.ts.map +1 -1
  18. package/dist/html/css/parse.js +62 -9
  19. package/dist/html/css/parse.js.map +1 -1
  20. package/dist/html/css/shapes.d.ts +18 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -1
  22. package/dist/html/css/shapes.js +21 -1
  23. package/dist/html/css/shapes.js.map +1 -1
  24. package/dist/html/css/style.d.ts +42 -5
  25. package/dist/html/css/style.d.ts.map +1 -1
  26. package/dist/html/css/style.js +74 -12
  27. package/dist/html/css/style.js.map +1 -1
  28. package/dist/html/css/ua.js +15 -0
  29. package/dist/html/css/ua.js.map +1 -1
  30. package/dist/html/css/values.d.ts +5 -0
  31. package/dist/html/css/values.d.ts.map +1 -1
  32. package/dist/html/css/values.js +18 -9
  33. package/dist/html/css/values.js.map +1 -1
  34. package/dist/html/index.d.ts.map +1 -1
  35. package/dist/html/index.js +7 -0
  36. package/dist/html/index.js.map +1 -1
  37. package/dist/html/layout/block.d.ts +29 -9
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +141 -52
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +16 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +144 -23
  44. package/dist/html/layout/boxes.js.map +1 -1
  45. package/dist/html/layout/css-grid.d.ts.map +1 -1
  46. package/dist/html/layout/css-grid.js +11 -9
  47. package/dist/html/layout/css-grid.js.map +1 -1
  48. package/dist/html/layout/flex.d.ts.map +1 -1
  49. package/dist/html/layout/flex.js +1631 -221
  50. package/dist/html/layout/flex.js.map +1 -1
  51. package/dist/html/layout/inline.d.ts +15 -3
  52. package/dist/html/layout/inline.d.ts.map +1 -1
  53. package/dist/html/layout/inline.js +23 -8
  54. package/dist/html/layout/inline.js.map +1 -1
  55. package/dist/html/layout/table.d.ts +27 -1
  56. package/dist/html/layout/table.d.ts.map +1 -1
  57. package/dist/html/layout/table.js +886 -324
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +35 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +118 -26
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts.map +1 -1
  64. package/dist/html/paint.js +558 -178
  65. package/dist/html/paint.js.map +1 -1
  66. package/dist/html/resources.js +14 -1
  67. package/dist/html/resources.js.map +1 -1
  68. package/dist/html/svg.d.ts +23 -7
  69. package/dist/html/svg.d.ts.map +1 -1
  70. package/dist/html/svg.js +264 -59
  71. package/dist/html/svg.js.map +1 -1
  72. package/dist/html/widgets.d.ts.map +1 -1
  73. package/dist/html/widgets.js +57 -4
  74. package/dist/html/widgets.js.map +1 -1
  75. package/package.json +3 -3
  76. package/src/code-editor/node.ts +24 -5
  77. package/src/html/css/cascade.ts +697 -221
  78. package/src/html/css/color.ts +345 -18
  79. package/src/html/css/content.ts +173 -15
  80. package/src/html/css/parse.ts +60 -8
  81. package/src/html/css/shapes.ts +35 -1
  82. package/src/html/css/style.ts +122 -17
  83. package/src/html/css/ua.ts +15 -0
  84. package/src/html/css/values.ts +23 -9
  85. package/src/html/index.ts +7 -0
  86. package/src/html/layout/block.ts +149 -58
  87. package/src/html/layout/boxes.ts +171 -27
  88. package/src/html/layout/css-grid.ts +12 -9
  89. package/src/html/layout/flex.ts +1843 -212
  90. package/src/html/layout/inline.ts +27 -7
  91. package/src/html/layout/table.ts +961 -332
  92. package/src/html/node.ts +151 -27
  93. package/src/html/paint.ts +692 -234
  94. package/src/html/resources.ts +13 -1
  95. package/src/html/svg.ts +299 -56
  96. package/src/html/widgets.ts +61 -4
@@ -16,7 +16,7 @@
16
16
  // reason richtext's node gives for answering from what it draws: an index
17
17
  // built by a second traversal can disagree with the boxes, and a selection
18
18
  // that disagrees with the glyphs is worse than no selection.
19
- import type { Element } from 'domhandler';
19
+ import type { ChildNode, Element } from 'domhandler';
20
20
 
21
21
  import type { TextRun } from '../../richtext/index.js';
22
22
  import {
@@ -28,6 +28,7 @@ import {
28
28
  NON_RENDERED,
29
29
  tagOf,
30
30
  } from '../dom.js';
31
+ import { ShapeCopies, languageOf, metaColorScheme } from '../css/cascade.js';
31
32
  import type {
32
33
  Cascade,
33
34
  FirstLetterRules,
@@ -48,7 +49,7 @@ import {
48
49
  inherit,
49
50
  } from '../css/style.js';
50
51
  import { AUTO } from '../css/values.js';
51
- import { svgIntrinsics } from '../svg.js';
52
+ import { concreteSize, svgIntrinsics } from '../svg.js';
52
53
  import type { IntrinsicSize } from '../svg.js';
53
54
  import type { ComputedStyle } from '../css/style.js';
54
55
  import type { GridLines } from './grid-lines.js';
@@ -82,15 +83,32 @@ export interface Marker {
82
83
  /** Its own style where a `::marker` rule gives it one; the item's where
83
84
  * none does. */
84
85
  style: ComputedStyle | null;
85
- /** The image it is, `list-style-image`'s, at its size in device pixels,
86
- * where that has arrived; its text is then not drawn. */
87
- image?: { url: string; width: number; height: number };
86
+ /** The image it is, `list-style-image`'s, where that has arrived, at its
87
+ * size in device pixels — its own, or the one a marker image with none
88
+ * is given (`Builder._markerImageSize`); its text is then not drawn.
89
+ * `gap` is the room between it and the content, in device pixels
90
+ * (`MARKER_IMAGE_GAP`). */
91
+ image?: { url: string; width: number; height: number; gap: number };
88
92
  /** Its text is its own — a string `list-style-type`, a `::marker`'s
89
93
  * `content` — and ends at the content's edge, with no gap a number
90
94
  * or a bullet is set apart by. */
91
95
  flush?: true;
92
96
  }
93
97
 
98
+ /**
99
+ * The room between a list marker's image and the item's content, in CSS
100
+ * pixels: between the image and the content's edge outside the item, and
101
+ * after the image inside it. CSS 2.1 (12.5.1) leaves where a marker goes
102
+ * to the user agent, and this is Blink's `kCMarkerPaddingPx`
103
+ * (`ListMarker::InlineMarginsForOutside` and `…ForInside`), which WebKit
104
+ * has too. Outside, ours was the gap a bullet has, 0.4em — 16 pixels at
105
+ * 40px text where Chrome has 7 — and inside the space a marker's text ends
106
+ * in, a space's width. Blink writes it into the marker's margins unzoomed,
107
+ * so at a device scale of 2 Chrome's is 7 device pixels; here it is 7 CSS
108
+ * pixels at any scale, so that a page at 2x is the page at 1x doubled.
109
+ */
110
+ const MARKER_IMAGE_GAP = 7;
111
+
94
112
  /** An out-of-flow box's static position, as an offset from the box whose
95
113
  * flow it was taken from, which may yet move. */
96
114
  export interface StaticPosition {
@@ -789,6 +807,11 @@ export interface BoxTree {
789
807
  * could reach one: worked out as a drawing is first painted, since a
790
808
  * page of icons shows few of them at once. */
791
809
  shapeStyler: ShapeStyler | null;
810
+ /** What the drawings of this build share in `Cascade.shapeStyles`: the
811
+ * document's elements by their ids, indexed when first asked, where a
812
+ * `<use>` finds the icon a sprite has for it; and what the rules matched
813
+ * in each copy a `<use>` makes. Null where `shapeStyler` is. */
814
+ shapeCopies: ShapeCopies | null;
792
815
  /** Whether a float or an out-of-flow box sits in an inline box: where
793
816
  * none does, layout looks for them among a block's own children and
794
817
  * goes through no inline box to find them. */
@@ -826,6 +849,10 @@ export interface BuildOptions {
826
849
  imageSize(el: Element): IntrinsicSize | null;
827
850
  /** The same for an image named by url — generated content's. */
828
851
  urlSize?(url: string): IntrinsicSize | null;
852
+ /** How far a style's first face reaches above its baseline, in device
853
+ * pixels: what a list marker's image with no size of its own is sized
854
+ * by (`_markerImageSize`). Where it is not known, 0.8em. */
855
+ faceAscent?(style: ComputedStyle): number | undefined;
829
856
  /** The size a real widget wants, so the box in the flow is the size the
830
857
  * control will be drawn at. */
831
858
  controlSize(
@@ -847,6 +874,28 @@ export function buildBoxes(
847
874
  return builder.run(root as Element);
848
875
  }
849
876
 
877
+ /** A document's first element with an id: one walk of it, the first time
878
+ * an id is asked for (`ShapeCopies`). */
879
+ function idIndex(root: Element): (id: string) => Element | null {
880
+ let index: Map<string, Element> | null = null;
881
+ return (id) => {
882
+ if (!index) {
883
+ index = new Map();
884
+ const stack: ChildNode[] = [...root.children].reverse();
885
+ for (let node = stack.pop(); node; node = stack.pop()) {
886
+ if (!isElement(node)) continue;
887
+ const own = node.attribs.id;
888
+ if (own && !index.has(own)) index.set(own, node);
889
+ const children = node.children;
890
+ for (let i = children.length - 1; i >= 0; i -= 1) {
891
+ stack.push(children[i]);
892
+ }
893
+ }
894
+ }
895
+ return index.get(id) ?? null;
896
+ };
897
+ }
898
+
850
899
  /**
851
900
  * How deep the box tree may go. Everything downstream of the builder — the
852
901
  * fix-up pass, layout, paint, the accessor walks — recurses on box depth, so
@@ -854,8 +903,17 @@ export function buildBoxes(
854
903
  * output, a runaway template) from a stack overflow five phases later.
855
904
  * Blink's parser flattens at 512 for the same reason; content past the cap
856
905
  * is dropped, which beats the alternative of crashing the application.
906
+ *
857
907
  * The parser keeps a document to 256 elements (`dom.ts`), so what reaches
858
- * this now is the anonymous boxes a table builds round each of them.
908
+ * this is the anonymous boxes a table builds round them, which the builder
909
+ * counts as it goes (`wrappersAround`), since the fix-up makes them after
910
+ * it. Counted as elements, a `display: table-cell` box in another was one
911
+ * level where it is four boxes — a table, a row group, a row and the cell
912
+ * — and every pass pays for a box: painting one takes some 800 bytes of
913
+ * the stack. Two hundred and fifty cells deep, a thousand boxes, layout
914
+ * and paint each ran out of the 984 KB V8 gives the main thread, and the
915
+ * document came out blank. 512 boxes paint in under half of it, and are a
916
+ * cell in a cell 128 deep.
859
917
  */
860
918
  const MAX_DEPTH = 512;
861
919
 
@@ -916,11 +974,15 @@ class Builder {
916
974
  run(root: Element): BoxTree {
917
975
  const cascade = this._options.cascade;
918
976
  cascade.beginSharing(this._options.kept ?? null);
977
+ cascade.pageColorScheme = metaColorScheme(root);
919
978
  const { style: rootStyle, html: impliedHtml } = cascade.rootStyle(
920
979
  hasBody(root),
921
980
  hasHtml(root),
922
981
  );
923
982
  const rootBox = new Box('block', null, rootStyle);
983
+ // the root standing in for a `<body>` has its background clipped to
984
+ // the document's text as the body's would be
985
+ if (rootStyle.backgroundClipText) this._clipText = true;
924
986
  // a fragment's root stands in for a `<body>`, counters and all
925
987
  this._scopes.open();
926
988
  this._counterChanges(rootStyle, {
@@ -938,6 +1000,7 @@ class Builder {
938
1000
  const anonymous = anonymousStyles(cascade.initial);
939
1001
  fixUp(rootBox, anonymous);
940
1002
  assignSubtreeRanges(rootBox);
1003
+ const copies = cascade.stylesShapes ? new ShapeCopies(idIndex(root)) : null;
941
1004
  return {
942
1005
  root: rootBox,
943
1006
  anonymous,
@@ -954,7 +1017,8 @@ class Builder {
954
1017
  firstLine: this._firstLine,
955
1018
  firstLineStyler: this._firstLine ? this._firstLineStyler() : null,
956
1019
  selectionStyler: cascade.hasSelection ? this._selectionStyler() : null,
957
- shapeStyler: cascade.stylesShapes ? this._shapeStyler() : null,
1020
+ shapeStyler: copies ? this._shapeStyler(copies) : null,
1021
+ shapeCopies: copies,
958
1022
  nestedOutOfLine: this._nestedOutOfLine,
959
1023
  movedInline: this._movedInline,
960
1024
  clipText: this._clipText,
@@ -989,12 +1053,12 @@ class Builder {
989
1053
 
990
1054
  /** `BoxTree.shapeStyler`, over this build's cascade: each drawing's
991
1055
  * once, from its box's style as it is when first asked. */
992
- private _shapeStyler(): ShapeStyler {
1056
+ private _shapeStyler(copies: ShapeCopies): ShapeStyler {
993
1057
  const cascade = this._options.cascade;
994
1058
  return (box) => {
995
1059
  let shapes = SHAPE_STYLES.get(box);
996
1060
  if (shapes === undefined) {
997
- shapes = box.el ? cascade.shapeStyles(box.el, box.style) : null;
1061
+ shapes = box.el ? cascade.shapeStyles(box.el, box.style, copies) : null;
998
1062
  SHAPE_STYLES.set(box, shapes);
999
1063
  }
1000
1064
  return shapes;
@@ -1190,8 +1254,10 @@ class Builder {
1190
1254
  return;
1191
1255
  }
1192
1256
 
1193
- if (this._depth >= MAX_DEPTH) return;
1194
1257
  const kind = boxKindFor(style.display);
1258
+ // the box, and the anonymous boxes the fix-up will put round it
1259
+ const depth = 1 + wrappersAround(into.kind, kind, style);
1260
+ if (this._depth + depth > MAX_DEPTH) return;
1195
1261
  const box = new Box(kind, el, style);
1196
1262
  into.append(box);
1197
1263
  if (
@@ -1221,7 +1287,7 @@ class Builder {
1221
1287
  let markerStyle: ComputedStyle | null = null;
1222
1288
  let ownMarker = false;
1223
1289
  let markerImage: { url: string; size: IntrinsicSize } | null = null;
1224
- let insideImage: string | null = null;
1290
+ let insideImage: NonNullable<Marker['image']> | null = null;
1225
1291
  if (style.display === 'list-item' && style.listStyleImage) {
1226
1292
  // asked for as generated content's images are, and the tree built
1227
1293
  // again when it arrives; until it has, and where it never does, the
@@ -1256,20 +1322,21 @@ class Builder {
1256
1322
  flush = true;
1257
1323
  }
1258
1324
  if (markerImage && !ownMarker) {
1259
- if (style.listStylePosition === 'inside') insideImage = markerImage.url;
1325
+ const [width, height] = this._markerImageSize(
1326
+ markerImage.size,
1327
+ markerStyle ?? style,
1328
+ );
1329
+ const gap = MARKER_IMAGE_GAP * (this._options.scale ?? 1);
1330
+ const image = { url: markerImage.url, width, height, gap };
1331
+ if (style.listStylePosition === 'inside') insideImage = image;
1260
1332
  else {
1261
- const scale = this._options.scale ?? 1;
1262
1333
  box.marker = {
1263
1334
  text: '',
1264
1335
  layout: null,
1265
1336
  x: 0,
1266
1337
  y: 0,
1267
1338
  style: markerStyle,
1268
- image: {
1269
- url: markerImage.url,
1270
- width: (markerImage.size.width ?? 0) * scale,
1271
- height: (markerImage.size.height ?? 0) * scale,
1272
- },
1339
+ image,
1273
1340
  };
1274
1341
  }
1275
1342
  } else if (text && style.listStylePosition === 'inside') {
@@ -1302,19 +1369,23 @@ class Builder {
1302
1369
  : null;
1303
1370
  const ownLetter = ownRules ? { rules: ownRules, punctuation: [] } : null;
1304
1371
  if (ownLetter) this._firstLetter = ownLetter;
1305
- this._depth += 1;
1372
+ this._depth += depth;
1306
1373
  if (insideImage) {
1307
1374
  // an inline image at the start of the first line, as a generated
1308
- // image is, and the space a marker's text ends in
1309
- this._contentImage(insideImage, box, style, el);
1310
- this._textNode(' ', box, style, el);
1375
+ // image is, and the room after it Blink gives it rather than a
1376
+ // space: the content's own white space after it is kept, as it is
1377
+ // after an image (`MARKER_IMAGE_GAP`)
1378
+ const { url, gap } = insideImage;
1379
+ const image = this._contentImage(url, box, style, el, insideImage);
1380
+ if (style.direction === 'rtl') image.style.marginLeft = gap;
1381
+ else image.style.marginRight = gap;
1311
1382
  } else if (insideMarker) {
1312
1383
  this._insideMarker(insideMarker, style, markerStyle, box, el);
1313
1384
  }
1314
1385
  this._pseudo(el, 'before', style, box);
1315
1386
  this._children(el, box, style, childInFlex, el, key);
1316
1387
  this._pseudo(el, 'after', style, box);
1317
- this._depth -= 1;
1388
+ this._depth -= depth;
1318
1389
  this._letterAfter(flow, skipped, outerLetter, ownLetter);
1319
1390
  if (flow !== 'inline') this._endLine();
1320
1391
  this._ws = after(flow, this._ws, around);
@@ -1754,14 +1825,14 @@ class Builder {
1754
1825
  .join(item.separator);
1755
1826
  break;
1756
1827
  case 'open-quote':
1757
- text += quoteAt(style.quotes, this._quoteDepth, 0);
1828
+ text += quoteAt(style.quotes, this._quoteDepth, 0, languageOf(el));
1758
1829
  this._quoteDepth += 1;
1759
1830
  break;
1760
1831
  case 'close-quote':
1761
1832
  // a close with nothing open writes nothing and closes nothing
1762
1833
  if (this._quoteDepth > 0) {
1763
1834
  this._quoteDepth -= 1;
1764
- text += quoteAt(style.quotes, this._quoteDepth, 1);
1835
+ text += quoteAt(style.quotes, this._quoteDepth, 1, languageOf(el));
1765
1836
  }
1766
1837
  break;
1767
1838
  case 'no-open-quote':
@@ -1787,7 +1858,10 @@ class Builder {
1787
1858
  into: Box,
1788
1859
  style: ComputedStyle,
1789
1860
  owner: Element,
1790
- ): void {
1861
+ /** Its size where it is a list marker's, which has one whatever the
1862
+ * image says (`_markerImageSize`). */
1863
+ marker?: { width: number; height: number },
1864
+ ): Box {
1791
1865
  const box = new Box('replaced', null, {
1792
1866
  ...inherit(style, this._options.cascade.initial),
1793
1867
  display: 'inline',
@@ -1797,11 +1871,40 @@ class Builder {
1797
1871
  CONTENT_IMAGES.set(box, url);
1798
1872
  const size = this._options.urlSize?.(url) ?? null;
1799
1873
  this._contentImages.push({ url, element: owner, sized: !!size });
1800
- if (size) setIntrinsics(box, size, this._options.scale ?? 1);
1874
+ if (marker) {
1875
+ const { width, height } = marker;
1876
+ const ratio = width > 0 && height > 0 ? width / height : 0;
1877
+ box.intrinsic = { width, height, missing: 0, ratio };
1878
+ } else if (size) setIntrinsics(box, size, this._options.scale ?? 1);
1801
1879
  else box.intrinsic = { width: 0, height: 0, missing: 0, ratio: 0 };
1802
1880
  this._ws = after('atomic', this._ws, this._ws);
1803
1881
  this._lettered = false;
1804
1882
  this._abandonLetter();
1883
+ return box;
1884
+ }
1885
+
1886
+ /**
1887
+ * A list marker image's size, in device pixels: its own where it has one,
1888
+ * and where it lacks some — an SVG with only a `viewBox`, or with no size
1889
+ * and no ratio — CSS Images' default sizing in a square half its face's
1890
+ * ascent across. CSS Lists 3 (3.3) makes the square 1em, which both
1891
+ * engines find too large (w3c/csswg-drafts#4207): Blink's is half the
1892
+ * ascent, rounded to a whole pixel first
1893
+ * (`LayoutListMarkerImage::DefaultSize`), Gecko's 0.4em, and this is
1894
+ * Blink's. A `viewBox` alone came to 0 by 0 and drew no marker at all,
1895
+ * and inside the item to the 300 by 150 a generated image defaults to.
1896
+ */
1897
+ private _markerImageSize(
1898
+ size: IntrinsicSize,
1899
+ style: ComputedStyle,
1900
+ ): [number, number] {
1901
+ let side = 0;
1902
+ // CoreText reads a size of 0 as its default twelve points
1903
+ if ((size.width === null || size.height === null) && style.fontSize > 0) {
1904
+ const ascent = this._options.faceAscent?.(style) ?? style.fontSize * 0.8;
1905
+ side = Math.round(ascent) / 2;
1906
+ }
1907
+ return concreteSize(size, side, side, this._options.scale ?? 1);
1805
1908
  }
1806
1909
 
1807
1910
  /** A text node, whitespace-processed per the inherited `white-space`. */
@@ -2467,6 +2570,47 @@ function after(
2467
2570
  }
2468
2571
  }
2469
2572
 
2573
+ /**
2574
+ * How many anonymous boxes the fix-up will put between a box of `kind` and
2575
+ * the box it is appended to, `parent` (`fixUpTable`, `wrapOrphans`,
2576
+ * `wrapTableParts`): a table part outside a table is given what it is
2577
+ * missing of one, and what is in a table and no part of it is given a
2578
+ * cell, a row and a row group. A cell in a block is in a table, a row group
2579
+ * and a row of its own, and a block in a row is in a cell. Counted against
2580
+ * `MAX_DEPTH`, which bounds the boxes and not the elements.
2581
+ */
2582
+ function wrappersAround(
2583
+ parent: BoxKind,
2584
+ kind: BoxKind,
2585
+ style: ComputedStyle,
2586
+ ): number {
2587
+ const column =
2588
+ style.display === 'table-column' || style.display === 'table-column-group';
2589
+ switch (parent) {
2590
+ case 'table':
2591
+ if (kind === 'table-row-group' || kind === 'table-caption' || column) {
2592
+ return 0;
2593
+ }
2594
+ return kind === 'table-row' ? 1 : kind === 'table-cell' ? 2 : 3;
2595
+ case 'table-row-group':
2596
+ return kind === 'table-row' ? 0 : kind === 'table-cell' ? 1 : 2;
2597
+ case 'table-row':
2598
+ return kind === 'table-cell' ? 0 : 1;
2599
+ }
2600
+ // a part out of flow is no part (`isTablePart`)
2601
+ if (
2602
+ style.position === 'absolute' ||
2603
+ style.position === 'fixed' ||
2604
+ style.float !== 'none'
2605
+ ) {
2606
+ return 0;
2607
+ }
2608
+ if (kind === 'table-row-group' || kind === 'table-caption' || column) {
2609
+ return 1;
2610
+ }
2611
+ return kind === 'table-row' ? 2 : kind === 'table-cell' ? 3 : 0;
2612
+ }
2613
+
2470
2614
  function boxKindFor(display: ComputedStyle['display']): BoxKind {
2471
2615
  switch (display) {
2472
2616
  case 'inline':
@@ -374,18 +374,19 @@ export function layoutGrid(
374
374
  } else if (align === 'center') dy = (tall - outerHeight) / 2;
375
375
  else if (align === 'flex-end') dy = tall - outerHeight;
376
376
  else if (
377
- (align === 'stretch' || align === 'baseline') &&
377
+ (align === 'stretch' || align === 'normal' || align === 'baseline') &&
378
378
  child.style.height === AUTO &&
379
379
  !child.style.heightKeyword &&
380
- (child.style.alignSelf === 'stretch' ||
380
+ (align === 'stretch' ||
381
381
  (child.kind !== 'replaced' && !child.style.aspectRatio))
382
382
  ) {
383
383
  // a stretched item is its area's height, within its own least and
384
384
  // greatest (10.3): taller than what it holds, or shorter where its
385
385
  // tracks let it be, what it holds then running past it. An image, a
386
- // control or a box with an `aspect-ratio` keeps its own height, as
387
- // `normal` has one with a natural size or a ratio — which is what
388
- // the grid's `stretch` may be, and the item's own may not.
386
+ // control or a box with an `aspect-ratio` keeps its own height under
387
+ // `normal`, which stretches only an item with neither a natural size
388
+ // nor a ratio, and is stretched by `stretch`, the grid's or its own
389
+ // (6.2, and CSS Box Alignment 3, 6.1).
389
390
  const stretched = tall - child.marginTop - child.marginBottom;
390
391
  const height = clampHeight(
391
392
  child,
@@ -939,9 +940,9 @@ function percentOwn(box: Box): boolean {
939
940
  /**
940
941
  * An item's border-box height before the rows are sized, where it has one
941
942
  * then: a height of its own — a percentage of the rows it spans where they
942
- * have a length for a size — or the rows' where it is stretched down them
943
- * by its own `align-self`. Null where it has none, which is where its
944
- * height is its content's.
943
+ * have a length for a size — or the rows' where `stretch` stretches it
944
+ * down them, its own `align-self` or the grid's `align-items`. Null where
945
+ * it has none, which is where its height is its content's.
945
946
  */
946
947
  function heightBeforeRows(box: Box, rows: number): number | null {
947
948
  const style = box.style;
@@ -954,10 +955,12 @@ function heightBeforeRows(box: Box, rows: number): number | null {
954
955
  : own + box.verticalExtra,
955
956
  );
956
957
  }
958
+ const align =
959
+ style.alignSelf === AUTO ? box.parent?.style.alignItems : style.alignSelf;
957
960
  if (
958
961
  style.height === AUTO &&
959
962
  !style.heightKeyword &&
960
- style.alignSelf === 'stretch' &&
963
+ align === 'stretch' &&
961
964
  Number.isFinite(rows) &&
962
965
  !autoMargins(box, 'y').some(Boolean)
963
966
  ) {