@react-x11/components 0.10.0 → 0.12.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 (155) hide show
  1. package/dist/code/index.d.ts.map +1 -1
  2. package/dist/code/index.js +25 -24
  3. package/dist/code/index.js.map +1 -1
  4. package/dist/code-language/index.d.ts +1 -1
  5. package/dist/code-language/index.d.ts.map +1 -1
  6. package/dist/code-language/index.js +1 -1
  7. package/dist/code-language/index.js.map +1 -1
  8. package/dist/code-language/runs.d.ts +31 -0
  9. package/dist/code-language/runs.d.ts.map +1 -1
  10. package/dist/code-language/runs.js +105 -0
  11. package/dist/code-language/runs.js.map +1 -1
  12. package/dist/codeblock/index.d.ts +13 -0
  13. package/dist/codeblock/index.d.ts.map +1 -1
  14. package/dist/codeblock/index.js +37 -1
  15. package/dist/codeblock/index.js.map +1 -1
  16. package/dist/flow/node.d.ts +14 -6
  17. package/dist/flow/node.d.ts.map +1 -1
  18. package/dist/flow/node.js +82 -64
  19. package/dist/flow/node.js.map +1 -1
  20. package/dist/html/controls.d.ts +26 -7
  21. package/dist/html/controls.d.ts.map +1 -1
  22. package/dist/html/controls.js +28 -3
  23. package/dist/html/controls.js.map +1 -1
  24. package/dist/html/css/cascade.d.ts +32 -10
  25. package/dist/html/css/cascade.d.ts.map +1 -1
  26. package/dist/html/css/cascade.js +89 -31
  27. package/dist/html/css/cascade.js.map +1 -1
  28. package/dist/html/css/color.d.ts +19 -0
  29. package/dist/html/css/color.d.ts.map +1 -1
  30. package/dist/html/css/color.js +86 -0
  31. package/dist/html/css/color.js.map +1 -1
  32. package/dist/html/css/parse.d.ts +11 -0
  33. package/dist/html/css/parse.d.ts.map +1 -1
  34. package/dist/html/css/parse.js +114 -4
  35. package/dist/html/css/parse.js.map +1 -1
  36. package/dist/html/css/style.d.ts +45 -5
  37. package/dist/html/css/style.d.ts.map +1 -1
  38. package/dist/html/css/style.js +363 -14
  39. package/dist/html/css/style.js.map +1 -1
  40. package/dist/html/css/ua.js +22 -5
  41. package/dist/html/css/ua.js.map +1 -1
  42. package/dist/html/css/values.d.ts +10 -1
  43. package/dist/html/css/values.d.ts.map +1 -1
  44. package/dist/html/css/values.js +27 -2
  45. package/dist/html/css/values.js.map +1 -1
  46. package/dist/html/css/vars.d.ts +5 -0
  47. package/dist/html/css/vars.d.ts.map +1 -1
  48. package/dist/html/css/vars.js +2 -2
  49. package/dist/html/css/vars.js.map +1 -1
  50. package/dist/html/dom.d.ts.map +1 -1
  51. package/dist/html/dom.js +35 -2
  52. package/dist/html/dom.js.map +1 -1
  53. package/dist/html/index.d.ts.map +1 -1
  54. package/dist/html/index.js +38 -1
  55. package/dist/html/index.js.map +1 -1
  56. package/dist/html/layout/block.d.ts +11 -2
  57. package/dist/html/layout/block.d.ts.map +1 -1
  58. package/dist/html/layout/block.js +306 -25
  59. package/dist/html/layout/block.js.map +1 -1
  60. package/dist/html/layout/boxes.d.ts +61 -6
  61. package/dist/html/layout/boxes.d.ts.map +1 -1
  62. package/dist/html/layout/boxes.js +65 -16
  63. package/dist/html/layout/boxes.js.map +1 -1
  64. package/dist/html/layout/cache.d.ts +2 -2
  65. package/dist/html/layout/cache.d.ts.map +1 -1
  66. package/dist/html/layout/cache.js +8 -3
  67. package/dist/html/layout/cache.js.map +1 -1
  68. package/dist/html/layout/floats.d.ts +4 -0
  69. package/dist/html/layout/floats.d.ts.map +1 -1
  70. package/dist/html/layout/floats.js +26 -0
  71. package/dist/html/layout/floats.js.map +1 -1
  72. package/dist/html/layout/inline.d.ts +29 -2
  73. package/dist/html/layout/inline.d.ts.map +1 -1
  74. package/dist/html/layout/inline.js +1454 -125
  75. package/dist/html/layout/inline.js.map +1 -1
  76. package/dist/html/node.d.ts +33 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +608 -86
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +53 -1
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +746 -214
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/surfaces.d.ts +7 -2
  85. package/dist/html/surfaces.d.ts.map +1 -1
  86. package/dist/html/surfaces.js +16 -0
  87. package/dist/html/surfaces.js.map +1 -1
  88. package/dist/html/svg.d.ts.map +1 -1
  89. package/dist/html/svg.js +27 -20
  90. package/dist/html/svg.js.map +1 -1
  91. package/dist/internal/codelines.d.ts +42 -0
  92. package/dist/internal/codelines.d.ts.map +1 -0
  93. package/dist/internal/codelines.js +170 -0
  94. package/dist/internal/codelines.js.map +1 -0
  95. package/dist/internal/window.d.ts +22 -0
  96. package/dist/internal/window.d.ts.map +1 -1
  97. package/dist/internal/window.js +35 -8
  98. package/dist/internal/window.js.map +1 -1
  99. package/dist/markdown/index.d.ts.map +1 -1
  100. package/dist/markdown/index.js +98 -26
  101. package/dist/markdown/index.js.map +1 -1
  102. package/dist/rich-text-editor/index.d.ts.map +1 -1
  103. package/dist/rich-text-editor/index.js +3 -8
  104. package/dist/rich-text-editor/index.js.map +1 -1
  105. package/dist/rich-text-editor/markdown.d.ts.map +1 -1
  106. package/dist/rich-text-editor/markdown.js +30 -1
  107. package/dist/rich-text-editor/markdown.js.map +1 -1
  108. package/dist/rich-text-editor/replace.d.ts +13 -0
  109. package/dist/rich-text-editor/replace.d.ts.map +1 -0
  110. package/dist/rich-text-editor/replace.js +64 -0
  111. package/dist/rich-text-editor/replace.js.map +1 -0
  112. package/dist/richtext/node.d.ts +7 -0
  113. package/dist/richtext/node.d.ts.map +1 -1
  114. package/dist/richtext/node.js.map +1 -1
  115. package/dist/terminal-output/index.d.ts.map +1 -1
  116. package/dist/terminal-output/index.js +100 -25
  117. package/dist/terminal-output/index.js.map +1 -1
  118. package/dist/terminal-output/runs.d.ts +4 -1
  119. package/dist/terminal-output/runs.d.ts.map +1 -1
  120. package/dist/terminal-output/runs.js +5 -1
  121. package/dist/terminal-output/runs.js.map +1 -1
  122. package/package.json +5 -3
  123. package/src/code/index.ts +34 -32
  124. package/src/code-language/index.ts +1 -1
  125. package/src/code-language/runs.ts +111 -1
  126. package/src/codeblock/index.ts +48 -1
  127. package/src/flow/node.ts +80 -64
  128. package/src/html/controls.ts +50 -9
  129. package/src/html/css/cascade.ts +134 -43
  130. package/src/html/css/color.ts +87 -0
  131. package/src/html/css/parse.ts +107 -4
  132. package/src/html/css/style.ts +386 -23
  133. package/src/html/css/ua.ts +22 -5
  134. package/src/html/css/values.ts +31 -3
  135. package/src/html/css/vars.ts +2 -2
  136. package/src/html/dom.ts +38 -2
  137. package/src/html/index.ts +39 -1
  138. package/src/html/layout/block.ts +360 -22
  139. package/src/html/layout/boxes.ts +98 -23
  140. package/src/html/layout/cache.ts +8 -3
  141. package/src/html/layout/floats.ts +24 -0
  142. package/src/html/layout/inline.ts +1690 -119
  143. package/src/html/node.ts +648 -101
  144. package/src/html/paint.ts +883 -283
  145. package/src/html/surfaces.ts +21 -1
  146. package/src/html/svg.ts +30 -19
  147. package/src/internal/codelines.ts +200 -0
  148. package/src/internal/window.ts +36 -8
  149. package/src/markdown/index.ts +145 -26
  150. package/src/rich-text-editor/index.ts +3 -11
  151. package/src/rich-text-editor/markdown.ts +36 -1
  152. package/src/rich-text-editor/replace.ts +85 -0
  153. package/src/richtext/node.ts +7 -0
  154. package/src/terminal-output/index.ts +127 -34
  155. package/src/terminal-output/runs.ts +16 -2
@@ -40,6 +40,7 @@ import {
40
40
  } from './content.js';
41
41
  import type { ContentItem, CounterChange } from './content.js';
42
42
  import type { CustomProps } from './vars.js';
43
+ import { LIGHT_DARK, lightDark, usedColorScheme } from './color.js';
43
44
 
44
45
  export type Display =
45
46
  | 'none'
@@ -202,6 +203,14 @@ export type ContentSize =
202
203
  export interface ComputedStyle {
203
204
  // --- inherited ------------------------------------------------------------
204
205
  color: string;
206
+ /**
207
+ * The colour scheme the element is drawn in (CSS Color Adjust 1, 2.2):
208
+ * its `color-scheme` resolved against the scheme the palette is, which
209
+ * stands for the reader's preference, as it does for `@media
210
+ * (prefers-color-scheme)`. What a `light-dark()` picks by. `normal` is
211
+ * the palette's own, since the palette is this renderer's default look.
212
+ */
213
+ colorScheme: 'light' | 'dark';
205
214
  fontFamily: string;
206
215
  fontSize: number;
207
216
  fontWeight: number;
@@ -320,6 +329,11 @@ export interface ComputedStyle {
320
329
  float: 'none' | 'left' | 'right';
321
330
  clear: 'none' | 'left' | 'right' | 'both';
322
331
  boxSizing: 'content-box' | 'border-box';
332
+ /** `box-decoration-break` (CSS Fragmentation 3, 5.4): how an inline box
333
+ * that goes on to another line places its background images and
334
+ * gradients — `slice`, from its fragments laid end to end as one box,
335
+ * or `clone`, from each fragment's own. */
336
+ boxDecorationBreak: 'slice' | 'clone';
323
337
  /** Whether a block is a line-clamp container, and how many lines of its
324
338
  * formatting context it shows (CSS Overflow 4, 5.3.1): its `max-lines`,
325
339
  * or Infinity for `line-clamp: auto`, which shows as many as its height
@@ -497,14 +511,18 @@ export interface ComputedStyle {
497
511
  /** `background-clip` and `background-origin` (CSS Backgrounds 3, 3.7 and
498
512
  * 3.8): the box a layer is painted in and the box it is placed in, the
499
513
  * colour painted in the bottom layer's; each a list as the four above. */
500
- backgroundClip: VisualBox;
514
+ backgroundClip: BackgroundClip;
501
515
  backgroundOrigin: VisualBox;
502
- backgroundClips: VisualBox[] | null;
516
+ backgroundClips: BackgroundClip[] | null;
503
517
  backgroundOrigins: VisualBox[] | null;
504
518
  /** `border-image` and its longhands (CSS Backgrounds 3, 6): one object,
505
519
  * replaced whole when any of them changes, and `NO_BORDER_IMAGE` where
506
520
  * nothing sets them. */
507
521
  borderImage: BorderImage;
522
+ /** `mask-image` and the longhands that place its layers (CSS Masking 1,
523
+ * 7): one object, replaced whole when any of them changes, and
524
+ * `NO_MASK` where nothing sets them. */
525
+ mask: MaskLayers;
508
526
  /** `-webkit-text-fill-color`: what the glyphs are filled with where it
509
527
  * is not the text's `color` — Tailwind's `text-transparent` over a
510
528
  * `bg-clip-text` gradient; null for `color`, as `currentColor` is. */
@@ -608,6 +626,7 @@ export interface ComputedStyle {
608
626
  * same list. */
609
627
  export const INHERITED = [
610
628
  'color',
629
+ 'colorScheme',
611
630
  'textFillColor',
612
631
  'fontFamily',
613
632
  'fontSize',
@@ -689,6 +708,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
689
708
  // a copy of the literal, not the literal: see `copyStyle`
690
709
  return copyStyle({
691
710
  color: look.color,
711
+ colorScheme: look.colorScheme,
692
712
  fontFamily: look.fontFamily,
693
713
  fontSize: look.fontSize,
694
714
  fontWeight: 400,
@@ -747,6 +767,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
747
767
  float: 'none',
748
768
  clear: 'none',
749
769
  boxSizing: 'content-box',
770
+ boxDecorationBreak: 'slice',
750
771
  lineClamp: null,
751
772
  maxLines: null,
752
773
  clampContinue: 'auto',
@@ -846,6 +867,7 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
846
867
  backgroundClips: null,
847
868
  backgroundOrigins: null,
848
869
  borderImage: NO_BORDER_IMAGE,
870
+ mask: NO_MASK,
849
871
  textFillColor: null,
850
872
 
851
873
  textDecorationLine: 'none',
@@ -941,6 +963,7 @@ export function inherit(
941
963
  // quarters of the cost of every element's style, a store through a
942
964
  // computed name costing thirty times one through a written one
943
965
  out.color = parent.color;
966
+ out.colorScheme = parent.colorScheme;
944
967
  out.textFillColor = parent.textFillColor;
945
968
  out.fontFamily = parent.fontFamily;
946
969
  out.fontSize = parent.fontSize;
@@ -1162,6 +1185,15 @@ export function applyDeclaration(
1162
1185
  if (/!\s*important$/i.test(value)) {
1163
1186
  value = value.replace(/!\s*important$/i, '').trim();
1164
1187
  }
1188
+ // the branch of each `light-dark()` this element's scheme picks, before
1189
+ // anything reads the value: it stands wherever a colour can, in a
1190
+ // shorthand, a shadow, a gradient or a mix, and most often arrives
1191
+ // through a `var()` from a theme on `:root`
1192
+ if (LIGHT_DARK.test(value)) {
1193
+ const picked = lightDark(value, style.colorScheme);
1194
+ if (picked === null) return;
1195
+ value = picked;
1196
+ }
1165
1197
 
1166
1198
  const color = COLOR_PROPS[name];
1167
1199
  if (color) {
@@ -1178,6 +1210,12 @@ export function applyDeclaration(
1178
1210
  }
1179
1211
 
1180
1212
  switch (name) {
1213
+ case 'color-scheme': {
1214
+ const scheme = usedColorScheme(value, ctx.initial?.colorScheme);
1215
+ if (scheme) style.colorScheme = scheme;
1216
+ return;
1217
+ }
1218
+
1181
1219
  // --- box ----------------------------------------------------------------
1182
1220
  case 'display': {
1183
1221
  const v = value.toLowerCase();
@@ -1366,6 +1404,12 @@ export function applyDeclaration(
1366
1404
  if (v === 'border-box' || v === 'content-box') style.boxSizing = v;
1367
1405
  return;
1368
1406
  }
1407
+ case 'box-decoration-break':
1408
+ case '-webkit-box-decoration-break': {
1409
+ const v = value.trim().toLowerCase();
1410
+ if (v === 'slice' || v === 'clone') style.boxDecorationBreak = v;
1411
+ return;
1412
+ }
1369
1413
  case 'aspect-ratio': {
1370
1414
  const parsed = parseAspectRatio(value);
1371
1415
  if (parsed !== undefined) style.aspectRatio = parsed;
@@ -1799,14 +1843,29 @@ export function applyDeclaration(
1799
1843
  }
1800
1844
  case 'background-clip':
1801
1845
  case '-webkit-background-clip': {
1802
- // `text` the first layer's alone, which paints through the text
1846
+ // `text` the first layer's alone, which paints through the text —
1847
+ // and with `border-area` through its border as well, in either order
1848
+ // (CSS Backgrounds 4, 2.1)
1803
1849
  const clips = layerValues(value, (part) => {
1804
1850
  const v = part.toLowerCase();
1805
- return v === 'text' || isVisualBox(v) ? v : null;
1851
+ if (v === 'text' || isBackgroundClip(v)) return v;
1852
+ const [a, b, more] = splitValue(v);
1853
+ return !more &&
1854
+ ((a === 'border-area' && b === 'text') ||
1855
+ (a === 'text' && b === 'border-area'))
1856
+ ? 'border-area text'
1857
+ : null;
1806
1858
  });
1807
1859
  if (!clips) return;
1808
- style.backgroundClipText = clips[0] === 'text';
1809
- const boxes = clips.map((c) => (c === 'text' ? 'border-box' : c));
1860
+ style.backgroundClipText =
1861
+ clips[0] === 'text' || clips[0] === 'border-area text';
1862
+ const boxes = clips.map((c): BackgroundClip =>
1863
+ c === 'text'
1864
+ ? 'border-box'
1865
+ : c === 'border-area text'
1866
+ ? 'border-area'
1867
+ : (c as BackgroundClip),
1868
+ );
1810
1869
  style.backgroundClip = boxes[0];
1811
1870
  style.backgroundClips = boxes.length > 1 ? boxes : null;
1812
1871
  return;
@@ -1891,6 +1950,67 @@ export function applyDeclaration(
1891
1950
  return;
1892
1951
  }
1893
1952
 
1953
+ // --- masking --------------------------------------------------------------
1954
+ // Each `-webkit-` name is the same property's (Compatibility, 5): every
1955
+ // engine takes both, and a page writes both — Wikipedia's icons do.
1956
+ case 'mask':
1957
+ case '-webkit-mask': {
1958
+ const mask = readMask(value, ctx);
1959
+ if (mask) style.mask = mask;
1960
+ return;
1961
+ }
1962
+ case 'mask-image':
1963
+ case '-webkit-mask-image': {
1964
+ // `none` a layer as much as an image is; a bad url drops it all
1965
+ const images: BackgroundImage[] = [];
1966
+ for (const part of splitCommas(value)) {
1967
+ const image = maskImageOf(part, ctx);
1968
+ if (image === undefined) return;
1969
+ images.push(image);
1970
+ }
1971
+ if (images.length) style.mask = { ...style.mask, images };
1972
+ return;
1973
+ }
1974
+ case 'mask-repeat':
1975
+ case '-webkit-mask-repeat': {
1976
+ const repeats = layerValues(value, (part) => {
1977
+ const words = splitValue(part.toLowerCase());
1978
+ return words.length <= 2 ? readRepeat(words) : null;
1979
+ });
1980
+ if (repeats) style.mask = { ...style.mask, repeats };
1981
+ return;
1982
+ }
1983
+ case 'mask-size':
1984
+ case '-webkit-mask-size': {
1985
+ const sizes = layerValues(value, (part) =>
1986
+ backgroundSizeOf(splitValue(part), ctx),
1987
+ );
1988
+ if (sizes) style.mask = { ...style.mask, sizes };
1989
+ return;
1990
+ }
1991
+ case 'mask-position':
1992
+ case '-webkit-mask-position': {
1993
+ const positions = layerValues(value, (part) =>
1994
+ positionPair(splitValue(part), ctx),
1995
+ );
1996
+ if (positions) style.mask = { ...style.mask, positions };
1997
+ return;
1998
+ }
1999
+ case 'mask-origin':
2000
+ case '-webkit-mask-origin': {
2001
+ const origins = layerValues(value, (part) => maskBox(part));
2002
+ if (origins) style.mask = { ...style.mask, origins };
2003
+ return;
2004
+ }
2005
+ case 'mask-clip':
2006
+ case '-webkit-mask-clip': {
2007
+ const clips = layerValues(value, (part) =>
2008
+ part.toLowerCase() === 'no-clip' ? 'border-box' : maskBox(part),
2009
+ );
2010
+ if (clips) style.mask = { ...style.mask, clips };
2011
+ return;
2012
+ }
2013
+
1894
2014
  // --- generated content --------------------------------------------------
1895
2015
  case 'content': {
1896
2016
  const parsed = parseContent(value);
@@ -1998,7 +2118,13 @@ export function applyDeclaration(
1998
2118
  style.lineHeight = len;
1999
2119
  style.lineHeightIsLength = true;
2000
2120
  } else if (len && typeof len === 'object') {
2001
- const px = resolve(len, style.fontSize);
2121
+ // a percentage of the element's own font size, the one it is
2122
+ // computed with (CSS 2.1 10.8.1), which `em` already is: not the
2123
+ // size in `style` as this declaration comes up, which a `font`
2124
+ // earlier in the same rule has just set to its own — `p { font:
2125
+ // 12px …; line-height: 100% }` under a `.lead { font-size: 16px }`
2126
+ // set sixteen-pixel text on twelve-pixel lines
2127
+ const px = resolve(len, ctx.em);
2002
2128
  if (px < 0) return;
2003
2129
  style.lineHeight = px;
2004
2130
  style.lineHeightIsLength = true;
@@ -2447,7 +2573,12 @@ export function applyDeclaration(
2447
2573
  return;
2448
2574
  }
2449
2575
  case 'align-content': {
2450
- const v = alignKeyword(value);
2576
+ // `normal` is `stretch` in a flex and a grid container, the only
2577
+ // boxes it moves anything in (CSS Box Alignment 3, 5.1)
2578
+ const v =
2579
+ value.trim().toLowerCase() === 'normal'
2580
+ ? 'stretch'
2581
+ : alignKeyword(value);
2451
2582
  if (v) style.alignContent = v as ComputedStyle['alignContent'];
2452
2583
  return;
2453
2584
  }
@@ -2682,6 +2813,30 @@ export function applyDeclaration(
2682
2813
  }
2683
2814
  return;
2684
2815
  }
2816
+ case 'place-content': {
2817
+ // `<'align-content'> <'justify-content'>?` (CSS Box Alignment 3,
2818
+ // 5.3), either half one keyword or two — `first baseline`, `safe
2819
+ // center` — and the second the first again where it is left out,
2820
+ // but for a baseline, which is no `justify-content`: `start`
2821
+ const words = value.trim().toLowerCase().split(/\s+/);
2822
+ const span = (at: number) =>
2823
+ /^(?:safe|unsafe|first|last)$/.test(words[at] ?? '') ? 2 : 1;
2824
+ const cut = span(0);
2825
+ const align = words.slice(0, cut).join(' ');
2826
+ const rest = words.slice(cut);
2827
+ if (rest.length && rest.length !== span(cut)) return;
2828
+ const justify = rest.length
2829
+ ? rest.join(' ')
2830
+ : /baseline$/.test(align)
2831
+ ? 'start'
2832
+ : align;
2833
+ // a shorthand is one declaration: either half unread drops both
2834
+ if (align !== 'normal' && !alignKeyword(align)) return;
2835
+ if (!justifyKeyword(justify)) return;
2836
+ applyDeclaration(style, parent, 'align-content', align, ctx);
2837
+ applyDeclaration(style, parent, 'justify-content', justify, ctx);
2838
+ return;
2839
+ }
2685
2840
  case 'place-items':
2686
2841
  case 'place-self': {
2687
2842
  const parts = splitValue(value);
@@ -2877,12 +3032,6 @@ export function settleOverflow(style: ComputedStyle): void {
2877
3032
  else style.overflowX = x === 'visible' ? 'auto' : 'hidden';
2878
3033
  }
2879
3034
 
2880
- /**
2881
- * Whether a box is a scroll container: its `overflow` is neither `visible`
2882
- * nor `clip`. `clip` cuts what overflows as `hidden` does, but makes no
2883
- * formatting context and keeps a flex or grid item's automatic minimum
2884
- * (CSS Overflow 3, 3.1) — so only this asks it apart from `hidden`.
2885
- */
2886
3035
  /** A `<visual-box>` (CSS Box 4): the edge of a box a property names. */
2887
3036
  export type VisualBox = 'border-box' | 'padding-box' | 'content-box';
2888
3037
 
@@ -2890,6 +3039,20 @@ function isVisualBox(v: string): v is VisualBox {
2890
3039
  return v === 'border-box' || v === 'padding-box' || v === 'content-box';
2891
3040
  }
2892
3041
 
3042
+ /** Where a background layer is painted: inside one of the box's edges, or
3043
+ * `border-area`, in what its border paints (CSS Backgrounds 4, 2.1). */
3044
+ export type BackgroundClip = VisualBox | 'border-area';
3045
+
3046
+ function isBackgroundClip(v: string): v is BackgroundClip {
3047
+ return v === 'border-area' || isVisualBox(v);
3048
+ }
3049
+
3050
+ /**
3051
+ * Whether a box is a scroll container: its `overflow` is neither `visible`
3052
+ * nor `clip`. `clip` cuts what overflows as `hidden` does, but makes no
3053
+ * formatting context and keeps a flex or grid item's automatic minimum
3054
+ * (CSS Overflow 3, 3.1) — so only this asks it apart from `hidden`.
3055
+ */
2893
3056
  export function scrolls(style: ComputedStyle): boolean {
2894
3057
  const x = style.overflowX;
2895
3058
  const y = style.overflowY;
@@ -3121,6 +3284,157 @@ export const NO_BORDER_IMAGE: BorderImage = {
3121
3284
  repeat: ['stretch', 'stretch'],
3122
3285
  };
3123
3286
 
3287
+ /**
3288
+ * An element's mask layers (CSS Masking 1, 7), top first, a list a
3289
+ * property as a background's are: each layer takes the images' place in
3290
+ * the others, and they repeat where they are fewer. A layer's image is a
3291
+ * url, a gradient, or null for `none` — which is the one layer of an
3292
+ * element that is not masked.
3293
+ */
3294
+ export interface MaskLayers {
3295
+ images: readonly BackgroundImage[];
3296
+ repeats: readonly BackgroundRepeat[];
3297
+ sizes: readonly ComputedStyle['backgroundSize'][];
3298
+ positions: readonly (readonly [Len, Len])[];
3299
+ /** The box a layer is placed in, and the one it is painted in: the
3300
+ * border box unless a layer says (7.5, 7.6). */
3301
+ origins: readonly VisualBox[];
3302
+ clips: readonly VisualBox[];
3303
+ }
3304
+
3305
+ /** Whether a style masks its element: a layer of its mask has an image. */
3306
+ export function masked(style: ComputedStyle): boolean {
3307
+ const mask = style.mask;
3308
+ if (mask === NO_MASK) return false;
3309
+ for (const image of mask.images) if (image !== null) return true;
3310
+ return false;
3311
+ }
3312
+
3313
+ /**
3314
+ * One layer's `mask-image`: a url, a gradient — or none, for an image
3315
+ * function this does not draw, or a `url(#id)`, which names an SVG
3316
+ * `<mask>` element in the document rather than an image to fetch (7.2):
3317
+ * drawn with no mask rather than as the transparent layer a mask that
3318
+ * cannot be had is, since this draws no `<mask>`. Undefined where it is no
3319
+ * image at all.
3320
+ */
3321
+ function maskImageOf(
3322
+ text: string,
3323
+ ctx: UnitContext,
3324
+ ): BackgroundImage | undefined {
3325
+ const image = backgroundImageOf(text, ctx);
3326
+ return typeof image === 'string' && image[0] === '#' ? null : image;
3327
+ }
3328
+
3329
+ /** A `<geometry-box>` as an HTML element has it (CSS Masking 1, 7.5): the
3330
+ * SVG boxes are its content box and its border box. */
3331
+ function maskBox(text: string): VisualBox | null {
3332
+ const v = text.trim().toLowerCase();
3333
+ if (isVisualBox(v)) return v;
3334
+ if (v === 'fill-box') return 'content-box';
3335
+ if (v === 'stroke-box' || v === 'view-box') return 'border-box';
3336
+ return null;
3337
+ }
3338
+
3339
+ /**
3340
+ * The `mask` shorthand (CSS Masking 1, 7.10): a layer a comma group, each
3341
+ * an image, a position with a size after a `/`, a repeat, one box for the
3342
+ * origin and the clip or two, `no-clip`, a compositing operator and a
3343
+ * mode, each at most once, in any order. The operator and the mode are
3344
+ * read and not honoured: layers are added, and an image masks by its
3345
+ * alpha, which is what `match-source` makes of one. Null where a layer is
3346
+ * not one, which drops the declaration.
3347
+ */
3348
+ function readMask(value: string, ctx: UnitContext): MaskLayers | null {
3349
+ const out = {
3350
+ images: [] as BackgroundImage[],
3351
+ repeats: [] as BackgroundRepeat[],
3352
+ sizes: [] as ComputedStyle['backgroundSize'][],
3353
+ positions: [] as [Len, Len][],
3354
+ origins: [] as VisualBox[],
3355
+ clips: [] as VisualBox[],
3356
+ };
3357
+ for (const text of splitCommas(value)) {
3358
+ const parts = splitValue(text).flatMap(splitSlash);
3359
+ let image: BackgroundImage = null;
3360
+ let repeat = NO_MASK.repeats[0];
3361
+ let size: ComputedStyle['backgroundSize'] = 'auto';
3362
+ let position: [Len, Len] = [0, 0];
3363
+ const boxes: VisualBox[] = [];
3364
+ let seen = 0;
3365
+ const once = (bit: number): boolean => {
3366
+ if (seen & bit) return false;
3367
+ seen |= bit;
3368
+ return true;
3369
+ };
3370
+ for (let i = 0; i < parts.length;) {
3371
+ const part = parts[i];
3372
+ const v = part.toLowerCase();
3373
+ if (v === 'none' || v.startsWith('url(') || IMAGE_FUNCTION.test(v)) {
3374
+ if (!once(1)) return null;
3375
+ const read = maskImageOf(part, ctx);
3376
+ if (read === undefined) return null;
3377
+ image = read;
3378
+ i += 1;
3379
+ } else if (v === 'repeat-x' || v === 'repeat-y' || REPEATS.has(v)) {
3380
+ if (!once(2)) return null;
3381
+ const pair = REPEATS.has(v) && REPEATS.has(parts[i + 1]?.toLowerCase());
3382
+ const read = readRepeat(pair ? [v, parts[i + 1].toLowerCase()] : [v]);
3383
+ if (!read) return null;
3384
+ repeat = read;
3385
+ i += pair ? 2 : 1;
3386
+ } else if (maskBox(v) || v === 'no-clip') {
3387
+ if (boxes.length === 2 || (v === 'no-clip' && !once(4))) return null;
3388
+ boxes.push(maskBox(v) ?? 'border-box');
3389
+ i += 1;
3390
+ } else if (/^(?:add|subtract|intersect|exclude)$/.test(v)) {
3391
+ if (!once(8)) return null;
3392
+ i += 1;
3393
+ } else if (/^(?:alpha|luminance|match-source)$/.test(v)) {
3394
+ if (!once(16)) return null;
3395
+ i += 1;
3396
+ } else if (isPositionPart(v, ctx)) {
3397
+ if (!once(32)) return null;
3398
+ let end = i;
3399
+ while (
3400
+ end < parts.length &&
3401
+ isPositionPart(parts[end].toLowerCase(), ctx)
3402
+ ) {
3403
+ end += 1;
3404
+ }
3405
+ const read = positionPair(parts.slice(i, end), ctx);
3406
+ if (!read) return null;
3407
+ position = read;
3408
+ i = end;
3409
+ if (parts[i] !== '/') continue;
3410
+ const first = parts[i + 1]?.toLowerCase();
3411
+ if (first === 'cover' || first === 'contain') {
3412
+ size = first;
3413
+ i += 2;
3414
+ } else if (first !== undefined && isSizePart(first, ctx)) {
3415
+ const two =
3416
+ i + 2 < parts.length && isSizePart(parts[i + 2].toLowerCase(), ctx);
3417
+ size =
3418
+ backgroundSizeOf(parts.slice(i + 1, i + (two ? 3 : 2)), ctx) ??
3419
+ 'auto';
3420
+ i += two ? 3 : 2;
3421
+ } else {
3422
+ return null;
3423
+ }
3424
+ } else {
3425
+ return null;
3426
+ }
3427
+ }
3428
+ out.images.push(image);
3429
+ out.repeats.push(repeat);
3430
+ out.sizes.push(size);
3431
+ out.positions.push(position);
3432
+ out.origins.push(boxes[0] ?? 'border-box');
3433
+ out.clips.push(boxes[1] ?? boxes[0] ?? 'border-box');
3434
+ }
3435
+ return out.images.length ? out : null;
3436
+ }
3437
+
3124
3438
  /** One to four values, one a side as `margin` takes them; null where one
3125
3439
  * is not a value. */
3126
3440
  function sidesOf<T>(
@@ -3315,6 +3629,15 @@ function repeatPair(x: RepeatMode, y: RepeatMode): BackgroundRepeat {
3315
3629
 
3316
3630
  const REPEAT = repeatPair('repeat', 'repeat');
3317
3631
 
3632
+ export const NO_MASK: MaskLayers = {
3633
+ images: [null],
3634
+ repeats: [REPEAT],
3635
+ sizes: ['auto'],
3636
+ positions: [[0, 0]],
3637
+ origins: ['border-box'],
3638
+ clips: ['border-box'],
3639
+ };
3640
+
3318
3641
  function applyBorderShorthand(
3319
3642
  style: ComputedStyle,
3320
3643
  name: string,
@@ -3485,7 +3808,7 @@ interface BackgroundLayer {
3485
3808
  size: ComputedStyle['backgroundSize'];
3486
3809
  attachment: ComputedStyle['backgroundAttachment'];
3487
3810
  origin: VisualBox;
3488
- clip: VisualBox;
3811
+ clip: BackgroundClip;
3489
3812
  position: [Len, Len] | null;
3490
3813
  }
3491
3814
 
@@ -3545,9 +3868,14 @@ function readBackgroundLayer(
3545
3868
  layer.attachment = v;
3546
3869
  i += 1;
3547
3870
  } else if (isVisualBox(v)) {
3548
- // one box is both the origin and the clip, and a second the clip
3549
- if ((boxes += 1) > 2) return null;
3871
+ // one box is both the origin and the clip, and a second the clip —
3872
+ // which `border-area` is instead of, and the one box the origin
3873
+ if ((boxes += 1) > (seen & 32 ? 1 : 2)) return null;
3550
3874
  if (boxes === 1) layer.origin = v;
3875
+ if (!(seen & 32)) layer.clip = v;
3876
+ i += 1;
3877
+ } else if (v === 'border-area') {
3878
+ if (boxes > 1 || !once(32)) return null;
3551
3879
  layer.clip = v;
3552
3880
  i += 1;
3553
3881
  } else if (isPositionPart(v, ctx)) {
@@ -3998,12 +4326,10 @@ function applyFontShorthand(
3998
4326
  style.lineHeight = 'normal';
3999
4327
  style.lineHeightIsLength = false;
4000
4328
  applyDeclaration(style, parent, 'font-size', sizeText, ctx);
4001
- if (lineText) {
4002
- applyDeclaration(style, parent, 'line-height', lineText, {
4003
- ...ctx,
4004
- em: style.fontSize,
4005
- });
4006
- }
4329
+ // against the element's own size, which `ctx.em` is once the cascade has
4330
+ // settled it: a later rule's `font-size` outranks this one's, and
4331
+ // `font: 12px/150%` under a `font-size: 16px` is on 24px lines
4332
+ if (lineText) applyDeclaration(style, parent, 'line-height', lineText, ctx);
4007
4333
  applyDeclaration(style, parent, 'font-family', family, ctx);
4008
4334
  }
4009
4335
 
@@ -4723,6 +5049,7 @@ function camel(name: string): string {
4723
5049
 
4724
5050
  const INHERITED_NAMES = new Set<string>([
4725
5051
  'color',
5052
+ 'color-scheme',
4726
5053
  '-webkit-text-fill-color',
4727
5054
  'font',
4728
5055
  'font-family',
@@ -4773,6 +5100,11 @@ export function initialOne(
4773
5100
  initial: ComputedStyle,
4774
5101
  name: string,
4775
5102
  ): void {
5103
+ const list = MASK_LISTS[name];
5104
+ if (list) {
5105
+ style.mask = { ...style.mask, [list]: initial.mask[list] };
5106
+ return;
5107
+ }
4776
5108
  const keys = INHERIT_TARGETS[name];
4777
5109
  if (!keys) return;
4778
5110
  for (const key of keys) {
@@ -4785,6 +5117,11 @@ function inheritOne(
4785
5117
  parent: ComputedStyle,
4786
5118
  name: string,
4787
5119
  ): void {
5120
+ const list = MASK_LISTS[name];
5121
+ if (list) {
5122
+ style.mask = { ...style.mask, [list]: parent.mask[list] };
5123
+ return;
5124
+ }
4788
5125
  const keys = INHERIT_TARGETS[name];
4789
5126
  if (!keys) return;
4790
5127
  // A border colour left to `currentColor` inherits as the keyword and
@@ -4800,8 +5137,30 @@ const sides = (
4800
5137
  make: (side: (typeof SIDES)[number]) => keyof ComputedStyle,
4801
5138
  ): (keyof ComputedStyle)[] => SIDES.map(make);
4802
5139
 
5140
+ /**
5141
+ * The list of the mask's layers each longhand is, which `initial`,
5142
+ * `inherit` and a `var()` that fails reset alone: one field holds them all,
5143
+ * and reset whole, a `mask-size: calc(var(--x) - 4px)` put back the
5144
+ * `mask-repeat: no-repeat` before it, and Wikipedia's chevron repeated.
5145
+ */
5146
+ const MASK_LISTS: Record<string, keyof MaskLayers> = {
5147
+ 'mask-image': 'images',
5148
+ '-webkit-mask-image': 'images',
5149
+ 'mask-repeat': 'repeats',
5150
+ '-webkit-mask-repeat': 'repeats',
5151
+ 'mask-size': 'sizes',
5152
+ '-webkit-mask-size': 'sizes',
5153
+ 'mask-position': 'positions',
5154
+ '-webkit-mask-position': 'positions',
5155
+ 'mask-origin': 'origins',
5156
+ '-webkit-mask-origin': 'origins',
5157
+ 'mask-clip': 'clips',
5158
+ '-webkit-mask-clip': 'clips',
5159
+ };
5160
+
4803
5161
  const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
4804
5162
  color: ['color'],
5163
+ 'color-scheme': ['colorScheme'],
4805
5164
  '-webkit-text-fill-color': ['textFillColor'],
4806
5165
  'background-clip': [
4807
5166
  'backgroundClipText',
@@ -4815,6 +5174,8 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
4815
5174
  ],
4816
5175
  'background-origin': ['backgroundOrigin', 'backgroundOrigins'],
4817
5176
  'border-image': ['borderImage'],
5177
+ mask: ['mask'],
5178
+ '-webkit-mask': ['mask'],
4818
5179
  'border-image-source': ['borderImage'],
4819
5180
  'border-image-slice': ['borderImage'],
4820
5181
  'border-image-width': ['borderImage'],
@@ -4895,6 +5256,8 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
4895
5256
  'min-height': ['minHeight', 'minHeightKeyword'],
4896
5257
  'max-height': ['maxHeight', 'maxHeightKeyword'],
4897
5258
  'box-sizing': ['boxSizing'],
5259
+ 'box-decoration-break': ['boxDecorationBreak'],
5260
+ '-webkit-box-decoration-break': ['boxDecorationBreak'],
4898
5261
  margin: sides((s) => `margin${s}`),
4899
5262
  'margin-top': ['marginTop'],
4900
5263
  'margin-right': ['marginRight'],
@@ -44,8 +44,11 @@ export function uaStylesheet(look: RootLook): Stylesheet {
44
44
  }
45
45
 
46
46
  /**
47
- * Margins are in `em` throughout, so a document that sets `font-size` on
48
- * `body` scales its whitespace with its text the way a browser's does.
47
+ * Margins above and below are in `em`, so a document that sets `font-size`
48
+ * on `body` scales its whitespace with its text the way a browser's does.
49
+ * The indents of lists, definitions and figures are the HTML standard's
50
+ * 40px (15.3.3, 15.3.8), whatever the size: a list in a 10px sidebar is
51
+ * indented as far as one in the body.
49
52
  */
50
53
  function uaText(look: RootLook): string {
51
54
  const mono = look.monoFamily;
@@ -81,7 +84,7 @@ h4 { font-size: 1em; font-weight: bold; margin: 1.33em 0; }
81
84
  h5 { font-size: 0.83em; font-weight: bold; margin: 1.67em 0; }
82
85
  h6 { font-size: 0.67em; font-weight: bold; margin: 2.33em 0; }
83
86
 
84
- ul, ol { margin: 1em 0; padding-left: 2.5em; }
87
+ ul, ol { margin: 1em 0; padding-left: 40px; }
85
88
  ul { list-style-type: disc; }
86
89
  ol { list-style-type: decimal; }
87
90
  ol, ul, menu, dir { counter-reset: list-item; }
@@ -92,7 +95,7 @@ ul ul ul, ol ol ul, ul ol ul, ol ul ul { list-style-type: square; }
92
95
  ul ul, ul ol, ol ul, ol ol { margin: 0; }
93
96
 
94
97
  dl { margin: 1em 0; }
95
- dd { margin-left: 2.5em; }
98
+ dd { margin-left: 40px; }
96
99
  dt { font-weight: bold; }
97
100
 
98
101
  blockquote {
@@ -150,7 +153,7 @@ img { display: inline-block; }
150
153
  iframe { border: 2px inset; }
151
154
  /* a poster is drawn within the video's box at its own ratio (HTML 15.4.1) */
152
155
  video { object-fit: contain; }
153
- figure { margin: 1em 2.5em; }
156
+ figure { margin: 1em 40px; }
154
157
 
155
158
  table { display: table; border-collapse: separate; border-spacing: 2px; box-sizing: border-box; }
156
159
  caption { display: table-caption; text-align: center; }
@@ -185,6 +188,20 @@ input, button, select, textarea, meter, progress {
185
188
  }
186
189
  /* Chrome's own UA margins for the checkables, near enough: they are the
187
190
  controls that sit hard against their label text otherwise. */
191
+ /* HTML's rendering section (15.3.10): a control's text keeps none of the
192
+ spacing, the line height, the case or the indent of the text around it.
193
+ A button in a paragraph of 'line-height: 1.5' is its own font's line
194
+ tall, as it is in a browser. */
195
+ input, button, textarea {
196
+ letter-spacing: initial;
197
+ word-spacing: initial;
198
+ line-height: initial;
199
+ }
200
+ input, select, button, textarea {
201
+ text-transform: initial;
202
+ text-indent: initial;
203
+ text-shadow: initial;
204
+ }
188
205
  input[type=checkbox] { margin: 3px 4px 3px 4px; }
189
206
  input[type=radio] { margin: 3px 4px 3px 5px; }
190
207
  input[type=hidden] { display: none; }