@react-x11/components 0.13.0 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. package/dist/html/controls.d.ts +29 -2
  2. package/dist/html/controls.d.ts.map +1 -1
  3. package/dist/html/controls.js +70 -2
  4. package/dist/html/controls.js.map +1 -1
  5. package/dist/html/css/cascade.d.ts +42 -1
  6. package/dist/html/css/cascade.d.ts.map +1 -1
  7. package/dist/html/css/cascade.js +336 -16
  8. package/dist/html/css/cascade.js.map +1 -1
  9. package/dist/html/css/color.d.ts +18 -0
  10. package/dist/html/css/color.d.ts.map +1 -1
  11. package/dist/html/css/color.js +27 -0
  12. package/dist/html/css/color.js.map +1 -1
  13. package/dist/html/css/parse.d.ts +18 -4
  14. package/dist/html/css/parse.d.ts.map +1 -1
  15. package/dist/html/css/parse.js +66 -28
  16. package/dist/html/css/parse.js.map +1 -1
  17. package/dist/html/css/shapes.d.ts +44 -0
  18. package/dist/html/css/shapes.d.ts.map +1 -0
  19. package/dist/html/css/shapes.js +194 -0
  20. package/dist/html/css/shapes.js.map +1 -0
  21. package/dist/html/css/style.d.ts +128 -15
  22. package/dist/html/css/style.d.ts.map +1 -1
  23. package/dist/html/css/style.js +547 -144
  24. package/dist/html/css/style.js.map +1 -1
  25. package/dist/html/css/transform.d.ts +85 -0
  26. package/dist/html/css/transform.d.ts.map +1 -0
  27. package/dist/html/css/transform.js +425 -0
  28. package/dist/html/css/transform.js.map +1 -0
  29. package/dist/html/css/ua.d.ts.map +1 -1
  30. package/dist/html/css/ua.js +14 -2
  31. package/dist/html/css/ua.js.map +1 -1
  32. package/dist/html/css/values.d.ts +4 -0
  33. package/dist/html/css/values.d.ts.map +1 -1
  34. package/dist/html/css/values.js.map +1 -1
  35. package/dist/html/fonts.d.ts +55 -5
  36. package/dist/html/fonts.d.ts.map +1 -1
  37. package/dist/html/fonts.js +284 -23
  38. package/dist/html/fonts.js.map +1 -1
  39. package/dist/html/layout/axes.d.ts +14 -0
  40. package/dist/html/layout/axes.d.ts.map +1 -0
  41. package/dist/html/layout/axes.js +28 -0
  42. package/dist/html/layout/axes.js.map +1 -0
  43. package/dist/html/layout/block.d.ts +104 -12
  44. package/dist/html/layout/block.d.ts.map +1 -1
  45. package/dist/html/layout/block.js +410 -39
  46. package/dist/html/layout/block.js.map +1 -1
  47. package/dist/html/layout/boxes.d.ts +72 -1
  48. package/dist/html/layout/boxes.d.ts.map +1 -1
  49. package/dist/html/layout/boxes.js +40 -2
  50. package/dist/html/layout/boxes.js.map +1 -1
  51. package/dist/html/layout/cache.d.ts +1 -1
  52. package/dist/html/layout/cache.d.ts.map +1 -1
  53. package/dist/html/layout/cache.js +6 -0
  54. package/dist/html/layout/cache.js.map +1 -1
  55. package/dist/html/layout/css-grid.d.ts.map +1 -1
  56. package/dist/html/layout/css-grid.js +144 -24
  57. package/dist/html/layout/css-grid.js.map +1 -1
  58. package/dist/html/layout/flex.d.ts.map +1 -1
  59. package/dist/html/layout/flex.js +585 -85
  60. package/dist/html/layout/flex.js.map +1 -1
  61. package/dist/html/layout/inline.d.ts +3 -0
  62. package/dist/html/layout/inline.d.ts.map +1 -1
  63. package/dist/html/layout/inline.js +144 -54
  64. package/dist/html/layout/inline.js.map +1 -1
  65. package/dist/html/layout/multicol.d.ts +42 -0
  66. package/dist/html/layout/multicol.d.ts.map +1 -0
  67. package/dist/html/layout/multicol.js +897 -0
  68. package/dist/html/layout/multicol.js.map +1 -0
  69. package/dist/html/layout/shaping.d.ts +7 -0
  70. package/dist/html/layout/shaping.d.ts.map +1 -1
  71. package/dist/html/layout/shaping.js +25 -20
  72. package/dist/html/layout/shaping.js.map +1 -1
  73. package/dist/html/layout/table.d.ts.map +1 -1
  74. package/dist/html/layout/table.js +97 -31
  75. package/dist/html/layout/table.js.map +1 -1
  76. package/dist/html/node.d.ts +4 -0
  77. package/dist/html/node.d.ts.map +1 -1
  78. package/dist/html/node.js +319 -47
  79. package/dist/html/node.js.map +1 -1
  80. package/dist/html/paint.d.ts +83 -5
  81. package/dist/html/paint.d.ts.map +1 -1
  82. package/dist/html/paint.js +1474 -217
  83. package/dist/html/paint.js.map +1 -1
  84. package/dist/html/svg.d.ts +39 -9
  85. package/dist/html/svg.d.ts.map +1 -1
  86. package/dist/html/svg.js +482 -37
  87. package/dist/html/svg.js.map +1 -1
  88. package/dist/html/widgets.js +87 -7
  89. package/dist/html/widgets.js.map +1 -1
  90. package/dist/html/woff2.d.ts +14 -0
  91. package/dist/html/woff2.d.ts.map +1 -0
  92. package/dist/html/woff2.js +608 -0
  93. package/dist/html/woff2.js.map +1 -0
  94. package/dist/maps/controller.d.ts +17 -1
  95. package/dist/maps/controller.d.ts.map +1 -1
  96. package/dist/maps/controller.js +27 -7
  97. package/dist/maps/controller.js.map +1 -1
  98. package/dist/richtext/node.d.ts +8 -1
  99. package/dist/richtext/node.d.ts.map +1 -1
  100. package/dist/richtext/node.js +5 -1
  101. package/dist/richtext/node.js.map +1 -1
  102. package/dist/richtext/runs.d.ts +25 -6
  103. package/dist/richtext/runs.d.ts.map +1 -1
  104. package/dist/richtext/runs.js +240 -24
  105. package/dist/richtext/runs.js.map +1 -1
  106. package/package.json +1 -1
  107. package/src/html/controls.ts +85 -2
  108. package/src/html/css/cascade.ts +366 -8
  109. package/src/html/css/color.ts +29 -0
  110. package/src/html/css/parse.ts +71 -31
  111. package/src/html/css/shapes.ts +239 -0
  112. package/src/html/css/style.ts +652 -147
  113. package/src/html/css/transform.ts +459 -0
  114. package/src/html/css/ua.ts +15 -2
  115. package/src/html/css/values.ts +4 -0
  116. package/src/html/fonts.ts +312 -29
  117. package/src/html/layout/axes.ts +62 -0
  118. package/src/html/layout/block.ts +466 -37
  119. package/src/html/layout/boxes.ts +95 -3
  120. package/src/html/layout/cache.ts +6 -0
  121. package/src/html/layout/css-grid.ts +169 -32
  122. package/src/html/layout/flex.ts +686 -79
  123. package/src/html/layout/inline.ts +149 -55
  124. package/src/html/layout/multicol.ts +977 -0
  125. package/src/html/layout/shaping.ts +27 -18
  126. package/src/html/layout/table.ts +105 -35
  127. package/src/html/node.ts +354 -48
  128. package/src/html/paint.ts +1804 -315
  129. package/src/html/svg.ts +573 -36
  130. package/src/html/widgets.ts +95 -9
  131. package/src/html/woff2.ts +612 -0
  132. package/src/maps/controller.ts +28 -7
  133. package/src/richtext/node.ts +13 -2
  134. package/src/richtext/runs.ts +313 -35
@@ -40,7 +40,10 @@ 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 { parseRotate, parseScale, parseTransform } from './transform.js';
44
+ import type { TransformFunction } from './transform.js';
43
45
  import { LIGHT_DARK, lightDark, usedColorScheme } from './color.js';
46
+ import { svgPaint } from './shapes.js';
44
47
 
45
48
  export type Display =
46
49
  | 'none'
@@ -108,15 +111,43 @@ export interface BoxShadow {
108
111
  inset: boolean;
109
112
  }
110
113
 
114
+ /** A gradient's colour stop: its colour, at a position along the gradient
115
+ * or, with none, where its neighbours put it. */
116
+ export interface GradientStop {
117
+ color: string;
118
+ at: Len | null;
119
+ }
120
+
111
121
  /** A `linear-gradient()` (CSS Images 3, 3.1): its direction, an angle
112
122
  * clockwise from up or a corner the box's shape turns into one, and its
113
- * colour stops, each at a position or where its neighbours put it. */
123
+ * colour stops. */
114
124
  export interface LinearGradient {
125
+ kind: 'linear';
115
126
  angle: number;
116
127
  corner: 'top left' | 'top right' | 'bottom left' | 'bottom right' | null;
117
- stops: { color: string; at: Len | null }[];
128
+ stops: GradientStop[];
129
+ }
130
+
131
+ /** A `radial-gradient()` (CSS Images 3, 3.2): its ending shape, a circle or
132
+ * an ellipse; that shape's size, a keyword for the side or the corner of
133
+ * the box it reaches or the radii it was given, one for a circle and two
134
+ * for an ellipse; its centre, a position in the box; and its colour stops,
135
+ * along the ray from the centre to the shape's edge. */
136
+ export interface RadialGradient {
137
+ kind: 'radial';
138
+ circle: boolean;
139
+ extent: RadialExtent | null;
140
+ radii: [Len, Len] | null;
141
+ at: [Len, Len];
142
+ stops: GradientStop[];
118
143
  }
119
144
 
145
+ export type RadialExtent =
146
+ 'closest-side' | 'closest-corner' | 'farthest-side' | 'farthest-corner';
147
+
148
+ /** An image a gradient function draws. */
149
+ export type Gradient = LinearGradient | RadialGradient;
150
+
120
151
  /** Where a grid item starts or ends (CSS Grid 1, 8.3): a line, counted
121
152
  * among the lines of a name where it has one; a span, to a line of a name
122
153
  * where it has one; a name alone, an area's edge or a line's; or auto. */
@@ -174,6 +205,121 @@ function parseClip(
174
205
  return { top: edges[0], right: edges[1], bottom: edges[2], left: edges[3] };
175
206
  }
176
207
 
208
+ /** The box a `clip-path`'s shape is measured in, a `<geometry-box>` (CSS
209
+ * Masking 1, 5.1). */
210
+ export type ClipBox =
211
+ 'margin-box' | 'border-box' | 'padding-box' | 'content-box';
212
+
213
+ /**
214
+ * `clip-path` (CSS Masking 1, 5.1): a rectangle in a box of the element's,
215
+ * its corners rounded or not — a `<basic-shape-rect>` (CSS Shapes 1, 3.1),
216
+ * kept as the function it was written as, since each measures its four
217
+ * lengths its own way and a percentage among them has no pixels before
218
+ * layout. The element shows through it, and everything in it does.
219
+ */
220
+ export interface ClipPath {
221
+ box: ClipBox;
222
+ /** What the four lengths are: `inset()`'s, how far in from the box's
223
+ * top, right, bottom and left; `rect()`'s, those four edges from its top
224
+ * and its left, `auto` the box's own; `xywh()`'s, the rectangle's left
225
+ * and top from the box's, and its width and height. Null for a box
226
+ * named alone, which is the shape: the whole of it, rounded as the
227
+ * element's corners are. */
228
+ shape: 'inset' | 'rect' | 'xywh' | null;
229
+ lengths: readonly [Len, Len, Len, Len];
230
+ /** `round`: the corners' radii across, and down where they differ, as
231
+ * `border-radius` has them — a percentage is of the box, and not of the
232
+ * rectangle in it. Null for square corners. */
233
+ radii: readonly [Len, Len, Len, Len] | null;
234
+ radiiY: readonly [Len, Len, Len, Len] | null;
235
+ }
236
+
237
+ /** The boxes `clip-path` names; an SVG one is the CSS box that stands for
238
+ * it on an element with a CSS layout box (5.1). */
239
+ const CLIP_BOXES: Record<string, ClipBox> = {
240
+ 'margin-box': 'margin-box',
241
+ 'border-box': 'border-box',
242
+ 'padding-box': 'padding-box',
243
+ 'content-box': 'content-box',
244
+ 'fill-box': 'content-box',
245
+ 'stroke-box': 'border-box',
246
+ 'view-box': 'border-box',
247
+ };
248
+
249
+ /** The shapes that are no rectangle, and a reference to a `<clipPath>`
250
+ * element: a value, which sets the property, and none this draws. */
251
+ const UNDRAWN_CLIPS = /^(circle|ellipse|polygon|path|shape|url)\(/i;
252
+
253
+ const NO_LENGTHS = [0, 0, 0, 0] as const;
254
+
255
+ /**
256
+ * `none`; a rectangle's function, a box, or the two in either order; or a
257
+ * shape that is no rectangle, which is a value and clips nothing here.
258
+ * Undefined for anything else, which leaves the value as it was.
259
+ */
260
+ function parseClipPath(
261
+ value: string,
262
+ ctx: UnitContext,
263
+ ): ClipPath | null | undefined {
264
+ const v = value.trim();
265
+ if (/^none$/i.test(v)) return null;
266
+ let box: ClipBox | null = null;
267
+ let shape: Omit<ClipPath, 'box'> | null | undefined;
268
+ for (const part of splitValue(v)) {
269
+ const named = CLIP_BOXES[part.toLowerCase()];
270
+ if (named) {
271
+ if (box) return undefined;
272
+ box = named;
273
+ continue;
274
+ }
275
+ if (shape !== undefined) return undefined;
276
+ if (UNDRAWN_CLIPS.test(part) && part.endsWith(')')) shape = null;
277
+ else shape = rectShape(part, ctx);
278
+ if (shape === undefined) return undefined;
279
+ }
280
+ if (shape === null) return null;
281
+ if (shape) return { box: box ?? 'border-box', ...shape };
282
+ if (!box) return undefined;
283
+ return { box, shape: null, lengths: NO_LENGTHS, radii: null, radiiY: null };
284
+ }
285
+
286
+ /** `inset()`, `rect()` or `xywh()`, with the corners it rounds; undefined
287
+ * for anything else. */
288
+ function rectShape(
289
+ text: string,
290
+ ctx: UnitContext,
291
+ ): Omit<ClipPath, 'box'> | undefined {
292
+ const m = /^(inset|rect|xywh)\((.*)\)$/is.exec(text);
293
+ if (!m) return undefined;
294
+ const shape = m[1].toLowerCase() as 'inset' | 'rect' | 'xywh';
295
+ const parts = splitValue(m[2].trim());
296
+ const round = parts.findIndex((p) => p.toLowerCase() === 'round');
297
+ const sides = round < 0 ? parts : parts.slice(0, round);
298
+ let radii: ClipPath['radii'] = null;
299
+ let radiiY: ClipPath['radiiY'] = null;
300
+ if (round >= 0) {
301
+ const corners = parseRadii(parts.slice(round + 1).join(' '), ctx);
302
+ if (!corners) return undefined;
303
+ [radii, radiiY] = corners;
304
+ }
305
+ // one to four insets, as a margin's sides are written; the other two
306
+ // take all four
307
+ if (
308
+ shape === 'inset' ? !sides.length || sides.length > 4 : sides.length !== 4
309
+ )
310
+ return undefined;
311
+ const lengths: Len[] = [];
312
+ for (let i = 0; i < sides.length; i += 1) {
313
+ const len = parseLength(sides[i], ctx);
314
+ if (len === null) return undefined;
315
+ // only `rect()` has an edge that is `auto`, and a width is not negative
316
+ if (len === AUTO && shape !== 'rect') return undefined;
317
+ if (shape === 'xywh' && i > 1 && !notNegative(len)) return undefined;
318
+ lengths.push(len);
319
+ }
320
+ return { shape, lengths: fourSides(lengths), radii, radiiY };
321
+ }
322
+
177
323
  export type BorderStyle =
178
324
  | 'none'
179
325
  | 'hidden'
@@ -187,7 +333,7 @@ export type BorderStyle =
187
333
  | 'outset';
188
334
 
189
335
  /** A background layer's image: a url, a gradient, or none. */
190
- export type BackgroundImage = string | LinearGradient | null;
336
+ export type BackgroundImage = string | Gradient | null;
191
337
 
192
338
  /** An intrinsic size: `min-content`, `max-content` or `fit-content`, and
193
339
  * `fit-content()`, whose argument stands in for the room (`fit`). */
@@ -290,6 +436,15 @@ export interface ComputedStyle {
290
436
  * in place of its `list-style-type`'s; null for `none`. */
291
437
  listStyleImage: string | null;
292
438
  cursor: string | null;
439
+ /** `pointer-events`: `none` for an element the pointer passes through,
440
+ * to what is under it (CSS UI 4, 5.2). Its SVG values are `auto`. */
441
+ pointerEvents: 'auto' | 'none';
442
+ /** `fill` and `stroke`, what an `<svg>` in the document is painted with
443
+ * (SVG 2, 13.2): a colour, `currentColor`, `none`, or a `url()` of a
444
+ * paint server in the drawing — and null where nothing has set one,
445
+ * which leaves the drawing its own. */
446
+ fill: string | null;
447
+ stroke: string | null;
293
448
  borderCollapse: 'separate' | 'collapse';
294
449
  /** `border-spacing`: between columns, and between rows. */
295
450
  borderSpacing: number;
@@ -369,6 +524,26 @@ export interface ComputedStyle {
369
524
  * multicol container, which this lays out as one column and which
370
525
  * `line-clamp` does not clamp (CSS Overflow 4, 5.2). */
371
526
  columns: number;
527
+ /** `column-count` where it is a number, and `column-width` where it is
528
+ * a length; null for `auto` (CSS Multi-column 1, 2 and 3). */
529
+ columnCount: number | null;
530
+ columnWidth: number | null;
531
+ /** Whether `column-gap` is `normal`: nothing in a flex box or a grid,
532
+ * and an em between the columns of a multicol container (4.1). */
533
+ columnGapNormal: boolean;
534
+ /** The fewest lines of a block a column may end on, and the fewest it
535
+ * may start with (CSS Fragmentation 3, 4.3). Inherited. */
536
+ orphans: number;
537
+ widows: number;
538
+ /** `break-inside`: whether a column break may fall inside the box;
539
+ * `avoid-column` and `avoid-page` are read as `avoid`. */
540
+ breakInside: 'auto' | 'avoid';
541
+ /** `column-fill`: whether the columns are balanced, or each filled
542
+ * before the next is started; `balance-all` is `balance`. */
543
+ columnFill: 'balance' | 'auto';
544
+ /** `column-span: all`: whether the box is set across all the columns of
545
+ * the multicol container it is in (CSS Multi-column 1, 6). */
546
+ columnSpan: boolean;
372
547
  /** How a line cut by `overflow` ends: `clip`, or with an ellipsis. */
373
548
  textOverflow: 'clip' | 'ellipsis';
374
549
  /** `aspect-ratio`: a box's width over its height, where its height is
@@ -388,16 +563,28 @@ export interface ComputedStyle {
388
563
  * its edges measured from the border box's top left, a null edge the
389
564
  * border box's own (CSS 2.1 11.1.2). Null for `auto`. */
390
565
  clip: ClipRect | null;
566
+ /** `clip-path`: the rectangle the element and all it holds show through
567
+ * (CSS Masking 1, 5.1); null for `none`, and for a shape this does not
568
+ * draw. */
569
+ clipPath: ClipPath | null;
391
570
  opacity: number;
392
571
  /** Where `translate` moves the box after layout (CSS Transforms 2): a
393
572
  * length or a percentage of its own border box across and down; null
394
573
  * for `none`. */
395
574
  translate: [Len, Len] | null;
396
- /** The translation in its `transform`, the one part of a transform
397
- * this draws — rotating, scaling and skewing are not; null for `none`.
398
- * Either makes the box a containing block and paints it with the
399
- * positioned boxes, as a transform does. */
400
- transformTranslate: [Len, Len] | null;
575
+ /** `rotate`: a turn in the plane of the page, in degrees; null for
576
+ * `none`, and for a turn about an axis in the page. */
577
+ rotate: number | null;
578
+ /** `scale`: across and down; null for `none`. */
579
+ scale: [number, number] | null;
580
+ /** `transform`: its functions in the order written, the ones out of the
581
+ * plane left out (`css/transform.ts`); null for `none`. Any of the four
582
+ * makes the box a containing block and a stacking context, and paints it
583
+ * with the positioned boxes (CSS Transforms 1, 3; `transformed`). */
584
+ transform: TransformFunction[] | null;
585
+ /** `transform-origin`: the point a transform turns and scales about,
586
+ * from the border box's top left, a percentage of its width and height. */
587
+ transformOrigin: [Len, Len];
401
588
  zIndex: number | 'auto';
402
589
  /** A keyword, a length to raise the box by, or a percentage of its own
403
590
  * line height. */
@@ -484,7 +671,7 @@ export interface ComputedStyle {
484
671
  backgroundImage: string | null;
485
672
  /** A background drawn rather than fetched: the first layer's linear
486
673
  * gradient, where it is one. */
487
- backgroundGradient: LinearGradient | null;
674
+ backgroundGradient: Gradient | null;
488
675
  /** How the first layer repeats across and down (CSS Backgrounds 3, 3.4):
489
676
  * one of a few pairs made once, so a style copies no array. */
490
677
  backgroundRepeat: BackgroundRepeat;
@@ -543,10 +730,17 @@ export interface ComputedStyle {
543
730
  * set it (`decorate`). No property: the cascade works them out. */
544
731
  underline: string | null;
545
732
  underlineStyle: 'solid' | 'double' | 'dotted' | 'dashed' | 'wavy';
546
- /** And the underline's thickness and offset, the box's that set it. */
733
+ /** And the underline's thickness and offset, the box's that set it: the
734
+ * thickness the one used, which `auto` takes from that box's font size
735
+ * (`usedThickness`), and the offset null where it is `auto`. */
547
736
  underlineThickness: number | null;
548
737
  underlineOffset: number | null;
549
738
  lineThrough: string | null;
739
+ /** And the line through's style and thickness, the box's that set it, as
740
+ * the underline's are. It has no offset to set: it is drawn where each
741
+ * font it crosses puts it (`paintRunRules`). */
742
+ lineThroughStyle: 'solid' | 'double' | 'dotted' | 'dashed' | 'wavy';
743
+ lineThroughThickness: number | null;
550
744
 
551
745
  // flex — handed to yoga rather than interpreted here
552
746
  flexDirection: 'row' | 'row-reverse' | 'column' | 'column-reverse';
@@ -653,6 +847,8 @@ export const INHERITED = [
653
847
  'fontFeatureSettings',
654
848
  'tabSize',
655
849
  'tabSizeIsLength',
850
+ 'orphans',
851
+ 'widows',
656
852
  'whiteSpace',
657
853
  'overflowWrap',
658
854
  'wordBreak',
@@ -664,6 +860,9 @@ export const INHERITED = [
664
860
  'listStylePosition',
665
861
  'listStyleImage',
666
862
  'cursor',
863
+ 'pointerEvents',
864
+ 'fill',
865
+ 'stroke',
667
866
  'borderCollapse',
668
867
  'borderSpacing',
669
868
  'borderSpacingY',
@@ -758,6 +957,9 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
758
957
  listStylePosition: 'outside',
759
958
  listStyleImage: null,
760
959
  cursor: null,
960
+ pointerEvents: 'auto',
961
+ fill: null,
962
+ stroke: null,
761
963
  borderCollapse: 'separate',
762
964
  // CSS's initial value; a `<table>` gets its 2px from the UA sheet, and
763
965
  // an anonymous table, which no sheet names, has none
@@ -793,6 +995,14 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
793
995
  webkitBoxAlign: null,
794
996
  webkitBoxFlex: 0,
795
997
  columns: 0,
998
+ columnCount: null,
999
+ columnWidth: null,
1000
+ columnGapNormal: true,
1001
+ orphans: 2,
1002
+ widows: 2,
1003
+ breakInside: 'auto',
1004
+ columnFill: 'balance',
1005
+ columnSpan: false,
796
1006
  textOverflow: 'clip',
797
1007
  aspectRatio: null,
798
1008
  objectFit: 'fill',
@@ -802,9 +1012,13 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
802
1012
  overflowX: 'visible',
803
1013
  overflowY: 'visible',
804
1014
  clip: null,
1015
+ clipPath: null,
805
1016
  opacity: 1,
806
1017
  translate: null,
807
- transformTranslate: null,
1018
+ rotate: null,
1019
+ scale: null,
1020
+ transform: null,
1021
+ transformOrigin: [{ pct: 50 }, { pct: 50 }],
808
1022
  zIndex: AUTO,
809
1023
  verticalAlign: 'baseline',
810
1024
 
@@ -894,6 +1108,8 @@ export function initialStyle(look: RootLook, scale = 1): ComputedStyle {
894
1108
  underlineThickness: null,
895
1109
  underlineOffset: null,
896
1110
  lineThrough: null,
1111
+ lineThroughStyle: 'solid',
1112
+ lineThroughThickness: null,
897
1113
 
898
1114
  flexDirection: 'row',
899
1115
  flexWrap: 'nowrap',
@@ -1003,6 +1219,8 @@ export function inherit(
1003
1219
  out.fontFeatureSettings = parent.fontFeatureSettings;
1004
1220
  out.tabSize = parent.tabSize;
1005
1221
  out.tabSizeIsLength = parent.tabSizeIsLength;
1222
+ out.orphans = parent.orphans;
1223
+ out.widows = parent.widows;
1006
1224
  out.whiteSpace = parent.whiteSpace;
1007
1225
  out.overflowWrap = parent.overflowWrap;
1008
1226
  out.wordBreak = parent.wordBreak;
@@ -1014,6 +1232,9 @@ export function inherit(
1014
1232
  out.listStylePosition = parent.listStylePosition;
1015
1233
  out.listStyleImage = parent.listStyleImage;
1016
1234
  out.cursor = parent.cursor;
1235
+ out.pointerEvents = parent.pointerEvents;
1236
+ out.fill = parent.fill;
1237
+ out.stroke = parent.stroke;
1017
1238
  out.borderCollapse = parent.borderCollapse;
1018
1239
  out.borderSpacing = parent.borderSpacing;
1019
1240
  out.borderSpacingY = parent.borderSpacingY;
@@ -1033,6 +1254,8 @@ export function inherit(
1033
1254
  out.underlineThickness = parent.underlineThickness;
1034
1255
  out.underlineOffset = parent.underlineOffset;
1035
1256
  out.lineThrough = parent.lineThrough;
1257
+ out.lineThroughStyle = parent.lineThroughStyle;
1258
+ out.lineThroughThickness = parent.lineThroughThickness;
1036
1259
  return out;
1037
1260
  }
1038
1261
 
@@ -1067,6 +1290,8 @@ export const FIRST_LINE_INHERITED = [
1067
1290
  'underlineThickness',
1068
1291
  'underlineOffset',
1069
1292
  'lineThrough',
1293
+ 'lineThroughStyle',
1294
+ 'lineThroughThickness',
1070
1295
  ] as const satisfies readonly (keyof ComputedStyle)[];
1071
1296
 
1072
1297
  /**
@@ -1142,6 +1367,15 @@ const BORDER_WIDTH_KEYWORDS: Record<string, number> = {
1142
1367
  thick: 5,
1143
1368
  };
1144
1369
 
1370
+ /** `-webkit-` names that are the multicol properties' own. */
1371
+ const MULTICOL_ALIASES: Record<string, string> = {
1372
+ '-webkit-columns': 'columns',
1373
+ '-webkit-column-count': 'column-count',
1374
+ '-webkit-column-width': 'column-width',
1375
+ '-webkit-column-gap': 'column-gap',
1376
+ '-webkit-column-break-inside': 'break-inside',
1377
+ };
1378
+
1145
1379
  /**
1146
1380
  * Apply one declaration to a style, in place.
1147
1381
  *
@@ -1157,7 +1391,12 @@ export function applyDeclaration(
1157
1391
  rawValue: string,
1158
1392
  ctx: UnitContext,
1159
1393
  ): void {
1160
- const name = prop.toLowerCase();
1394
+ const written = prop.toLowerCase();
1395
+ // the multicol properties under the names WebKit had them by, which
1396
+ // Blink still reads, each an alias of the property (`-webkit-column-
1397
+ // count` in its `css_properties.json5`): a page written for it alone
1398
+ // names no other
1399
+ const name = MULTICOL_ALIASES[written] ?? written;
1161
1400
  let value = rawValue.trim();
1162
1401
  if (!value) return;
1163
1402
  // a logical property is the physical one it stands for, the CSS-wide
@@ -1285,17 +1524,63 @@ export function applyDeclaration(
1285
1524
  case 'column-count':
1286
1525
  case 'column-width':
1287
1526
  case 'columns': {
1288
- // `auto`, or a count, a width or both of them
1527
+ // `auto`, or a count, a width or both of them, in either order; what
1528
+ // the shorthand leaves out is `auto`
1289
1529
  const bit = name === 'column-count' ? 1 : name === 'column-width' ? 2 : 3;
1290
- let set = 0;
1530
+ let count: number | null = null;
1531
+ let width: number | null = null;
1291
1532
  for (const part of splitValue(value)) {
1292
1533
  const v = part.toLowerCase();
1293
1534
  if (v === 'auto') continue;
1294
- if (/^\d+$/.test(v) && Number(v) >= 1) set |= 1;
1295
- else if (parseLength(part, ctx) !== null) set |= 2;
1296
- else return;
1535
+ if (/^\d+$/.test(v) && Number(v) >= 1) {
1536
+ if (count !== null || !(bit & 1)) return;
1537
+ count = Number(v);
1538
+ continue;
1539
+ }
1540
+ const len = parseLength(part, ctx);
1541
+ if (typeof len !== 'number' || !(len > 0)) return;
1542
+ if (width !== null || !(bit & 2)) return;
1543
+ width = len;
1544
+ }
1545
+ if (bit & 1) style.columnCount = count;
1546
+ if (bit & 2) style.columnWidth = width;
1547
+ style.columns =
1548
+ (style.columnCount !== null ? 1 : 0) |
1549
+ (style.columnWidth !== null ? 2 : 0);
1550
+ return;
1551
+ }
1552
+ case 'orphans':
1553
+ case 'widows': {
1554
+ // a whole number of lines, one or more
1555
+ const v = value.trim();
1556
+ if (!/^\+?\d+$/.test(v) || Number(v) < 1) return;
1557
+ style[name] = Number(v);
1558
+ return;
1559
+ }
1560
+ case '-webkit-column-span':
1561
+ case 'column-span': {
1562
+ const v = value.trim().toLowerCase();
1563
+ if (v === 'all') style.columnSpan = true;
1564
+ else if (v === 'none') style.columnSpan = false;
1565
+ return;
1566
+ }
1567
+ case 'column-fill': {
1568
+ const v = value.trim().toLowerCase();
1569
+ if (v === 'auto') style.columnFill = 'auto';
1570
+ else if (v === 'balance' || v === 'balance-all') {
1571
+ style.columnFill = 'balance';
1572
+ }
1573
+ return;
1574
+ }
1575
+ case 'break-inside':
1576
+ case 'page-break-inside': {
1577
+ const v = value.trim().toLowerCase();
1578
+ if (v === 'auto') style.breakInside = 'auto';
1579
+ else if (/^avoid(?:-page|-column|-region)?$/.test(v)) {
1580
+ // the legacy property knows `avoid` alone
1581
+ if (name === 'page-break-inside' && v !== 'avoid') return;
1582
+ style.breakInside = 'avoid';
1297
1583
  }
1298
- style.columns = (style.columns & ~bit) | (set & bit);
1299
1584
  return;
1300
1585
  }
1301
1586
  case '-webkit-box-flex': {
@@ -1534,6 +1819,12 @@ export function applyDeclaration(
1534
1819
  if (clip !== undefined) style.clip = clip;
1535
1820
  return;
1536
1821
  }
1822
+ case 'clip-path':
1823
+ case '-webkit-clip-path': {
1824
+ const path = parseClipPath(value, ctx);
1825
+ if (path !== undefined) style.clipPath = path;
1826
+ return;
1827
+ }
1537
1828
  case 'opacity': {
1538
1829
  const a = parseAlpha(value);
1539
1830
  if (a !== null) style.opacity = a;
@@ -1553,9 +1844,31 @@ export function applyDeclaration(
1553
1844
  style.translate = [x, y ?? 0];
1554
1845
  return;
1555
1846
  }
1556
- case 'transform': {
1557
- const moved = transformTranslation(value, ctx);
1558
- if (moved !== undefined) style.transformTranslate = moved;
1847
+ case 'rotate': {
1848
+ const turn = parseRotate(value);
1849
+ if (turn !== undefined) style.rotate = turn;
1850
+ return;
1851
+ }
1852
+ case 'scale': {
1853
+ const factors = parseScale(value);
1854
+ if (factors !== undefined) style.scale = factors;
1855
+ return;
1856
+ }
1857
+ case 'transform':
1858
+ case '-webkit-transform': {
1859
+ const list = parseTransform(value, ctx);
1860
+ if (list !== undefined) style.transform = list;
1861
+ return;
1862
+ }
1863
+ case 'transform-origin':
1864
+ case '-webkit-transform-origin': {
1865
+ // across and down, as a position is, and a depth nothing here has
1866
+ const parts = splitValue(value);
1867
+ if (parts.length === 3) {
1868
+ if (typeof parseLength(parts.pop()!, ctx) !== 'number') return;
1869
+ } else if (parts.length > 2) return;
1870
+ const origin = positionPair(parts, ctx);
1871
+ if (origin) style.transformOrigin = origin;
1559
1872
  return;
1560
1873
  }
1561
1874
  case 'visibility': {
@@ -1818,19 +2131,8 @@ export function applyDeclaration(
1818
2131
  return;
1819
2132
  }
1820
2133
  case 'border-radius': {
1821
- // one to four radii, and after a `/` one to four more for the
1822
- // vertical radii where they differ: elliptical corners
1823
- const halves = value.split('/');
1824
- if (halves.length > 2) return;
1825
- const [horizontal, vertical] = halves.map((half) =>
1826
- splitValue(half).map((p) => radiusOf(p, ctx)),
1827
- );
1828
- for (const parts of vertical ? [horizontal, vertical] : [horizontal]) {
1829
- if (!parts.length || parts.length > 4 || parts.includes(null)) return;
1830
- }
1831
- style.borderRadius = fourSides(horizontal as Len[]);
1832
- const y = vertical ? fourSides(vertical as Len[]) : null;
1833
- style.borderRadiusY = y && !sameRadii(style.borderRadius, y) ? y : null;
2134
+ const radii = parseRadii(value, ctx);
2135
+ if (radii) [style.borderRadius, style.borderRadiusY] = radii;
1834
2136
  return;
1835
2137
  }
1836
2138
 
@@ -2073,7 +2375,20 @@ export function applyDeclaration(
2073
2375
  )
2074
2376
  .filter(Boolean)
2075
2377
  .join(', ');
2076
- style.fontFamily = ctx.families ? ctx.families(list) : list;
2378
+ // A list that ends in no generic family ends in the document's own
2379
+ // font: where no face in the list matches, the text is set in the
2380
+ // user agent's default (CSS Fonts 4, 5.1), which for a browser is its
2381
+ // standard font and here is the one the document is set in. The text
2382
+ // engine's own answer for a name nothing has is its platform's pick,
2383
+ // Verdana under fontconfig: the Zen Garden's 216 sets its summary in
2384
+ // Montserrat alone, which no machine here has, and Chrome sets it in
2385
+ // Times where ours was Verdana, a fifth wider
2386
+ const last = names[names.length - 1].trim();
2387
+ const generic =
2388
+ !/^['"]/.test(last) && GENERIC_FAMILY.has(last.toLowerCase());
2389
+ const fallback = generic ? null : (ctx.fallbackFamily?.() ?? null);
2390
+ const full = fallback ? `${list}, ${fallback}` : list;
2391
+ style.fontFamily = ctx.families ? ctx.families(full) : full;
2077
2392
  return;
2078
2393
  }
2079
2394
  case 'font-size': {
@@ -2479,6 +2794,20 @@ export function applyDeclaration(
2479
2794
  style.cursor = splitCommas(value)[0]?.trim().toLowerCase() || null;
2480
2795
  return;
2481
2796
  }
2797
+ case 'pointer-events': {
2798
+ // `none`, or one of the values SVG gives it, which are `auto` on
2799
+ // anything that is not SVG's to draw
2800
+ const v = value.trim().toLowerCase();
2801
+ if (v === 'none') style.pointerEvents = 'none';
2802
+ else if (POINTER_EVENTS.has(v)) style.pointerEvents = 'auto';
2803
+ return;
2804
+ }
2805
+ case 'fill':
2806
+ case 'stroke': {
2807
+ const paint = svgPaint(value);
2808
+ if (paint !== null) style[name] = paint;
2809
+ return;
2810
+ }
2482
2811
 
2483
2812
  // --- lists --------------------------------------------------------------
2484
2813
  case 'list-style': {
@@ -2651,7 +2980,11 @@ export function applyDeclaration(
2651
2980
  const col = parts.length > 1 ? gap(parts[1]) : row;
2652
2981
  if (row === null || col === null) return;
2653
2982
  if (which !== 'column-gap') style.rowGap = row;
2654
- if (which !== 'row-gap') style.columnGap = col;
2983
+ if (which !== 'row-gap') {
2984
+ style.columnGap = col;
2985
+ const word = parts[parts.length - 1].toLowerCase();
2986
+ style.columnGapNormal = word === 'normal';
2987
+ }
2655
2988
  return;
2656
2989
  }
2657
2990
  case 'grid-template-columns':
@@ -3128,6 +3461,12 @@ function alignKeyword(value: string): string | null {
3128
3461
  return 'flex-end';
3129
3462
  case 'first baseline':
3130
3463
  return 'baseline';
3464
+ // By last baselines, which nothing here aligns: set where that falls
3465
+ // back to, the end (CSS Box Alignment 3, 4.2). Dropped, a row that asks
3466
+ // for it stretched its items, and a flex box among them was as tall as
3467
+ // the row, its lines spread down it.
3468
+ case 'last baseline':
3469
+ return 'flex-end';
3131
3470
  case 'center':
3132
3471
  return 'center';
3133
3472
  case 'stretch':
@@ -3799,7 +4138,7 @@ function backgroundImageOf(
3799
4138
  ctx: UnitContext,
3800
4139
  ): BackgroundImage | undefined {
3801
4140
  const v = text.trim();
3802
- if (IMAGE_FUNCTION.test(v.toLowerCase())) return parseLinearGradient(v, ctx);
4141
+ if (IMAGE_FUNCTION.test(v.toLowerCase())) return parseGradient(v, ctx);
3803
4142
  return parseUrl(v);
3804
4143
  }
3805
4144
 
@@ -3821,7 +4160,7 @@ function layerValues<T>(
3821
4160
  interface BackgroundLayer {
3822
4161
  color: string | null;
3823
4162
  image: string | null;
3824
- gradient: LinearGradient | null;
4163
+ gradient: Gradient | null;
3825
4164
  repeat: ComputedStyle['backgroundRepeat'];
3826
4165
  size: ComputedStyle['backgroundSize'];
3827
4166
  attachment: ComputedStyle['backgroundAttachment'];
@@ -3837,7 +4176,7 @@ interface BackgroundLayer {
3837
4176
  * is dropped rather than resetting the background it meant to replace
3838
4177
  * (CSS 2.1 4.2). What CSS3 adds is read too, so that a declaration a
3839
4178
  * browser keeps is kept: `/ cover` after the position, `space` and `round`,
3840
- * a gradient — drawn as nothing, over the layer's colour.
4179
+ * and a gradient, over the layer's colour.
3841
4180
  */
3842
4181
  function readBackgroundLayer(
3843
4182
  text: string,
@@ -3872,7 +4211,7 @@ function readBackgroundLayer(
3872
4211
  const url = parseUrl(part);
3873
4212
  if (url === undefined) return null;
3874
4213
  layer.image = url;
3875
- } else if (v !== 'none') layer.gradient = parseLinearGradient(part, ctx);
4214
+ } else if (v !== 'none') layer.gradient = parseGradient(part, ctx);
3876
4215
  i += 1;
3877
4216
  } else if (v === 'repeat-x' || v === 'repeat-y' || REPEATS.has(v)) {
3878
4217
  if (!once(2)) return null;
@@ -3936,10 +4275,8 @@ function readBackgroundLayer(
3936
4275
 
3937
4276
  /**
3938
4277
  * A `linear-gradient()`, or null for any other image function and for one
3939
- * that is no gradient: a direction by angle, by side or by corner, with a
3940
- * colour interpolation method (`in oklab`, which Tailwind 4 writes) read and
3941
- * not honoured, and at least two stops, each with no position, one, or two;
3942
- * a bare percentage between stops, a hint, is passed over.
4278
+ * that is no gradient: a direction by angle, by side or by corner, and its
4279
+ * colour stops.
3943
4280
  */
3944
4281
  function parseLinearGradient(
3945
4282
  text: string,
@@ -3948,14 +4285,13 @@ function parseLinearGradient(
3948
4285
  const m = /^linear-gradient\((.*)\)$/is.exec(text.trim());
3949
4286
  if (!m) return null;
3950
4287
  const args = splitCommas(m[1]).map((a) => a.trim());
3951
- const out: LinearGradient = { angle: Math.PI, corner: null, stops: [] };
3952
- const first = (args[0] ?? '')
3953
- .toLowerCase()
3954
- .replace(
3955
- /\bin\s+[a-z-]+(?:\s+(?:shorter|longer|increasing|decreasing)\s+hue)?/,
3956
- '',
3957
- )
3958
- .trim();
4288
+ const out: LinearGradient = {
4289
+ kind: 'linear',
4290
+ angle: Math.PI,
4291
+ corner: null,
4292
+ stops: [],
4293
+ };
4294
+ const first = withoutInterpolation(args[0] ?? '');
3959
4295
  let from = 0;
3960
4296
  if (!first || first.startsWith('to ')) {
3961
4297
  from = 1;
@@ -3991,7 +4327,120 @@ function parseLinearGradient(
3991
4327
  from = 1;
3992
4328
  }
3993
4329
  }
3994
- for (const arg of args.slice(from)) {
4330
+ const stops = parseStops(args.slice(from), ctx);
4331
+ if (!stops) return null;
4332
+ out.stops = stops;
4333
+ return out;
4334
+ }
4335
+
4336
+ /**
4337
+ * A `radial-gradient()` (CSS Images 3, 3.2.1), or null for anything else:
4338
+ * an ending shape, `circle` or `ellipse`, and its size, in either order —
4339
+ * a keyword for the side or corner of the box it reaches, one length for a
4340
+ * circle's radius, or two lengths or percentages for an ellipse's — then
4341
+ * `at` and its centre, a position as `background-position` writes one;
4342
+ * each of the three may be left out, for an ellipse through the farthest
4343
+ * corner from the middle of the box. A colour interpolation method is read
4344
+ * and not honoured, and the stops are a linear gradient's, spaced along
4345
+ * the ray from the centre to the shape's edge.
4346
+ */
4347
+ function parseRadialGradient(
4348
+ text: string,
4349
+ ctx: UnitContext,
4350
+ ): RadialGradient | null {
4351
+ const m = /^radial-gradient\((.*)\)$/is.exec(text.trim());
4352
+ if (!m) return null;
4353
+ const args = splitCommas(m[1]).map((a) => a.trim());
4354
+ const out: RadialGradient = {
4355
+ kind: 'radial',
4356
+ circle: false,
4357
+ extent: null,
4358
+ radii: null,
4359
+ at: [{ pct: 50 }, { pct: 50 }],
4360
+ stops: [],
4361
+ };
4362
+ const first = withoutInterpolation(args[0] ?? '');
4363
+ let from = 0;
4364
+ if (!first) from = 1;
4365
+ else if (parseColor(splitValue(first)[0]) === null) {
4366
+ from = 1;
4367
+ const words = splitValue(first);
4368
+ const at = words.indexOf('at');
4369
+ if (at >= 0) {
4370
+ const position = positionPair(words.slice(at + 1), ctx);
4371
+ if (!position) return null;
4372
+ out.at = position;
4373
+ }
4374
+ let shape: string | null = null;
4375
+ const sizes: Len[] = [];
4376
+ // the size is one run of words, the shape's keyword before or after it
4377
+ let sized = false;
4378
+ for (const word of at >= 0 ? words.slice(0, at) : words) {
4379
+ if (word === 'circle' || word === 'ellipse') {
4380
+ if (shape) return null;
4381
+ shape = word;
4382
+ if (sizes.length || out.extent) sized = true;
4383
+ continue;
4384
+ }
4385
+ if (sized || out.extent) return null;
4386
+ if (RADIAL_EXTENTS.has(word)) {
4387
+ if (sizes.length) return null;
4388
+ out.extent = word as RadialExtent;
4389
+ continue;
4390
+ }
4391
+ const len = parseLength(word, ctx);
4392
+ if (len === null || len === AUTO || !notNegative(len)) return null;
4393
+ sizes.push(len);
4394
+ }
4395
+ if (sizes.length === 1) {
4396
+ // a circle's radius is a length: a percentage of what, the spec asks
4397
+ const [r] = sizes;
4398
+ if (shape === 'ellipse' || typeof r !== 'number') {
4399
+ return null;
4400
+ }
4401
+ out.circle = true;
4402
+ out.radii = [r, r];
4403
+ } else if (sizes.length === 2) {
4404
+ if (shape === 'circle') return null;
4405
+ out.radii = [sizes[0], sizes[1]];
4406
+ } else if (sizes.length) return null;
4407
+ else {
4408
+ out.circle = shape === 'circle';
4409
+ out.extent ??= 'farthest-corner';
4410
+ }
4411
+ }
4412
+ const stops = parseStops(args.slice(from), ctx);
4413
+ if (!stops) return null;
4414
+ out.stops = stops;
4415
+ return out;
4416
+ }
4417
+
4418
+ const RADIAL_EXTENTS = new Set([
4419
+ 'closest-side',
4420
+ 'closest-corner',
4421
+ 'farthest-side',
4422
+ 'farthest-corner',
4423
+ ]);
4424
+
4425
+ /** A gradient's first argument, lower-cased, without the colour
4426
+ * interpolation method CSS Images 4 adds to it (`in oklab`, which
4427
+ * Tailwind 4 writes): read, and not honoured. */
4428
+ function withoutInterpolation(arg: string): string {
4429
+ return arg
4430
+ .toLowerCase()
4431
+ .replace(
4432
+ /\bin\s+[a-z-]+(?:\s+(?:shorter|longer|increasing|decreasing)\s+hue)?/,
4433
+ '',
4434
+ )
4435
+ .trim();
4436
+ }
4437
+
4438
+ /** A gradient's colour stops, at least two, each with no position, one or
4439
+ * two; a bare length between two stops, a hint, is passed over. Null
4440
+ * where an argument is none of those. */
4441
+ function parseStops(args: string[], ctx: UnitContext): GradientStop[] | null {
4442
+ const stops: GradientStop[] = [];
4443
+ for (const arg of args) {
3995
4444
  const parts = splitValue(arg);
3996
4445
  const color = parseColor(parts[0] ?? '');
3997
4446
  if (color === null) {
@@ -4002,14 +4451,19 @@ function parseLinearGradient(
4002
4451
  if (parts.length > 3) return null;
4003
4452
  const at = parts.slice(1).map((p) => parseLength(p, ctx));
4004
4453
  if (at.some((p) => p === null || p === AUTO)) return null;
4005
- if (!at.length) out.stops.push({ color, at: null });
4006
- for (const p of at) out.stops.push({ color, at: p as Len });
4454
+ if (!at.length) stops.push({ color, at: null });
4455
+ for (const p of at) stops.push({ color, at: p as Len });
4007
4456
  }
4008
- return out.stops.length >= 2 ? out : null;
4457
+ return stops.length >= 2 ? stops : null;
4458
+ }
4459
+
4460
+ /** A gradient function's image, where it is one this draws. */
4461
+ function parseGradient(text: string, ctx: UnitContext): Gradient | null {
4462
+ return parseLinearGradient(text, ctx) ?? parseRadialGradient(text, ctx);
4009
4463
  }
4010
4464
 
4011
4465
  /** The images CSS3 has beyond `url()`, which a layer may name: a linear
4012
- * gradient is drawn, and the rest draw as nothing. */
4466
+ * and a radial gradient are drawn, and the rest draw as nothing. */
4013
4467
  const IMAGE_FUNCTION =
4014
4468
  /^(?:-(?:webkit|moz|o|ms)-)?(?:(?:repeating-)?(?:linear|radial|conic)-gradient|gradient|image-set|cross-fade|element|paint)\(/;
4015
4469
 
@@ -4050,87 +4504,6 @@ function whiteSpaceOf(collapse: Collapse, wrap: boolean): WhiteSpace {
4050
4504
  return 'pre-line';
4051
4505
  }
4052
4506
 
4053
- /**
4054
- * The translation a `transform` makes: the sum of its translate functions'
4055
- * and its matrices' — `translate(-50%, -50%)`, Tailwind 3's `translate(
4056
- * var(--tw-translate-x), var(--tw-translate-y)) rotate(…) skewX(…) …` —
4057
- * with the functions that rotate, scale, skew or add perspective read and
4058
- * not drawn. Null for `none`, and undefined for what is no transform list,
4059
- * which drops the declaration.
4060
- */
4061
- function transformTranslation(
4062
- value: string,
4063
- ctx: UnitContext,
4064
- ): [Len, Len] | null | undefined {
4065
- if (value.trim().toLowerCase() === 'none') return null;
4066
- let x: Len = 0;
4067
- let y: Len = 0;
4068
- const parts = splitValue(value);
4069
- if (!parts.length) return undefined;
4070
- for (const part of parts) {
4071
- const m = /^([a-z0-9]+)\((.*)\)$/is.exec(part.trim());
4072
- if (!m) return undefined;
4073
- const name = m[1].toLowerCase();
4074
- const args = splitCommas(m[2]).map((a) => a.trim());
4075
- let dx: Len | null = 0;
4076
- let dy: Len | null = 0;
4077
- if (name === 'translate' || name === 'translate3d') {
4078
- dx = parseLength(args[0] ?? '', ctx);
4079
- dy = args[1] === undefined ? 0 : parseLength(args[1], ctx);
4080
- } else if (name === 'translatex') dx = parseLength(args[0] ?? '', ctx);
4081
- else if (name === 'translatey') dy = parseLength(args[0] ?? '', ctx);
4082
- else if (name === 'matrix' || name === 'matrix3d') {
4083
- // the translation is the last column's
4084
- const n = args.map(Number);
4085
- if (n.length !== (name === 'matrix' ? 6 : 16) || n.some(isNaN)) {
4086
- return undefined;
4087
- }
4088
- [dx, dy] = name === 'matrix' ? [n[4], n[5]] : [n[12], n[13]];
4089
- } else if (!TRANSFORMS.has(name)) return undefined;
4090
- if (dx === null || dx === AUTO || dy === null || dy === AUTO) {
4091
- return undefined;
4092
- }
4093
- const sx = addLen(x, dx);
4094
- const sy = addLen(y, dy);
4095
- if (sx === null || sy === null) return undefined;
4096
- x = sx;
4097
- y = sy;
4098
- }
4099
- return [x, y];
4100
- }
4101
-
4102
- /** The transform functions read and not drawn. */
4103
- const TRANSFORMS = new Set([
4104
- 'translatez',
4105
- 'rotate',
4106
- 'rotatex',
4107
- 'rotatey',
4108
- 'rotatez',
4109
- 'rotate3d',
4110
- 'scale',
4111
- 'scalex',
4112
- 'scaley',
4113
- 'scalez',
4114
- 'scale3d',
4115
- 'skew',
4116
- 'skewx',
4117
- 'skewy',
4118
- 'perspective',
4119
- ]);
4120
-
4121
- /** Two lengths added, a percentage and a length kept apart; null where
4122
- * one is a `min()` or a `max()`, which a sum cannot be kept of. */
4123
- function addLen(a: Len, b: Len): Len | null {
4124
- if (a === AUTO || b === AUTO) return null;
4125
- if (typeof a === 'number' && typeof b === 'number') return a + b;
4126
- if (a === 0) return b;
4127
- if (b === 0) return a;
4128
- const pa: Pct = typeof a === 'number' ? { pct: 0, px: a } : a;
4129
- const pb: Pct = typeof b === 'number' ? { pct: 0, px: b } : b;
4130
- if (pa.of || pb.of) return null;
4131
- return { pct: pa.pct + pb.pct, px: (pa.px ?? 0) + (pb.px ?? 0) };
4132
- }
4133
-
4134
4507
  /** An intrinsic size keyword, with the prefixes browsers still read, or
4135
4508
  * `fit-content()` of a length or a percentage (CSS Sizing 3, 3.1) — a
4136
4509
  * width's, where a height's is its content height as the keyword is;
@@ -4769,6 +5142,20 @@ function splitTopLevelSlash(value: string): string[] {
4769
5142
 
4770
5143
  // --- logical properties ----------------------------------------------------
4771
5144
 
5145
+ /** `pointer-events`' values besides `none` (CSS UI 4, SVG 2 15.6). */
5146
+ const POINTER_EVENTS = new Set([
5147
+ 'auto',
5148
+ 'bounding-box',
5149
+ 'visiblepainted',
5150
+ 'visiblefill',
5151
+ 'visiblestroke',
5152
+ 'visible',
5153
+ 'painted',
5154
+ 'fill',
5155
+ 'stroke',
5156
+ 'all',
5157
+ ]);
5158
+
4772
5159
  const OBJECT_FITS = new Set(['fill', 'contain', 'cover', 'none', 'scale-down']);
4773
5160
 
4774
5161
  /**
@@ -4860,6 +5247,29 @@ function radiusOf(text: string, ctx: UnitContext): Len | null {
4860
5247
  return len.pct < 0 && !len.px && !len.of ? null : len;
4861
5248
  }
4862
5249
 
5250
+ /**
5251
+ * `border-radius`'s value: one to four radii, and after a `/` one to four
5252
+ * more for the vertical radii where they differ, elliptical corners — the
5253
+ * radii across, and the ones down, null where they are the same values.
5254
+ * Null for anything else.
5255
+ */
5256
+ function parseRadii(
5257
+ value: string,
5258
+ ctx: UnitContext,
5259
+ ): [[Len, Len, Len, Len], [Len, Len, Len, Len] | null] | null {
5260
+ const halves = value.split('/');
5261
+ if (halves.length > 2) return null;
5262
+ const [horizontal, vertical] = halves.map((half) =>
5263
+ splitValue(half).map((p) => radiusOf(p, ctx)),
5264
+ );
5265
+ for (const parts of vertical ? [horizontal, vertical] : [horizontal]) {
5266
+ if (!parts.length || parts.length > 4 || parts.includes(null)) return null;
5267
+ }
5268
+ const across = fourSides(horizontal as Len[]);
5269
+ const down = vertical ? fourSides(vertical as Len[]) : null;
5270
+ return [across, down && !sameRadii(across, down) ? down : null];
5271
+ }
5272
+
4863
5273
  /** Whether two corners' radii are the same values. */
4864
5274
  function sameRadii(a: readonly Len[], b: readonly Len[]): boolean {
4865
5275
  for (let i = 0; i < 4; i += 1) {
@@ -5102,10 +5512,15 @@ const INHERITED_NAMES = new Set<string>([
5102
5512
  'list-style-position',
5103
5513
  'list-style-image',
5104
5514
  'cursor',
5515
+ 'pointer-events',
5516
+ 'fill',
5517
+ 'stroke',
5105
5518
  'border-collapse',
5106
5519
  'border-spacing',
5107
5520
  'empty-cells',
5108
5521
  'quotes',
5522
+ 'orphans',
5523
+ 'widows',
5109
5524
  ]);
5110
5525
 
5111
5526
  export function isInherited(name: string): boolean {
@@ -5260,6 +5675,9 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
5260
5675
  'list-style-position': ['listStylePosition'],
5261
5676
  'list-style-image': ['listStyleImage'],
5262
5677
  cursor: ['cursor'],
5678
+ 'pointer-events': ['pointerEvents'],
5679
+ fill: ['fill'],
5680
+ stroke: ['stroke'],
5263
5681
  'border-collapse': ['borderCollapse'],
5264
5682
  'caption-side': ['captionSide'],
5265
5683
  'empty-cells': ['emptyCells'],
@@ -5328,9 +5746,16 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
5328
5746
  '-webkit-box-pack': ['webkitBoxPack'],
5329
5747
  '-webkit-box-align': ['webkitBoxAlign'],
5330
5748
  '-webkit-box-flex': ['webkitBoxFlex'],
5331
- columns: ['columns'],
5332
- 'column-count': ['columns'],
5333
- 'column-width': ['columns'],
5749
+ columns: ['columns', 'columnCount', 'columnWidth'],
5750
+ 'column-count': ['columns', 'columnCount'],
5751
+ 'column-width': ['columns', 'columnWidth'],
5752
+ orphans: ['orphans'],
5753
+ widows: ['widows'],
5754
+ 'break-inside': ['breakInside'],
5755
+ 'column-fill': ['columnFill'],
5756
+ 'column-span': ['columnSpan'],
5757
+ '-webkit-column-span': ['columnSpan'],
5758
+ 'page-break-inside': ['breakInside'],
5334
5759
  'text-overflow': ['textOverflow'],
5335
5760
  // a flex box's and its items', and a grid's
5336
5761
  'flex-direction': ['flexDirection'],
@@ -5345,12 +5770,12 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
5345
5770
  'flex-shrink': ['flexShrink'],
5346
5771
  'flex-basis': ['flexBasis'],
5347
5772
  order: ['order'],
5348
- gap: ['rowGap', 'columnGap'],
5773
+ gap: ['rowGap', 'columnGap', 'columnGapNormal'],
5349
5774
  'row-gap': ['rowGap'],
5350
- 'column-gap': ['columnGap'],
5351
- 'grid-gap': ['rowGap', 'columnGap'],
5775
+ 'column-gap': ['columnGap', 'columnGapNormal'],
5776
+ 'grid-gap': ['rowGap', 'columnGap', 'columnGapNormal'],
5352
5777
  'grid-row-gap': ['rowGap'],
5353
- 'grid-column-gap': ['columnGap'],
5778
+ 'grid-column-gap': ['columnGap', 'columnGapNormal'],
5354
5779
  'grid-template-columns': ['gridColumns'],
5355
5780
  'grid-template-rows': ['gridRows'],
5356
5781
  'grid-auto-rows': ['gridAutoRows'],
@@ -5445,11 +5870,18 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
5445
5870
  clear: ['clear'],
5446
5871
  overflow: ['overflowX', 'overflowY'],
5447
5872
  clip: ['clip'],
5873
+ 'clip-path': ['clipPath'],
5874
+ '-webkit-clip-path': ['clipPath'],
5448
5875
  'overflow-x': ['overflowX'],
5449
5876
  'overflow-y': ['overflowY'],
5450
5877
  opacity: ['opacity'],
5451
5878
  translate: ['translate'],
5452
- transform: ['transformTranslate'],
5879
+ rotate: ['rotate'],
5880
+ scale: ['scale'],
5881
+ transform: ['transform'],
5882
+ '-webkit-transform': ['transform'],
5883
+ 'transform-origin': ['transformOrigin'],
5884
+ '-webkit-transform-origin': ['transformOrigin'],
5453
5885
  'z-index': ['zIndex'],
5454
5886
  'vertical-align': ['verticalAlign'],
5455
5887
  'text-decoration': [
@@ -5512,7 +5944,7 @@ export function decorate(style: ComputedStyle): void {
5512
5944
  style.color,
5513
5945
  );
5514
5946
  style.underlineStyle = style.textDecorationStyle;
5515
- style.underlineThickness = style.textDecorationThickness;
5947
+ style.underlineThickness = usedThickness(style);
5516
5948
  style.underlineOffset = style.textUnderlineOffset;
5517
5949
  }
5518
5950
  if (lines.includes('line-through')) {
@@ -5520,9 +5952,30 @@ export function decorate(style: ComputedStyle): void {
5520
5952
  style.textDecorationColor ?? 'currentColor',
5521
5953
  style.color,
5522
5954
  );
5955
+ style.lineThroughStyle = style.textDecorationStyle;
5956
+ style.lineThroughThickness = usedThickness(style);
5523
5957
  }
5524
5958
  }
5525
5959
 
5960
+ /**
5961
+ * How thick the lines a box decorates its text with are. `auto` leaves it
5962
+ * to the user agent, which is to take it from the font where the font says
5963
+ * (CSS Text Decoration 4, 2.4.1); neither text engine here reports a face's
5964
+ * underline, so it is a part of the font size, Blink's tenth — of the size
5965
+ * of the box that set the decoration, which draws one line of one thickness
5966
+ * through everything inside it (2.9). A pixel for every size drew the dots
5967
+ * under the `<abbr>` of a 140px title as a hairline. `from-font` is `auto`
5968
+ * for the same want of a metric, a length is itself on whole pixels, and
5969
+ * no line is thinner than one.
5970
+ */
5971
+ function usedThickness(style: ComputedStyle): number {
5972
+ const length = style.textDecorationThickness;
5973
+ return Math.max(
5974
+ 1,
5975
+ length === null ? style.fontSize / 10 : Math.round(length),
5976
+ );
5977
+ }
5978
+
5526
5979
  /**
5527
5980
  * The absolutely positioned styles that were inline-level before they were
5528
5981
  * made blocks: where such a box would have been in flow is on its line,
@@ -5638,3 +6091,55 @@ export function blockify(style: ComputedStyle, inFlexContainer: boolean): void {
5638
6091
  return;
5639
6092
  }
5640
6093
  }
6094
+
6095
+ /**
6096
+ * The `display` a `<button>` is laid out with, which is not always the one
6097
+ * it was given (HTML's rendering section, button layout, 15.5.3): a flex
6098
+ * box or a grid is that, and of the rest, "a value such that the outer
6099
+ * display type is 'inline'" behaves as `inline-block` and anything else as
6100
+ * `flow-root`. Blink computes them so — `inline-block` for `inline` and
6101
+ * `inline-table`, `block` for `table` and `list-item` — and makes the one
6102
+ * block flow of each; the formatting context is the layout's to give every
6103
+ * button that is a block (`establishesBFC`).
6104
+ *
6105
+ * Left an inline box, a button around a block was broken in two around it,
6106
+ * as any inline box is: as wide as the line, its `height` applied to
6107
+ * nothing, and its border and padding drawn above and below the line it
6108
+ * opened. A table's part is left what it is, where the two disagree: a
6109
+ * block to the text, an inline-block in Blink. After `blockify`, since a
6110
+ * float, an absolute box and a flex item are blocks first.
6111
+ */
6112
+ export function settleButton(style: ComputedStyle): void {
6113
+ switch (style.display) {
6114
+ case 'inline':
6115
+ case 'inline-table':
6116
+ style.display = 'inline-block';
6117
+ return;
6118
+ case 'list-item':
6119
+ case 'table':
6120
+ style.display = 'block';
6121
+ return;
6122
+ default:
6123
+ return;
6124
+ }
6125
+ }
6126
+
6127
+ /** The generic font families, which a list ending in one falls back
6128
+ * through already, in the text engine (CSS Fonts 4, 4.2). */
6129
+ const GENERIC_FAMILY = new Set([
6130
+ 'serif',
6131
+ 'sans-serif',
6132
+ 'monospace',
6133
+ 'cursive',
6134
+ 'fantasy',
6135
+ 'system-ui',
6136
+ 'ui-serif',
6137
+ 'ui-sans-serif',
6138
+ 'ui-monospace',
6139
+ 'ui-rounded',
6140
+ 'math',
6141
+ 'emoji',
6142
+ 'fangsong',
6143
+ '-apple-system',
6144
+ 'blinkmacsystemfont',
6145
+ ]);