@react-x11/components 0.11.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.
- package/dist/flow/node.d.ts +14 -6
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +82 -64
- package/dist/flow/node.js.map +1 -1
- package/dist/html/controls.d.ts +26 -7
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +28 -3
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +19 -6
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/color.d.ts +19 -0
- package/dist/html/css/color.d.ts.map +1 -1
- package/dist/html/css/color.js +86 -0
- package/dist/html/css/color.js.map +1 -1
- package/dist/html/css/parse.d.ts +11 -0
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +114 -4
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +38 -1
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +326 -8
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +14 -0
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/css/values.d.ts +10 -1
- package/dist/html/css/values.d.ts.map +1 -1
- package/dist/html/css/values.js +27 -2
- package/dist/html/css/values.js.map +1 -1
- package/dist/html/css/vars.d.ts +5 -0
- package/dist/html/css/vars.d.ts.map +1 -1
- package/dist/html/css/vars.js +2 -2
- package/dist/html/css/vars.js.map +1 -1
- package/dist/html/dom.d.ts.map +1 -1
- package/dist/html/dom.js +35 -2
- package/dist/html/dom.js.map +1 -1
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +38 -1
- package/dist/html/index.js.map +1 -1
- package/dist/html/layout/block.d.ts +11 -2
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +181 -18
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +34 -6
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +35 -14
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/cache.d.ts +2 -2
- package/dist/html/layout/cache.d.ts.map +1 -1
- package/dist/html/layout/cache.js +8 -3
- package/dist/html/layout/cache.js.map +1 -1
- package/dist/html/layout/floats.d.ts +4 -0
- package/dist/html/layout/floats.d.ts.map +1 -1
- package/dist/html/layout/floats.js +26 -0
- package/dist/html/layout/floats.js.map +1 -1
- package/dist/html/layout/inline.d.ts +26 -2
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +1222 -115
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +33 -0
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +512 -51
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +53 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +546 -180
- package/dist/html/paint.js.map +1 -1
- package/dist/html/surfaces.d.ts +7 -2
- package/dist/html/surfaces.d.ts.map +1 -1
- package/dist/html/surfaces.js +16 -0
- package/dist/html/surfaces.js.map +1 -1
- package/dist/internal/window.d.ts +22 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +35 -8
- package/dist/internal/window.js.map +1 -1
- package/dist/richtext/node.d.ts +7 -0
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js.map +1 -1
- package/package.json +5 -3
- package/src/flow/node.ts +80 -64
- package/src/html/controls.ts +50 -9
- package/src/html/css/cascade.ts +19 -6
- package/src/html/css/color.ts +87 -0
- package/src/html/css/parse.ts +107 -4
- package/src/html/css/style.ts +343 -8
- package/src/html/css/ua.ts +14 -0
- package/src/html/css/values.ts +31 -3
- package/src/html/css/vars.ts +2 -2
- package/src/html/dom.ts +38 -2
- package/src/html/index.ts +39 -1
- package/src/html/layout/block.ts +228 -16
- package/src/html/layout/boxes.ts +55 -21
- package/src/html/layout/cache.ts +8 -3
- package/src/html/layout/floats.ts +24 -0
- package/src/html/layout/inline.ts +1435 -108
- package/src/html/node.ts +535 -54
- package/src/html/paint.ts +650 -241
- package/src/html/surfaces.ts +21 -1
- package/src/internal/window.ts +36 -8
- package/src/richtext/node.ts +7 -0
package/src/html/css/style.ts
CHANGED
|
@@ -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
|
|
@@ -505,6 +519,10 @@ export interface ComputedStyle {
|
|
|
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;
|
|
@@ -1906,6 +1950,67 @@ export function applyDeclaration(
|
|
|
1906
1950
|
return;
|
|
1907
1951
|
}
|
|
1908
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
|
+
|
|
1909
2014
|
// --- generated content --------------------------------------------------
|
|
1910
2015
|
case 'content': {
|
|
1911
2016
|
const parsed = parseContent(value);
|
|
@@ -2013,7 +2118,13 @@ export function applyDeclaration(
|
|
|
2013
2118
|
style.lineHeight = len;
|
|
2014
2119
|
style.lineHeightIsLength = true;
|
|
2015
2120
|
} else if (len && typeof len === 'object') {
|
|
2016
|
-
|
|
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);
|
|
2017
2128
|
if (px < 0) return;
|
|
2018
2129
|
style.lineHeight = px;
|
|
2019
2130
|
style.lineHeightIsLength = true;
|
|
@@ -2462,7 +2573,12 @@ export function applyDeclaration(
|
|
|
2462
2573
|
return;
|
|
2463
2574
|
}
|
|
2464
2575
|
case 'align-content': {
|
|
2465
|
-
|
|
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);
|
|
2466
2582
|
if (v) style.alignContent = v as ComputedStyle['alignContent'];
|
|
2467
2583
|
return;
|
|
2468
2584
|
}
|
|
@@ -2697,6 +2813,30 @@ export function applyDeclaration(
|
|
|
2697
2813
|
}
|
|
2698
2814
|
return;
|
|
2699
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
|
+
}
|
|
2700
2840
|
case 'place-items':
|
|
2701
2841
|
case 'place-self': {
|
|
2702
2842
|
const parts = splitValue(value);
|
|
@@ -3144,6 +3284,157 @@ export const NO_BORDER_IMAGE: BorderImage = {
|
|
|
3144
3284
|
repeat: ['stretch', 'stretch'],
|
|
3145
3285
|
};
|
|
3146
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
|
+
|
|
3147
3438
|
/** One to four values, one a side as `margin` takes them; null where one
|
|
3148
3439
|
* is not a value. */
|
|
3149
3440
|
function sidesOf<T>(
|
|
@@ -3338,6 +3629,15 @@ function repeatPair(x: RepeatMode, y: RepeatMode): BackgroundRepeat {
|
|
|
3338
3629
|
|
|
3339
3630
|
const REPEAT = repeatPair('repeat', 'repeat');
|
|
3340
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
|
+
|
|
3341
3641
|
function applyBorderShorthand(
|
|
3342
3642
|
style: ComputedStyle,
|
|
3343
3643
|
name: string,
|
|
@@ -4026,12 +4326,10 @@ function applyFontShorthand(
|
|
|
4026
4326
|
style.lineHeight = 'normal';
|
|
4027
4327
|
style.lineHeightIsLength = false;
|
|
4028
4328
|
applyDeclaration(style, parent, 'font-size', sizeText, ctx);
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
});
|
|
4034
|
-
}
|
|
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);
|
|
4035
4333
|
applyDeclaration(style, parent, 'font-family', family, ctx);
|
|
4036
4334
|
}
|
|
4037
4335
|
|
|
@@ -4751,6 +5049,7 @@ function camel(name: string): string {
|
|
|
4751
5049
|
|
|
4752
5050
|
const INHERITED_NAMES = new Set<string>([
|
|
4753
5051
|
'color',
|
|
5052
|
+
'color-scheme',
|
|
4754
5053
|
'-webkit-text-fill-color',
|
|
4755
5054
|
'font',
|
|
4756
5055
|
'font-family',
|
|
@@ -4801,6 +5100,11 @@ export function initialOne(
|
|
|
4801
5100
|
initial: ComputedStyle,
|
|
4802
5101
|
name: string,
|
|
4803
5102
|
): void {
|
|
5103
|
+
const list = MASK_LISTS[name];
|
|
5104
|
+
if (list) {
|
|
5105
|
+
style.mask = { ...style.mask, [list]: initial.mask[list] };
|
|
5106
|
+
return;
|
|
5107
|
+
}
|
|
4804
5108
|
const keys = INHERIT_TARGETS[name];
|
|
4805
5109
|
if (!keys) return;
|
|
4806
5110
|
for (const key of keys) {
|
|
@@ -4813,6 +5117,11 @@ function inheritOne(
|
|
|
4813
5117
|
parent: ComputedStyle,
|
|
4814
5118
|
name: string,
|
|
4815
5119
|
): void {
|
|
5120
|
+
const list = MASK_LISTS[name];
|
|
5121
|
+
if (list) {
|
|
5122
|
+
style.mask = { ...style.mask, [list]: parent.mask[list] };
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
4816
5125
|
const keys = INHERIT_TARGETS[name];
|
|
4817
5126
|
if (!keys) return;
|
|
4818
5127
|
// A border colour left to `currentColor` inherits as the keyword and
|
|
@@ -4828,8 +5137,30 @@ const sides = (
|
|
|
4828
5137
|
make: (side: (typeof SIDES)[number]) => keyof ComputedStyle,
|
|
4829
5138
|
): (keyof ComputedStyle)[] => SIDES.map(make);
|
|
4830
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
|
+
|
|
4831
5161
|
const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
4832
5162
|
color: ['color'],
|
|
5163
|
+
'color-scheme': ['colorScheme'],
|
|
4833
5164
|
'-webkit-text-fill-color': ['textFillColor'],
|
|
4834
5165
|
'background-clip': [
|
|
4835
5166
|
'backgroundClipText',
|
|
@@ -4843,6 +5174,8 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
4843
5174
|
],
|
|
4844
5175
|
'background-origin': ['backgroundOrigin', 'backgroundOrigins'],
|
|
4845
5176
|
'border-image': ['borderImage'],
|
|
5177
|
+
mask: ['mask'],
|
|
5178
|
+
'-webkit-mask': ['mask'],
|
|
4846
5179
|
'border-image-source': ['borderImage'],
|
|
4847
5180
|
'border-image-slice': ['borderImage'],
|
|
4848
5181
|
'border-image-width': ['borderImage'],
|
|
@@ -4923,6 +5256,8 @@ const INHERIT_TARGETS: Record<string, readonly (keyof ComputedStyle)[]> = {
|
|
|
4923
5256
|
'min-height': ['minHeight', 'minHeightKeyword'],
|
|
4924
5257
|
'max-height': ['maxHeight', 'maxHeightKeyword'],
|
|
4925
5258
|
'box-sizing': ['boxSizing'],
|
|
5259
|
+
'box-decoration-break': ['boxDecorationBreak'],
|
|
5260
|
+
'-webkit-box-decoration-break': ['boxDecorationBreak'],
|
|
4926
5261
|
margin: sides((s) => `margin${s}`),
|
|
4927
5262
|
'margin-top': ['marginTop'],
|
|
4928
5263
|
'margin-right': ['marginRight'],
|
package/src/html/css/ua.ts
CHANGED
|
@@ -188,6 +188,20 @@ input, button, select, textarea, meter, progress {
|
|
|
188
188
|
}
|
|
189
189
|
/* Chrome's own UA margins for the checkables, near enough: they are the
|
|
190
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
|
+
}
|
|
191
205
|
input[type=checkbox] { margin: 3px 4px 3px 4px; }
|
|
192
206
|
input[type=radio] { margin: 3px 4px 3px 5px; }
|
|
193
207
|
input[type=hidden] { display: none; }
|
package/src/html/css/values.ts
CHANGED
|
@@ -102,7 +102,7 @@ export interface UnitContext {
|
|
|
102
102
|
em: number;
|
|
103
103
|
/** The root element's `font-size`. */
|
|
104
104
|
rem: number;
|
|
105
|
-
/** Viewport, for `vw`/`vh`/`vmin`/`vmax
|
|
105
|
+
/** Viewport, for `vw`/`vh`/`vmin`/`vmax` and their kin (`viewportUnit`). */
|
|
106
106
|
vw: number;
|
|
107
107
|
vh: number;
|
|
108
108
|
/** Device pixels per CSS pixel — the display scale. */
|
|
@@ -131,9 +131,37 @@ export interface UnitContext {
|
|
|
131
131
|
const NUMBER_SRC = '[+-]?(?:\\d*\\.\\d+|\\d+)(?:e[+-]?\\d+)?';
|
|
132
132
|
|
|
133
133
|
const LENGTH_RE = new RegExp(
|
|
134
|
-
`^(${NUMBER_SRC})(px|em|rem|pt|pc|in|cm|mm|ex|ch|lh|rlh|
|
|
134
|
+
`^(${NUMBER_SRC})(px|em|rem|pt|pc|in|cm|mm|ex|ch|lh|rlh|[sld]?v(?:w|h|i|b|min|max)|q|%)?$`,
|
|
135
135
|
);
|
|
136
136
|
|
|
137
|
+
/**
|
|
138
|
+
* The viewport unit a viewport-percentage length is (CSS Values 4, 6.1.2),
|
|
139
|
+
* or null for another unit. The small, large and dynamic viewports —
|
|
140
|
+
* `svh`, `lvh`, `dvh` and their kin — are the one viewport here, as they
|
|
141
|
+
* are on a desktop, where no toolbar comes and goes over the page; and the
|
|
142
|
+
* inline and block axes, `vi` and `vb`, are the width and the height of
|
|
143
|
+
* the horizontal writing mode every document here is laid out in.
|
|
144
|
+
*/
|
|
145
|
+
export function viewportUnit(
|
|
146
|
+
unit: string,
|
|
147
|
+
): 'vw' | 'vh' | 'vmin' | 'vmax' | null {
|
|
148
|
+
const u = unit.toLowerCase();
|
|
149
|
+
const bare = /^[sld]v/.test(u) ? u.slice(1) : u;
|
|
150
|
+
switch (bare) {
|
|
151
|
+
case 'vw':
|
|
152
|
+
case 'vi':
|
|
153
|
+
return 'vw';
|
|
154
|
+
case 'vh':
|
|
155
|
+
case 'vb':
|
|
156
|
+
return 'vh';
|
|
157
|
+
case 'vmin':
|
|
158
|
+
case 'vmax':
|
|
159
|
+
return bare;
|
|
160
|
+
default:
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
137
165
|
const NUMBER_RE = new RegExp(`^${NUMBER_SRC}$`, 'i');
|
|
138
166
|
|
|
139
167
|
/** A math function: `calc()`, `min()`, `max()` or `clamp()`. */
|
|
@@ -166,7 +194,7 @@ export function parseLength(
|
|
|
166
194
|
});
|
|
167
195
|
}
|
|
168
196
|
const n = Number(m[1]);
|
|
169
|
-
const unit = m[2];
|
|
197
|
+
const unit = m[2] && (viewportUnit(m[2]) ?? m[2]);
|
|
170
198
|
// a zero needs no unit however it is written — `-0`, `+0`, `0.0`
|
|
171
199
|
if (!unit) return n === 0 ? 0 : bareIsPx ? n * ctx.scale : null;
|
|
172
200
|
if (unit === '%') return { pct: n };
|
package/src/html/css/vars.ts
CHANGED
|
@@ -245,7 +245,7 @@ function findVar(value: string, from: number): number {
|
|
|
245
245
|
|
|
246
246
|
/** The `)` that closes a bracket opened just before `from`, strings and
|
|
247
247
|
* brackets inside it passed over; -1 where none does. */
|
|
248
|
-
function closingParen(value: string, from: number): number {
|
|
248
|
+
export function closingParen(value: string, from: number): number {
|
|
249
249
|
let depth = 0;
|
|
250
250
|
for (let i = from; i < value.length; i += 1) {
|
|
251
251
|
const c = value[i];
|
|
@@ -326,7 +326,7 @@ function topLevel(value: string, ch: string): number {
|
|
|
326
326
|
}
|
|
327
327
|
|
|
328
328
|
/** The first comma not inside a bracket or a string, or -1. */
|
|
329
|
-
function topLevelComma(value: string): number {
|
|
329
|
+
export function topLevelComma(value: string): number {
|
|
330
330
|
let depth = 0;
|
|
331
331
|
for (let i = 0; i < value.length; i += 1) {
|
|
332
332
|
const c = value[i];
|
package/src/html/dom.ts
CHANGED
|
@@ -572,15 +572,51 @@ function createParser(): { parser: Parser; handler: DomHandler } {
|
|
|
572
572
|
withStartIndices: false,
|
|
573
573
|
withEndIndices: false,
|
|
574
574
|
});
|
|
575
|
-
const parser = new
|
|
575
|
+
const parser = new DocumentParser(handler, {
|
|
576
576
|
lowerCaseTags: true,
|
|
577
577
|
lowerCaseAttributeNames: true,
|
|
578
|
-
|
|
578
|
+
// HTML closes an element written `<x/>` only where it is void, and so
|
|
579
|
+
// closed anyway, or in SVG or MathML (HTML 13.2.6.5, "acknowledge the
|
|
580
|
+
// self-closing flag"). A `<div/>` or an `<a href="" /*target="_blank"*/>`
|
|
581
|
+
// — melbcss.com's, a comment that is no comment in a tag — opens an
|
|
582
|
+
// element like any other, and closing it left the link's text outside
|
|
583
|
+
// the link.
|
|
584
|
+
recognizeSelfClosing: false,
|
|
579
585
|
decodeEntities: true,
|
|
580
586
|
});
|
|
581
587
|
return { parser, handler };
|
|
582
588
|
}
|
|
583
589
|
|
|
590
|
+
/**
|
|
591
|
+
* htmlparser2's parser, deciding a `/>` from the context its tag was read
|
|
592
|
+
* in, as HTML's does. htmlparser2 decides from the context the tag opens,
|
|
593
|
+
* and an integration point — SVG's `<title>`, `<desc>` and
|
|
594
|
+
* `<foreignObject>`, MathML's `<mi>` and its kin — opens an HTML one, so
|
|
595
|
+
* an `<svg><title/><path/>` took its path into the title.
|
|
596
|
+
*/
|
|
597
|
+
class DocumentParser extends Parser {
|
|
598
|
+
private _foreignTag = false;
|
|
599
|
+
private _acknowledging = false;
|
|
600
|
+
|
|
601
|
+
override isInForeignContext(): boolean {
|
|
602
|
+
return this._acknowledging || super.isInForeignContext();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
override onopentagname(start: number, endIndex: number): void {
|
|
606
|
+
this._foreignTag = this.isInForeignContext();
|
|
607
|
+
super.onopentagname(start, endIndex);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
override onselfclosingtag(endIndex: number): void {
|
|
611
|
+
this._acknowledging = this._foreignTag;
|
|
612
|
+
try {
|
|
613
|
+
super.onselfclosingtag(endIndex);
|
|
614
|
+
} finally {
|
|
615
|
+
this._acknowledging = false;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
584
620
|
/**
|
|
585
621
|
* Whether a `media` attribute is one a screen honours. Deliberately not a
|
|
586
622
|
* media-query engine: `screen`, `all` and an empty list apply, `print` and
|