@react-x11/components 0.14.0 → 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 (97) 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 +209 -23
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +873 -175
  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 +62 -0
  21. package/dist/html/css/shapes.d.ts.map +1 -0
  22. package/dist/html/css/shapes.js +214 -0
  23. package/dist/html/css/shapes.js.map +1 -0
  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 +75 -27
  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 +38 -7
  38. package/dist/html/layout/block.d.ts.map +1 -1
  39. package/dist/html/layout/block.js +160 -50
  40. package/dist/html/layout/block.js.map +1 -1
  41. package/dist/html/layout/boxes.d.ts +29 -2
  42. package/dist/html/layout/boxes.d.ts.map +1 -1
  43. package/dist/html/layout/boxes.js +159 -20
  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 +69 -19
  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 +1648 -225
  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 +907 -323
  58. package/dist/html/layout/table.js.map +1 -1
  59. package/dist/html/node.d.ts +36 -4
  60. package/dist/html/node.d.ts.map +1 -1
  61. package/dist/html/node.js +174 -37
  62. package/dist/html/node.js.map +1 -1
  63. package/dist/html/paint.d.ts +4 -1
  64. package/dist/html/paint.d.ts.map +1 -1
  65. package/dist/html/paint.js +769 -204
  66. package/dist/html/paint.js.map +1 -1
  67. package/dist/html/resources.js +14 -1
  68. package/dist/html/resources.js.map +1 -1
  69. package/dist/html/svg.d.ts +50 -17
  70. package/dist/html/svg.d.ts.map +1 -1
  71. package/dist/html/svg.js +501 -83
  72. package/dist/html/svg.js.map +1 -1
  73. package/dist/html/widgets.d.ts.map +1 -1
  74. package/dist/html/widgets.js +57 -4
  75. package/dist/html/widgets.js.map +1 -1
  76. package/package.json +3 -3
  77. package/src/code-editor/node.ts +24 -5
  78. package/src/html/css/cascade.ts +987 -176
  79. package/src/html/css/color.ts +345 -18
  80. package/src/html/css/content.ts +173 -15
  81. package/src/html/css/parse.ts +60 -8
  82. package/src/html/css/shapes.ts +273 -0
  83. package/src/html/css/style.ts +123 -30
  84. package/src/html/css/ua.ts +15 -0
  85. package/src/html/css/values.ts +23 -9
  86. package/src/html/index.ts +7 -0
  87. package/src/html/layout/block.ts +169 -56
  88. package/src/html/layout/boxes.ts +198 -24
  89. package/src/html/layout/css-grid.ts +89 -22
  90. package/src/html/layout/flex.ts +1863 -219
  91. package/src/html/layout/inline.ts +27 -7
  92. package/src/html/layout/table.ts +989 -333
  93. package/src/html/node.ts +204 -37
  94. package/src/html/paint.ts +999 -252
  95. package/src/html/resources.ts +13 -1
  96. package/src/html/svg.ts +600 -78
  97. package/src/html/widgets.ts +61 -4
@@ -127,6 +127,11 @@ export interface UnitContext {
127
127
  rlh?: () => number;
128
128
  /** Every property at its initial value, which `initial` sets one to. */
129
129
  initial?: ComputedStyle;
130
+ /** The palette's focus ring, which `-webkit-focus-ring-color` is. */
131
+ focusRing?: string;
132
+ /** Each system colour as the `light-dark()` it is, the palette's in the
133
+ * palette's scheme (`systemColorTable`); Chrome's where it is missing. */
134
+ systemColors?: Map<string, string>;
130
135
  }
131
136
 
132
137
  /** A CSS number (CSS Syntax 3 4.3.12): a sign, digits with at most one
@@ -348,9 +353,10 @@ export function fourSides<T>(parts: T[]): [T, T, T, T] {
348
353
  * element's own ink, everything else passed through. */
349
354
  export function inkColor(color: string, current: string): string {
350
355
  if (color === 'currentColor') return current;
351
- // a `color-mix()` with `currentColor` in it waits for the colour too;
352
- // every other functional colour was read when it was parsed
353
- if (color.charCodeAt(0) === 99 && color.startsWith('color-mix(')) {
356
+ // a `color-mix()` or a relative colour with `currentColor` in it waits
357
+ // for the colour too; every other functional colour was read when it was
358
+ // parsed, to `#rrggbb` or `rgba()` with no letters in it
359
+ if (color.charCodeAt(color.length - 1) === 41 && HAS_CURRENT.test(color)) {
354
360
  return functionalColor(color.replace(CURRENT, current)) ?? current;
355
361
  }
356
362
  return color;
@@ -358,6 +364,7 @@ export function inkColor(color: string, current: string): string {
358
364
 
359
365
  const CURRENT = /currentcolor/gi;
360
366
  const HAS_CURRENT = /currentcolor/i;
367
+ const RELATIVE_COLOR = /^[a-z]+\(\s*from\s/i;
361
368
 
362
369
  /** Whether a colour paints anything at all. Two string tests rather than a
363
370
  * parse, for the reason at the top of the file. */
@@ -410,12 +417,19 @@ export function parseColor(value: string): string | null {
410
417
  // five or seven, and a typo'd `#ff000` took the application down
411
418
  if (v.startsWith('#')) return HEX_COLOR.test(v) ? v : null;
412
419
  if (/^(?:rgba?|hsla?|hwb|lab|lch|oklab|oklch|color|color-mix)\(/i.test(v)) {
413
- // a mix with `currentColor` in it is kept to be mixed where it is used
414
- // (`inkColor`), once it is known to be one
415
- if (HAS_CURRENT.test(v) && /^color-mix\(/i.test(v)) {
416
- return functionalColor(v.replace(CURRENT, '#000')) === null
417
- ? null
418
- : `color-mix(${v.slice(v.indexOf('(') + 1)}`;
420
+ // a mix or a relative colour with `currentColor` in it is kept to be
421
+ // worked out where it is used (`inkColor`), once it is known to be one
422
+ if (HAS_CURRENT.test(v)) {
423
+ if (/^color-mix\(/i.test(v)) {
424
+ return functionalColor(v.replace(CURRENT, '#000')) === null
425
+ ? null
426
+ : `color-mix(${v.slice(v.indexOf('(') + 1)}`;
427
+ }
428
+ if (RELATIVE_COLOR.test(v)) {
429
+ return functionalColor(v.replace(CURRENT, '#000')) === null
430
+ ? null
431
+ : `${v.slice(0, v.indexOf('(')).toLowerCase()}${v.slice(v.indexOf('('))}`;
432
+ }
419
433
  }
420
434
  return functionalColor(v);
421
435
  }
package/src/html/index.ts CHANGED
@@ -238,6 +238,13 @@ function deriveLook(
238
238
  // `medium` and the face it reads pages in
239
239
  controlFontSize: Number(theme.fontSize ?? 14),
240
240
  controlFontFamily: String(theme.fontFamily ?? 'sans-serif'),
241
+ ...(typeof theme.focusRing === 'string' && { focusRing: theme.focusRing }),
242
+ ...(typeof theme.focusRingWidth === 'number' && {
243
+ focusRingWidth: theme.focusRingWidth,
244
+ }),
245
+ ...(typeof theme.focusRingOffset === 'number' && {
246
+ focusRingOffset: theme.focusRingOffset,
247
+ }),
241
248
  };
242
249
  }
243
250
 
@@ -30,6 +30,7 @@ import {
30
30
  CONTAIN_LAYOUT,
31
31
  CONTAIN_PAINT,
32
32
  CONTAIN_SIZE,
33
+ INLINE_BEFORE_ABSOLUTE,
33
34
  scrolls,
34
35
  } from '../css/style.js';
35
36
  import { about, linearOf, matrixOf, transformed } from '../css/transform.js';
@@ -71,6 +72,7 @@ import type {
71
72
  } from './boxes.js';
72
73
  import { FloatContext } from './floats.js';
73
74
  import {
75
+ emptyLinePoint,
74
76
  faceExtentOf,
75
77
  isButton,
76
78
  joinsBefore,
@@ -83,7 +85,7 @@ import {
83
85
  import type { FontsLike, InlineOptions, InlineResult } from './inline.js';
84
86
  import type { TextRun } from '../../richtext/index.js';
85
87
  import { layoutFlex } from './flex.js';
86
- import { finishCaptions, layoutTable } from './table.js';
88
+ import { finishCaptions, givenHeight, layoutTable } from './table.js';
87
89
  import { collapseEdges } from './collapse.js';
88
90
  import {
89
91
  clipsFor,
@@ -132,7 +134,8 @@ export interface LayoutContext {
132
134
  * its padding, its margins and its width limits are of (CSS 2.1 8.3,
133
135
  * 8.4, 10.4). Left out, `width` is both — a box probed at the room there
134
136
  * is for it. For a table cell it is the width of its row, which its
135
- * padding's percentages are of.
137
+ * padding's percentages are of, and it says the cell is laid out in its
138
+ * columns: at `width`, whatever its limits say.
136
139
  */
137
140
  layoutSubtree(box: Box, width: number, containing?: number): void;
138
141
  /** How many flex boxes' Yoga passes are running, one inside the last's
@@ -843,13 +846,19 @@ function layoutChildren(
843
846
  marked = null;
844
847
  }
845
848
  if (child.outOfFlow) {
846
- placeStatic(
847
- child,
848
- box,
849
- contentLeft,
850
- contentWidth,
851
- y + marginOf(pendingMargin),
852
- );
849
+ const top = y + marginOf(pendingMargin);
850
+ if (INLINE_BEFORE_ABSOLUTE.has(child.style)) {
851
+ // one that was inline-level is on a line of its own, beside the
852
+ // floats there, where the line's alignment puts what it holds
853
+ const band = floats.bandAt(
854
+ top,
855
+ 1,
856
+ contentLeft,
857
+ contentLeft + contentWidth,
858
+ );
859
+ const at = emptyLinePoint(box.style, band.left, band.right);
860
+ placeStatic(child, box, at, 0, top);
861
+ } else placeStatic(child, box, contentLeft, contentWidth, top);
853
862
  ctx.positioned.push({
854
863
  box: child,
855
864
  containing: containingBlockFor(child) ?? box,
@@ -1670,43 +1679,65 @@ export function measureIntrinsicWidth(
1670
1679
  available: number,
1671
1680
  content = false,
1672
1681
  ): number {
1673
- ctx.layoutSubtree(box, available);
1682
+ // not through `ctx.layoutSubtree`, which is this and one frame more on
1683
+ // the stack for every level of a document that measures what it holds
1684
+ // (`layoutTable`)
1685
+ layoutSubtree(box, ctx, available, undefined, true);
1674
1686
  const specified = box.style.width;
1675
1687
  if (!content && specified !== AUTO && Number.isFinite(box.width)) {
1676
1688
  return box.width;
1677
1689
  }
1678
- return intrinsicWidth(box) + box.horizontalExtra;
1690
+ return (
1691
+ intrinsicWidth(box, undefined, available === MIN_CONTENT_PROBE) +
1692
+ box.horizontalExtra
1693
+ );
1679
1694
  }
1680
1695
 
1681
1696
  /**
1682
1697
  * Lay a box and everything under it out at a width, at the origin — what
1683
- * `LayoutContext.layoutSubtree` hands to `flex.ts` and `table.ts`, and what
1684
- * the shrink-to-fit probe uses. A replaced box is sized rather than laid out,
1685
- * because there is nothing inside it to lay out.
1698
+ * `LayoutContext.layoutSubtree` hands to `flex.ts` and `css-grid.ts`, and
1699
+ * what the shrink-to-fit probe and a table's cells use. A replaced box is
1700
+ * sized rather than laid out, because there is nothing inside it to lay out.
1686
1701
  */
1687
- function layoutSubtree(
1702
+ export function layoutSubtree(
1688
1703
  box: Box,
1689
1704
  ctx: LayoutContext,
1690
1705
  width: number,
1691
1706
  /** Its containing block's width, where `width` is the box's own. */
1692
- containing = width,
1707
+ containing?: number,
1708
+ /** Whether this lays it out to measure it (`measureIntrinsicWidth`). */
1709
+ measuring = false,
1693
1710
  ): void {
1694
- resolveEdges(box, Number.isFinite(containing) ? containing : 0);
1711
+ const base = containing ?? width;
1712
+ resolveEdges(box, Number.isFinite(base) ? base : 0);
1695
1713
  if (box.kind === 'replaced') {
1696
1714
  sizeReplaced(box, width);
1697
1715
  box.x = 0;
1698
1716
  box.y = 0;
1699
1717
  return;
1700
1718
  }
1701
- // within its limits, a percentage among them of its containing block's
1719
+ // Within its limits, a percentage among them of its containing block's
1702
1720
  // 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.
1707
- const borderBox = Number.isFinite(width)
1708
- ? clampWidth(box, width, box.kind === 'table-cell' ? NaN : containing)
1709
- : Infinity;
1721
+ // layout made half its row was laid out at a quarter of it. An
1722
+ // intrinsic limit is among them, as it is in a block's flow: without it,
1723
+ // a flex or grid item `min-width: max-content` that its layout made as
1724
+ // wide as its content was laid out at a smaller `max-width`, where the
1725
+ // minimum wins (CSS Sizing 3, 3.1). Not where the box is laid out to be
1726
+ // measured, since that is what an intrinsic limit is measured from.
1727
+ //
1728
+ // A table cell laid out in its columns, which hand it its row's width,
1729
+ // is as wide as they are (CSS Tables 3, 3.10.2): its limits are theirs
1730
+ // to weigh (3.8.2), and have been. Clamped again, `td { max-width:
1731
+ // 50px }` in a column 200 wide set its text in 50 of it. Probed for
1732
+ // what it asks of them, at none, it keeps within its length limits as
1733
+ // any box does; a percentage one is of nothing and does not hold it.
1734
+ let borderBox = Infinity;
1735
+ if (Number.isFinite(width)) {
1736
+ if (box.kind !== 'table-cell') {
1737
+ borderBox = clampWidth(box, width, base, measuring ? undefined : ctx);
1738
+ } else if (containing !== undefined) borderBox = width;
1739
+ else borderBox = clampWidth(box, width, NaN);
1740
+ }
1710
1741
  layoutInternals(box, ctx, borderBox, 0, 0);
1711
1742
  }
1712
1743
 
@@ -2110,12 +2141,12 @@ function layoutMarker(box: Box, marker: Marker, ctx: LayoutContext): void {
2110
2141
  const style = box.style;
2111
2142
  if (marker.image) {
2112
2143
  // an image: its bottom on the first line's baseline, as an inline
2113
- // image's is, and the gap a bullet has before the content
2144
+ // image's is, and its own gap before the content (`MARKER_IMAGE_GAP`)
2114
2145
  const first = firstLineIn(box);
2115
2146
  marker.y = first
2116
2147
  ? first.y + first.baseline - marker.image.height
2117
2148
  : box.contentY;
2118
- const gap = Math.round(style.fontSize * 0.4);
2149
+ const { gap } = marker.image;
2119
2150
  // at the start of the line, which is its right in a right-to-left item
2120
2151
  marker.x =
2121
2152
  style.direction === 'rtl'
@@ -2150,9 +2181,13 @@ function layoutMarker(box: Box, marker: Marker, ctx: LayoutContext): void {
2150
2181
  });
2151
2182
  marker.layout = layout;
2152
2183
  // a space between it and the content — the one its style's suffix ends
2153
- // in, or its own text's — in its own face: a line measures without it
2154
- const gap =
2155
- marker.flush && !/\s$/.test(marker.text) ? 0 : markerGap(fonts, face);
2184
+ // in, or its own text's — in its own face: a line measures without the
2185
+ // spaces it ends in, and with a no-break space, which `content: "1.\a0"`
2186
+ // writes and which was a second gap on top of its own
2187
+ const trailing = marker.flush
2188
+ ? (/[ \t\n\r\f]*$/.exec(marker.text)?.[0].length ?? 0)
2189
+ : 1;
2190
+ const gap = trailing ? trailing * markerGap(fonts, face) : 0;
2156
2191
  // The marker sits on the first line of the item's *content*, which is not
2157
2192
  // always the item's own: an `<li>` holding a paragraph, or one holding text
2158
2193
  // and a nested list, has its inline content in an anonymous block. Looking
@@ -2324,8 +2359,9 @@ function stretchHeight(box: Box): number | null {
2324
2359
  * stretched item's, a flexed one's in a container of a definite height (CSS
2325
2360
  * Flexbox 9.8), a grid item's stretched down its area (CSS Grid 1, 11.1) —
2326
2361
  * 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.
2362
+ * a flex or grid container itself lays its own items out in, and a table
2363
+ * shares out among its rows. Set by `flex.ts` and `css-grid.ts` for the
2364
+ * item's own layout alone.
2329
2365
  */
2330
2366
  export const FLEXED_HEIGHT = new WeakMap<Box, number>();
2331
2367
 
@@ -2333,12 +2369,26 @@ export const FLEXED_HEIGHT = new WeakMap<Box, number>();
2333
2369
  * The content height a flex layout gave an item without making it definite:
2334
2370
  * flexed along a column that has no height of its own, which 9.8 leaves
2335
2371
  * 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.
2372
+ * out in it all the same — it is its used size (9.7) — as a table shares
2373
+ * it among its rows, and nothing in it takes a percentage of it. Set by
2374
+ * `flex.ts` for the item's own layout alone.
2339
2375
  */
2340
2376
  export const USED_HEIGHT = new WeakMap<Box, number>();
2341
2377
 
2378
+ /**
2379
+ * A table a grid or a flex layout gives its width, whose `auto` width is
2380
+ * then the one it is laid out at. `normal` stretches any grid item but a
2381
+ * replaced one across its area (CSS Grid 1, 6.2), and a flex item across a
2382
+ * column's line (CSS Flexbox 9.4, step 11), a table among them; and a flex
2383
+ * item along a row is the size the line flexed it to (9.7) — where in a
2384
+ * block's flow a table's `auto` width is its columns' (CSS 2.1 17.5.2;
2385
+ * Blink leaves tables out of the boxes a block stretches, `space_utils.cc`,
2386
+ * and stretches them in a grid, `grid_item.cc`, and a flex box). Set by
2387
+ * `css-grid.ts` and `flex.ts` for the item's own layout alone, and never
2388
+ * while its widths are measured.
2389
+ */
2390
+ export const STRETCHED_ACROSS = new WeakSet<Box>();
2391
+
2342
2392
  /**
2343
2393
  * Hand a box's children the height their percentages resolve against —
2344
2394
  * through inline boxes, which are no containing block, to the inline-block
@@ -2527,6 +2577,14 @@ function finishHeight(box: Box, contentHeight: number): void {
2527
2577
  if (box.kind === 'table-cell') {
2528
2578
  CELL_CONTENT.set(box, contentHeight + box.verticalExtra);
2529
2579
  }
2580
+ // A table a flex or grid layout gave a height is as tall as that, or as
2581
+ // its rows where they need more, which `layoutTable` has shared it out
2582
+ // among — whatever height it has of its own, which the flex layout
2583
+ // started from (`layoutTable`)
2584
+ if (box.kind === 'table' && Number.isFinite(givenHeight(box))) {
2585
+ box.height = contentHeight + box.verticalExtra;
2586
+ return;
2587
+ }
2530
2588
  const set = specifiedHeight(box);
2531
2589
  // at least zero: a `calc()` may come to less
2532
2590
  const specified = set === null ? null : Math.max(0, set);
@@ -2596,7 +2654,9 @@ export function clampHeight(box: Box, height: number): number {
2596
2654
  out,
2597
2655
  min + (box.style.boxSizing === 'border-box' ? 0 : box.verticalExtra),
2598
2656
  );
2599
- return Math.max(0, out);
2657
+ // no shorter than its padding and borders, as `clampWidth` is no
2658
+ // narrower
2659
+ return Math.max(box.verticalExtra, out);
2600
2660
  }
2601
2661
 
2602
2662
  /** Place a block-level box horizontally, honouring `margin: auto`. */
@@ -2816,7 +2876,10 @@ export function clampWidth(
2816
2876
  keywordWidth(box, ctx, style.minWidthKeyword, containingWidth, room),
2817
2877
  );
2818
2878
  }
2819
- return Math.max(0, out);
2879
+ // and no narrower than its padding and borders: a `border-box` length
2880
+ // less than them leaves its content box none wide, and not less (CSS
2881
+ // Sizing 3, 3.3)
2882
+ return Math.max(box.horizontalExtra, out);
2820
2883
  }
2821
2884
 
2822
2885
  /** An intrinsic `min-width` or `max-width` in the room a box's margins
@@ -2858,10 +2921,8 @@ export function contentSizedWidth(
2858
2921
  // the content's sizes, whatever width the box has of its own: a
2859
2922
  // `min-width: max-content` beside a `width` is its content's widest,
2860
2923
  // and a probe of the box at no width answered the probe's width
2861
- let probed = false;
2862
2924
  if (box.intrinsicMaxContent < 0) {
2863
2925
  box.intrinsicMaxContent = measureIntrinsicWidth(box, ctx, Infinity, true);
2864
- probed = true;
2865
2926
  }
2866
2927
  if (size !== 'max-content' && box.intrinsicMinContent < 0) {
2867
2928
  box.intrinsicMinContent = measureIntrinsicWidth(
@@ -2870,12 +2931,19 @@ export function contentSizedWidth(
2870
2931
  MIN_CONTENT_PROBE,
2871
2932
  true,
2872
2933
  );
2873
- probed = true;
2874
2934
  }
2875
- if (probed) resolveEdges(box, percentBase);
2876
- const max = box.intrinsicMaxContent;
2935
+ // The probes resolved a percentage of the box's padding or border
2936
+ // against nothing, which is how the sizes are kept, whatever the width
2937
+ // around the box: what one comes to against `percentBase` goes on top.
2938
+ // Left out, `width: max-content; padding-left: 10%` was as wide as its
2939
+ // content, the padding inside it.
2940
+ resolveEdges(box, 0);
2941
+ const bare = box.horizontalExtra;
2942
+ resolveEdges(box, Number.isFinite(percentBase) ? percentBase : 0);
2943
+ const extra = box.horizontalExtra - bare;
2944
+ const max = box.intrinsicMaxContent + extra;
2877
2945
  if (size === 'max-content') return max;
2878
- const min = box.intrinsicMinContent;
2946
+ const min = box.intrinsicMinContent + extra;
2879
2947
  if (size === 'min-content') return min;
2880
2948
  return Math.min(
2881
2949
  max,
@@ -3027,6 +3095,10 @@ const PREFERRED = new WeakMap<Box, number>();
3027
3095
  * or is indented, a block with widths of its own.
3028
3096
  */
3029
3097
  export function exactMinContent(box: Box, fonts: FontsLike): number | null {
3098
+ // a flex row's is its items' side by side, a grid's its columns', and a
3099
+ // replaced element has no words: read as its widest word, a row of two
3100
+ // in a flex column's item was as narrow as the wider of them
3101
+ if (box.kind !== 'block' && box.kind !== 'table-cell') return null;
3030
3102
  if (hasWidths(box.style) || contained(box, CONTAIN_WIDTH)) return null;
3031
3103
  const inner = exactWords(box, fonts);
3032
3104
  return inner === null ? null : inner + box.horizontalExtra;
@@ -3160,13 +3232,32 @@ function wordBound(box: Box, fonts: FontsLike, measured: boolean): number {
3160
3232
  * `seen.cut` is set where a line it measured was cut short by
3161
3233
  * `text-overflow`, in a box with a width to cut it at: the line is wider
3162
3234
  * than it was drawn, and the answer is of what was drawn.
3235
+ *
3236
+ * `narrowest` says the box was laid out by the min-content probe
3237
+ * (`MIN_CONTENT_PROBE`), and the answer is its min-content width: the
3238
+ * probe lays a box out at no width, but one with a least width of its own
3239
+ * at that, and nothing in the layout tells the two apart.
3163
3240
  */
3164
- export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3241
+ export function intrinsicWidth(
3242
+ box: Box,
3243
+ seen?: { cut: boolean },
3244
+ narrowest = false,
3245
+ ): number {
3165
3246
  // under size containment, as though it held nothing: the size
3166
3247
  // `contain-intrinsic-size` gives it, or none (CSS Containment 2, 3.2)
3167
3248
  if (contained(box, CONTAIN_WIDTH)) {
3168
3249
  return box.style.containIntrinsicWidth ?? 0;
3169
3250
  }
3251
+ // a table is as wide as its layout made it: its columns, the spacing
3252
+ // either side of them and between, and its captions' least (`layoutTable`)
3253
+ // — at no width limit its widest, and at none its narrowest. What it
3254
+ // holds says less: its rows run from the first column to the last, the
3255
+ // spacing either side outside them, and measured by them a table in a
3256
+ // flex row, a grid, a float or an `inline-table` came to that much too
3257
+ // narrow, and its columns gave it up
3258
+ if (box.kind === 'table' && Number.isFinite(box.width)) {
3259
+ return Math.max(0, box.width - box.horizontalExtra);
3260
+ }
3170
3261
  // a grid laid out at no width limit has its columns at their widest, and
3171
3262
  // is as wide as they are — an item that runs past its column makes it no
3172
3263
  // wider — or, where it has none, as where its items end
@@ -3185,11 +3276,17 @@ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3185
3276
  }
3186
3277
  let widest = 0;
3187
3278
  // A row of flex items is as wide as all of them side by side, and gaps
3188
- // between them; any other box is as wide as the widest thing in it
3279
+ // between them; any other box is as wide as the widest thing in it. So
3280
+ // is a row that wraps, at its narrowest: each of its items may be a line
3281
+ // of its own, and it is as wide as the widest of them (CSS Flexbox
3282
+ // 9.9.1). Summed, a wrapping row of three 30px items made a
3283
+ // `width: min-content` box around it 90 wide, the items side by side,
3284
+ // where Chrome has 30, one under another
3189
3285
  const display = box.style.display;
3190
3286
  const row =
3191
3287
  (display === 'flex' || display === 'inline-flex') &&
3192
3288
  box.style.flexDirection.startsWith('row');
3289
+ const wraps = row && narrowest && box.style.flexWrap !== 'nowrap';
3193
3290
  let total = 0;
3194
3291
  let items = 0;
3195
3292
  const lines = box.lines;
@@ -3257,7 +3354,8 @@ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3257
3354
  contribution = Math.max(contribution, stated + margins);
3258
3355
  }
3259
3356
  } else {
3260
- let inner = intrinsicWidth(child, seen) + child.horizontalExtra;
3357
+ let inner =
3358
+ intrinsicWidth(child, seen, narrowest) + child.horizontalExtra;
3261
3359
  if (typeof style.maxWidth === 'number') {
3262
3360
  inner = Math.min(inner, style.maxWidth + contentExtra(child));
3263
3361
  }
@@ -3269,10 +3367,10 @@ export function intrinsicWidth(box: Box, seen?: { cut: boolean }): number {
3269
3367
  across = Math.max(across, contribution);
3270
3368
  continue;
3271
3369
  }
3272
- if (row) {
3370
+ if (row && !wraps) {
3273
3371
  total += contribution;
3274
3372
  items += 1;
3275
- } else if (!sideBySide) widest = Math.max(widest, contribution);
3373
+ } else if (wraps || !sideBySide) widest = Math.max(widest, contribution);
3276
3374
  else if (child.isFloat) {
3277
3375
  const clear = style.clear;
3278
3376
  if (clear !== 'none') {
@@ -3404,8 +3502,17 @@ function sizeReplaced(
3404
3502
  const ih = own.missing & 2 ? null : own.height;
3405
3503
  // `aspect-ratio` over its own, unless written `auto` and it has one
3406
3504
  const aspect = style.aspectRatio;
3407
- const ratio =
3408
- aspect && !(aspect.auto && own.ratio > 0) ? aspect.ratio : own.ratio;
3505
+ const pages = aspect !== null && !(aspect.auto && own.ratio > 0);
3506
+ const ratio = pages ? aspect.ratio : own.ratio;
3507
+ // A ratio of the page's is of the box `box-sizing` names (CSS Sizing 4,
3508
+ // 5.1): one axis's content size to the other's goes through the border
3509
+ // box there. Written with `auto`, it is of the content box, as the
3510
+ // replaced element's own is, whichever it is.
3511
+ const outer = pages && !aspect.auto && style.boxSizing === 'border-box';
3512
+ const ox = outer ? box.horizontalExtra : 0;
3513
+ const oy = outer ? box.verticalExtra : 0;
3514
+ const widthOf = (h: number): number => Math.max(0, (h + oy) * ratio - ox);
3515
+ const heightOf = (w: number): number => Math.max(0, (w + ox) / ratio - oy);
3409
3516
  // the width a block would have here: what a ratio with no size fills, and
3410
3517
  // an `hr`, whose whole appearance is its border across the line
3411
3518
  const room = (): number => {
@@ -3421,16 +3528,16 @@ function sizeReplaced(
3421
3528
  // which moves both axes together
3422
3529
  if (iw !== null) {
3423
3530
  w = iw;
3424
- h = ih ?? iw / ratio;
3531
+ h = ih ?? heightOf(iw);
3425
3532
  } else if (ih !== null) {
3426
3533
  h = ih;
3427
- w = ih * ratio;
3534
+ w = widthOf(ih);
3428
3535
  } else {
3429
3536
  // a ratio and no size: CSS 2.1 leaves it undefined and suggests the
3430
3537
  // width a block would have, which is what browsers do — or, where the
3431
3538
  // containing block waits on this box, the default object size
3432
3539
  w = Number.isFinite(containingWidth) ? room() : own.width;
3433
- h = w / ratio;
3540
+ h = heightOf(w);
3434
3541
  }
3435
3542
  [w, h] = constrained(w, h, minW, maxW, minH, maxH);
3436
3543
  } else {
@@ -3439,12 +3546,12 @@ function sizeReplaced(
3439
3546
  // again" for a limit)
3440
3547
  const usedHeight = height === null ? null : clamp(height, minH, maxH);
3441
3548
  if (width !== null) w = width;
3442
- else if (usedHeight !== null && ratio > 0) w = usedHeight * ratio;
3549
+ else if (usedHeight !== null && ratio > 0) w = widthOf(usedHeight);
3443
3550
  else if (box.replaced === 'hr') w = room();
3444
3551
  else w = own.width;
3445
3552
  w = clamp(w, minW, maxW);
3446
3553
  if (usedHeight !== null) h = usedHeight;
3447
- else if (ratio > 0) h = clamp(w / ratio, minH, maxH);
3554
+ else if (ratio > 0) h = clamp(heightOf(w), minH, maxH);
3448
3555
  else h = clamp(own.height, minH, maxH);
3449
3556
  }
3450
3557
  box.width = w + box.horizontalExtra;
@@ -3793,10 +3900,16 @@ function layoutPositioned(box: Box, containing: Box, ctx: LayoutContext): void {
3793
3900
  0,
3794
3901
  cbHeight - top - bottom - box.marginTop - box.marginBottom,
3795
3902
  );
3796
- // — a box with a ratio has its height from its width
3903
+ // — a box with a ratio has its height from its width, and a table is
3904
+ // as tall as its rows, as it is as wide as its columns between two
3905
+ // offsets: `normal` stretches neither (Blink: "Replaced/tables don't
3906
+ // stretch in abspos", `out_of_flow_layout_part.cc`), and a table taken
3907
+ // to the height its offsets leave was that tall around rows that were
3908
+ // not
3797
3909
  const stretches =
3798
3910
  style.height === AUTO &&
3799
3911
  (box.kind !== 'replaced' || control) &&
3912
+ box.kind !== 'table' &&
3800
3913
  !boxRatio(box);
3801
3914
  // both offsets and no height: the box fills what they leave, its
3802
3915
  // `auto` margins nothing (10.6.4, rule 5) — unless `min-height` or