@react-x11/components 0.10.0 → 0.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/code/index.d.ts.map +1 -1
- package/dist/code/index.js +25 -24
- package/dist/code/index.js.map +1 -1
- package/dist/code-language/index.d.ts +1 -1
- package/dist/code-language/index.d.ts.map +1 -1
- package/dist/code-language/index.js +1 -1
- package/dist/code-language/index.js.map +1 -1
- package/dist/code-language/runs.d.ts +31 -0
- package/dist/code-language/runs.d.ts.map +1 -1
- package/dist/code-language/runs.js +105 -0
- package/dist/code-language/runs.js.map +1 -1
- package/dist/codeblock/index.d.ts +13 -0
- package/dist/codeblock/index.d.ts.map +1 -1
- package/dist/codeblock/index.js +37 -1
- package/dist/codeblock/index.js.map +1 -1
- 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 +32 -10
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +89 -31
- 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 +45 -5
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +363 -14
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +22 -5
- 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 +306 -25
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +61 -6
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +65 -16
- 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 +29 -2
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +1454 -125
- 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 +608 -86
- 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 +746 -214
- 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/html/svg.d.ts.map +1 -1
- package/dist/html/svg.js +27 -20
- package/dist/html/svg.js.map +1 -1
- package/dist/internal/codelines.d.ts +42 -0
- package/dist/internal/codelines.d.ts.map +1 -0
- package/dist/internal/codelines.js +170 -0
- package/dist/internal/codelines.js.map +1 -0
- 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/markdown/index.d.ts.map +1 -1
- package/dist/markdown/index.js +98 -26
- package/dist/markdown/index.js.map +1 -1
- package/dist/rich-text-editor/index.d.ts.map +1 -1
- package/dist/rich-text-editor/index.js +3 -8
- package/dist/rich-text-editor/index.js.map +1 -1
- package/dist/rich-text-editor/markdown.d.ts.map +1 -1
- package/dist/rich-text-editor/markdown.js +30 -1
- package/dist/rich-text-editor/markdown.js.map +1 -1
- package/dist/rich-text-editor/replace.d.ts +13 -0
- package/dist/rich-text-editor/replace.d.ts.map +1 -0
- package/dist/rich-text-editor/replace.js +64 -0
- package/dist/rich-text-editor/replace.js.map +1 -0
- 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/dist/terminal-output/index.d.ts.map +1 -1
- package/dist/terminal-output/index.js +100 -25
- package/dist/terminal-output/index.js.map +1 -1
- package/dist/terminal-output/runs.d.ts +4 -1
- package/dist/terminal-output/runs.d.ts.map +1 -1
- package/dist/terminal-output/runs.js +5 -1
- package/dist/terminal-output/runs.js.map +1 -1
- package/package.json +5 -3
- package/src/code/index.ts +34 -32
- package/src/code-language/index.ts +1 -1
- package/src/code-language/runs.ts +111 -1
- package/src/codeblock/index.ts +48 -1
- package/src/flow/node.ts +80 -64
- package/src/html/controls.ts +50 -9
- package/src/html/css/cascade.ts +134 -43
- package/src/html/css/color.ts +87 -0
- package/src/html/css/parse.ts +107 -4
- package/src/html/css/style.ts +386 -23
- package/src/html/css/ua.ts +22 -5
- 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 +360 -22
- package/src/html/layout/boxes.ts +98 -23
- package/src/html/layout/cache.ts +8 -3
- package/src/html/layout/floats.ts +24 -0
- package/src/html/layout/inline.ts +1690 -119
- package/src/html/node.ts +648 -101
- package/src/html/paint.ts +883 -283
- package/src/html/surfaces.ts +21 -1
- package/src/html/svg.ts +30 -19
- package/src/internal/codelines.ts +200 -0
- package/src/internal/window.ts +36 -8
- package/src/markdown/index.ts +145 -26
- package/src/rich-text-editor/index.ts +3 -11
- package/src/rich-text-editor/markdown.ts +36 -1
- package/src/rich-text-editor/replace.ts +85 -0
- package/src/richtext/node.ts +7 -0
- package/src/terminal-output/index.ts +127 -34
- package/src/terminal-output/runs.ts +16 -2
package/src/html/css/cascade.ts
CHANGED
|
@@ -49,6 +49,7 @@ import { parseDeclarations } from './parse.js';
|
|
|
49
49
|
import { CounterStyles, counterStyleRule } from './counter-styles.js';
|
|
50
50
|
import type { CounterStyleRule } from './counter-styles.js';
|
|
51
51
|
import type { UnitContext } from './values.js';
|
|
52
|
+
import { viewportUnit } from './values.js';
|
|
52
53
|
import { customProperties, substituteIn } from './vars.js';
|
|
53
54
|
import type { CustomProps } from './vars.js';
|
|
54
55
|
|
|
@@ -514,6 +515,46 @@ export interface PointerState {
|
|
|
514
515
|
|
|
515
516
|
const NO_POINTER: PointerState = { hovered: new Set(), active: new Set() };
|
|
516
517
|
|
|
518
|
+
/**
|
|
519
|
+
* The face a style's font-relative units are measured in: its family list,
|
|
520
|
+
* size, weight and slant, the four that pick one. A family's faces can be
|
|
521
|
+
* different fonts altogether — a page's `@font-face` rules may set its
|
|
522
|
+
* bold in another file — so an `ex` in bold text is the bold face's.
|
|
523
|
+
*/
|
|
524
|
+
export interface MetricFace {
|
|
525
|
+
family: string;
|
|
526
|
+
size: number;
|
|
527
|
+
weight: number;
|
|
528
|
+
style: ComputedStyle['fontStyle'];
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** What a face measures, where the fonts can say. */
|
|
532
|
+
export type FaceMetric = (face: MetricFace) => number | null;
|
|
533
|
+
|
|
534
|
+
/** What a style's face is read from: a computed style, or the part of one
|
|
535
|
+
* a declaration's font-relative units are measured against. */
|
|
536
|
+
type FaceSource = Pick<
|
|
537
|
+
ComputedStyle,
|
|
538
|
+
'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle'
|
|
539
|
+
>;
|
|
540
|
+
|
|
541
|
+
/** …and its line height, for `lh`. */
|
|
542
|
+
type LineSource = FaceSource &
|
|
543
|
+
Pick<ComputedStyle, 'lineHeight' | 'lineHeightIsLength'>;
|
|
544
|
+
|
|
545
|
+
function faceOf(style: FaceSource): MetricFace {
|
|
546
|
+
return {
|
|
547
|
+
family: style.fontFamily,
|
|
548
|
+
size: style.fontSize,
|
|
549
|
+
weight: style.fontWeight,
|
|
550
|
+
style: style.fontStyle,
|
|
551
|
+
};
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function faceKey(style: FaceSource): string {
|
|
555
|
+
return `${style.fontFamily}\u0001${style.fontSize}\u0001${style.fontWeight}\u0001${style.fontStyle}`;
|
|
556
|
+
}
|
|
557
|
+
|
|
517
558
|
/**
|
|
518
559
|
* A cascade over a fixed set of stylesheets. Rebuilt when the sheets change;
|
|
519
560
|
* re-run when the DOM, the viewport band or the pointer state does.
|
|
@@ -564,17 +605,15 @@ export class Cascade {
|
|
|
564
605
|
/** The counter styles the sheets define, over the predefined ones. */
|
|
565
606
|
readonly counterStyles: CounterStyles;
|
|
566
607
|
|
|
567
|
-
/** A
|
|
568
|
-
private _xHeightOf:
|
|
608
|
+
/** A face's x-height, for `ex`, where the fonts can say. */
|
|
609
|
+
private _xHeightOf: FaceMetric | null;
|
|
569
610
|
private _xHeights = new Map<string, number>();
|
|
570
|
-
/** The advance of a
|
|
571
|
-
private _zeroWidthOf:
|
|
572
|
-
((family: string, size: number) => number | null) | null;
|
|
611
|
+
/** The advance of a face's "0", for `ch`, likewise. */
|
|
612
|
+
private _zeroWidthOf: FaceMetric | null;
|
|
573
613
|
private _zeroWidths = new Map<string, number>();
|
|
574
|
-
/** A
|
|
575
|
-
*
|
|
576
|
-
private _normalLineOf:
|
|
577
|
-
((family: string, size: number) => number | null) | null;
|
|
614
|
+
/** A face's own line height, for `lh` where `line-height` is `normal`,
|
|
615
|
+
* likewise. */
|
|
616
|
+
private _normalLineOf: FaceMetric | null;
|
|
578
617
|
private _normalLines = new Map<string, number>();
|
|
579
618
|
/** Whether any declaration sets a custom property or reads one: without
|
|
580
619
|
* one, no element's style asks about them. */
|
|
@@ -599,9 +638,9 @@ export class Cascade {
|
|
|
599
638
|
viewportWidth: number,
|
|
600
639
|
viewportHeight: number,
|
|
601
640
|
scale = 1,
|
|
602
|
-
xHeight:
|
|
603
|
-
zeroWidth:
|
|
604
|
-
normalLine:
|
|
641
|
+
xHeight: FaceMetric | null = null,
|
|
642
|
+
zeroWidth: FaceMetric | null = null,
|
|
643
|
+
normalLine: FaceMetric | null = null,
|
|
605
644
|
families: FontFamilies | null = null,
|
|
606
645
|
) {
|
|
607
646
|
this._xHeightOf = xHeight;
|
|
@@ -665,7 +704,7 @@ export class Cascade {
|
|
|
665
704
|
for (const d of declarations) {
|
|
666
705
|
if (!VIEWPORT_UNIT.test(d.value)) continue;
|
|
667
706
|
for (const m of d.value.matchAll(VIEWPORT_UNITS)) {
|
|
668
|
-
const unit = m[1]
|
|
707
|
+
const unit = viewportUnit(m[1]);
|
|
669
708
|
if (unit !== 'vh') this.readsViewportWidth = true;
|
|
670
709
|
if (unit !== 'vw') this.readsViewportHeight = true;
|
|
671
710
|
}
|
|
@@ -1082,13 +1121,13 @@ export class Cascade {
|
|
|
1082
1121
|
return style;
|
|
1083
1122
|
}
|
|
1084
1123
|
|
|
1085
|
-
/** The x-height of a style's font: the font's own, asked once per face
|
|
1086
|
-
*
|
|
1087
|
-
private _exOf(style:
|
|
1088
|
-
const key =
|
|
1124
|
+
/** The x-height of a style's font: the font's own, asked once per face,
|
|
1125
|
+
* or half an em. */
|
|
1126
|
+
private _exOf(style: FaceSource): number {
|
|
1127
|
+
const key = faceKey(style);
|
|
1089
1128
|
let ex = this._xHeights.get(key);
|
|
1090
1129
|
if (ex === undefined) {
|
|
1091
|
-
ex = this._xHeightOf?.(style
|
|
1130
|
+
ex = this._xHeightOf?.(faceOf(style)) ?? NaN;
|
|
1092
1131
|
if (!(ex > 0)) ex = style.fontSize * 0.5;
|
|
1093
1132
|
this._xHeights.set(key, ex);
|
|
1094
1133
|
}
|
|
@@ -1096,12 +1135,12 @@ export class Cascade {
|
|
|
1096
1135
|
}
|
|
1097
1136
|
|
|
1098
1137
|
/** The advance of a style's font's "0": the font's own, asked once per
|
|
1099
|
-
* face
|
|
1100
|
-
private _chOf(style:
|
|
1101
|
-
const key =
|
|
1138
|
+
* face, or half an em. */
|
|
1139
|
+
private _chOf(style: FaceSource): number {
|
|
1140
|
+
const key = faceKey(style);
|
|
1102
1141
|
let ch = this._zeroWidths.get(key);
|
|
1103
1142
|
if (ch === undefined) {
|
|
1104
|
-
ch = this._zeroWidthOf?.(style
|
|
1143
|
+
ch = this._zeroWidthOf?.(faceOf(style)) ?? NaN;
|
|
1105
1144
|
if (!(ch > 0)) ch = style.fontSize * 0.5;
|
|
1106
1145
|
this._zeroWidths.set(key, ch);
|
|
1107
1146
|
}
|
|
@@ -1109,16 +1148,16 @@ export class Cascade {
|
|
|
1109
1148
|
}
|
|
1110
1149
|
|
|
1111
1150
|
/** A style's computed line height as a length, for `lh`: `normal` as
|
|
1112
|
-
* its font's own, asked once per face
|
|
1113
|
-
private _lineHeightOf(style:
|
|
1151
|
+
* its font's own, asked once per face, or 1.2em. */
|
|
1152
|
+
private _lineHeightOf(style: LineSource): number {
|
|
1114
1153
|
const set = style.lineHeight;
|
|
1115
1154
|
if (set !== 'normal') {
|
|
1116
1155
|
return style.lineHeightIsLength ? set : set * style.fontSize;
|
|
1117
1156
|
}
|
|
1118
|
-
const key =
|
|
1157
|
+
const key = faceKey(style);
|
|
1119
1158
|
let line = this._normalLines.get(key);
|
|
1120
1159
|
if (line === undefined) {
|
|
1121
|
-
line = this._normalLineOf?.(style
|
|
1160
|
+
line = this._normalLineOf?.(faceOf(style)) ?? NaN;
|
|
1122
1161
|
if (!(line > 0)) line = style.fontSize * 1.2;
|
|
1123
1162
|
this._normalLines.set(key, line);
|
|
1124
1163
|
}
|
|
@@ -1154,12 +1193,22 @@ export class Cascade {
|
|
|
1154
1193
|
lh: () => this._lineHeightOf(parentStyle),
|
|
1155
1194
|
rlh: () => this._lineHeightOf(this.initial),
|
|
1156
1195
|
};
|
|
1157
|
-
//
|
|
1196
|
+
// The family, the weight and the slant go with the size: together they
|
|
1197
|
+
// pick the face an `ex`, a `ch` or an `lh` in any declaration is
|
|
1198
|
+
// measured in, however the declarations are ordered. A `width: 10ex`
|
|
1199
|
+
// in a sheet under an inline `font-weight: 900` was measured in the
|
|
1200
|
+
// family's regular face. The colour scheme goes ahead of the rest for
|
|
1201
|
+
// the same reason: every `light-dark()` among them is read by it.
|
|
1158
1202
|
let sized = false;
|
|
1159
1203
|
let keyword = false;
|
|
1160
1204
|
for (const c of candidates) {
|
|
1161
1205
|
for (const d of pick(c)) {
|
|
1162
|
-
if (
|
|
1206
|
+
if (
|
|
1207
|
+
d.prop === 'font-family' ||
|
|
1208
|
+
d.prop === 'font-weight' ||
|
|
1209
|
+
d.prop === 'font-style' ||
|
|
1210
|
+
d.prop === 'color-scheme'
|
|
1211
|
+
) {
|
|
1163
1212
|
this._apply(style, parentStyle, d, ctxParent);
|
|
1164
1213
|
} else if (d.prop === 'font-size' || d.prop === 'font') {
|
|
1165
1214
|
// NaN until a size is set, so one the declaration did not take
|
|
@@ -1188,21 +1237,43 @@ export class Cascade {
|
|
|
1188
1237
|
) {
|
|
1189
1238
|
style.fontSize *= mono ? FIXED_SIZE : 1 / FIXED_SIZE;
|
|
1190
1239
|
}
|
|
1240
|
+
// The face as the first pass settled it. The second applies every
|
|
1241
|
+
// declaration again in cascade order, the font's among them, so the
|
|
1242
|
+
// style passes through the fonts of every rule on the way: read from
|
|
1243
|
+
// it, the `10ex` in `div { font-family: foo; width: 10ex }` was `foo`'s
|
|
1244
|
+
// under an inline `font-family: Ahem` that came after it.
|
|
1245
|
+
const face: FaceSource = {
|
|
1246
|
+
fontFamily: style.fontFamily,
|
|
1247
|
+
fontSize: style.fontSize,
|
|
1248
|
+
fontWeight: style.fontWeight,
|
|
1249
|
+
fontStyle: style.fontStyle,
|
|
1250
|
+
};
|
|
1251
|
+
let line: LineSource | null = null;
|
|
1191
1252
|
const ctx: UnitContext = {
|
|
1192
1253
|
...ctxParent,
|
|
1193
1254
|
em: style.fontSize,
|
|
1194
|
-
ex: () => this._exOf(
|
|
1195
|
-
ch: () => this._chOf(
|
|
1196
|
-
lh: () => this._lineHeightOf(style),
|
|
1255
|
+
ex: () => this._exOf(face),
|
|
1256
|
+
ch: () => this._chOf(face),
|
|
1257
|
+
lh: () => this._lineHeightOf(line ?? style),
|
|
1197
1258
|
};
|
|
1198
1259
|
// the line height, which an `lh` in any other declaration reads, ahead
|
|
1199
|
-
// of them
|
|
1260
|
+
// of them — a `font` sets one too — and read as it is then, for the
|
|
1261
|
+
// same reason as the face: set again with the rest, in its place among
|
|
1262
|
+
// them. A `font` sets the size as well, which the first pass settled.
|
|
1200
1263
|
if (this._lh) {
|
|
1201
1264
|
for (const c of candidates) {
|
|
1202
1265
|
for (const d of pick(c)) {
|
|
1203
|
-
if (d.prop === 'line-height'
|
|
1266
|
+
if (d.prop === 'line-height' || d.prop === 'font') {
|
|
1267
|
+
this._apply(style, parentStyle, d, ctx);
|
|
1268
|
+
}
|
|
1204
1269
|
}
|
|
1205
1270
|
}
|
|
1271
|
+
style.fontSize = face.fontSize;
|
|
1272
|
+
line = {
|
|
1273
|
+
...face,
|
|
1274
|
+
lineHeight: style.lineHeight,
|
|
1275
|
+
lineHeightIsLength: style.lineHeightIsLength,
|
|
1276
|
+
};
|
|
1206
1277
|
}
|
|
1207
1278
|
const settled = style.fontSize;
|
|
1208
1279
|
for (const c of candidates) {
|
|
@@ -1283,22 +1354,32 @@ export class Cascade {
|
|
|
1283
1354
|
* that implied `<html>` instead, so an `html { font-size }` still reaches
|
|
1284
1355
|
* the body. Taken as the initial style, the rule matched nothing and the
|
|
1285
1356
|
* whole page came out at the theme's size.
|
|
1357
|
+
*
|
|
1358
|
+
* `html` is the implied `<html>`'s own style where the root box stands in
|
|
1359
|
+
* for the body inside it, and null otherwise: what an `html { … }` or a
|
|
1360
|
+
* `:root { … }` rule gives it that is not inherited has no box to be
|
|
1361
|
+
* drawn on, and its background is the canvas's (`paintDocument`).
|
|
1286
1362
|
*/
|
|
1287
|
-
rootStyle(
|
|
1363
|
+
rootStyle(
|
|
1364
|
+
hasBody: boolean,
|
|
1365
|
+
hasHtml = true,
|
|
1366
|
+
): { style: ComputedStyle; html: ComputedStyle | null } {
|
|
1288
1367
|
const style = copyStyle(this.initial);
|
|
1289
1368
|
style.display = 'block';
|
|
1290
|
-
if (hasBody && hasHtml) return style;
|
|
1369
|
+
if (hasBody && hasHtml) return { style, html: null };
|
|
1291
1370
|
const synthetic = new DomElement('body', {}, []);
|
|
1292
1371
|
let parent = style;
|
|
1372
|
+
let html: ComputedStyle | null = null;
|
|
1293
1373
|
if (!hasHtml) {
|
|
1294
|
-
const
|
|
1295
|
-
synthetic.parent =
|
|
1296
|
-
parent = this.styleFor(
|
|
1297
|
-
if (hasBody) return asRoot(parent);
|
|
1374
|
+
const element = new DomElement('html', {}, hasBody ? [] : [synthetic]);
|
|
1375
|
+
synthetic.parent = element;
|
|
1376
|
+
parent = this.styleFor(element, style, false);
|
|
1377
|
+
if (hasBody) return { style: asRoot(parent), html: null };
|
|
1378
|
+
html = parent;
|
|
1298
1379
|
}
|
|
1299
1380
|
// Only the box the body would have drawn is taken, not its layout role:
|
|
1300
1381
|
// the root is still the initial containing block.
|
|
1301
|
-
return asRoot(this.styleFor(synthetic, parent, false));
|
|
1382
|
+
return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
|
|
1302
1383
|
}
|
|
1303
1384
|
|
|
1304
1385
|
/** The rules of `index` that match `el`, pushed onto `out` as candidates,
|
|
@@ -1412,10 +1493,20 @@ function asRoot(style: ComputedStyle): ComputedStyle {
|
|
|
1412
1493
|
|
|
1413
1494
|
/** `:root` is the `<html>` element: the one a browser implies around a
|
|
1414
1495
|
* fragment, whose style the root box takes (`rootStyle`), and never a
|
|
1415
|
-
* fragment's top-level elements, which css-select would take for it.
|
|
1496
|
+
* fragment's top-level elements, which css-select would take for it.
|
|
1497
|
+
*
|
|
1498
|
+
* `:focus` and its two kin match nothing: no element of the document takes
|
|
1499
|
+
* the focus — a control's widget does, beside it. css-select has none of
|
|
1500
|
+
* the three and throws on them, which dropped every rule that named one,
|
|
1501
|
+
* `:not(:focus)` among them: Wikipedia's skip link hides with
|
|
1502
|
+
* `.mw-jump-link:not(:focus)`, and without it stood in the flow at the top
|
|
1503
|
+
* of every page. */
|
|
1416
1504
|
const PSEUDOS = {
|
|
1417
1505
|
root: (el: Element) =>
|
|
1418
1506
|
el.name === 'html' && !(el.parent && isTag(el.parent as Element)),
|
|
1507
|
+
focus: () => false,
|
|
1508
|
+
'focus-visible': () => false,
|
|
1509
|
+
'focus-within': () => false,
|
|
1419
1510
|
// css-select's, but its ranges and tags lower-cased as ASCII has it, and
|
|
1420
1511
|
// no other script (CSS 2.1 4.1.3): Unicode's took `:lang(\u212Al)`, a
|
|
1421
1512
|
// Kelvin sign for the K, for `:lang(kl)`
|
|
@@ -1449,8 +1540,8 @@ const PSEUDOS = {
|
|
|
1449
1540
|
|
|
1450
1541
|
/** A length in a unit of the viewport: a number, then the unit, and no
|
|
1451
1542
|
* more of a name after it. */
|
|
1452
|
-
const VIEWPORT_UNIT = /\d(?:
|
|
1453
|
-
const VIEWPORT_UNITS = /\d(
|
|
1543
|
+
const VIEWPORT_UNIT = /\d[sld]?v(?:w|h|i|b|min|max)(?![\w-])/i;
|
|
1544
|
+
const VIEWPORT_UNITS = /\d([sld]?v(?:w|h|i|b|min|max))(?![\w-])/gi;
|
|
1454
1545
|
|
|
1455
1546
|
/**
|
|
1456
1547
|
* A selector with its `[attr~=""]` made one that matches nothing: an empty
|
package/src/html/css/color.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as ntk from 'react-x11/ntk';
|
|
2
|
+
import { closingParen, topLevelComma } from './vars.js';
|
|
2
3
|
|
|
3
4
|
// CSS Color 4's functional colours: read here, and written back in the one
|
|
4
5
|
// form every drawing context reads — `#rrggbb`, or `rgba(r, g, b, a)` with
|
|
@@ -753,3 +754,89 @@ const REC2020_TO_XYZ: readonly Triple[] = [
|
|
|
753
754
|
[26158966 / 99577255, 472592308 / 697040785, 8267143 / 139408157],
|
|
754
755
|
[0, 19567812 / 697040785, 295819943 / 278816314],
|
|
755
756
|
];
|
|
757
|
+
|
|
758
|
+
// --- colour schemes ---------------------------------------------------------
|
|
759
|
+
|
|
760
|
+
/** Whether a value has a `light-dark()` in it. */
|
|
761
|
+
export const LIGHT_DARK = /(?:^|[^\w-])light-dark\(/i;
|
|
762
|
+
const LIGHT_DARK_AT = /(?:^|[^\w-])(light-dark\()/gi;
|
|
763
|
+
|
|
764
|
+
/**
|
|
765
|
+
* A value with each `light-dark()` in it replaced by the branch `scheme`
|
|
766
|
+
* picks (CSS Color 5, 7): the first where the element's used colour
|
|
767
|
+
* scheme is light, the second where it is dark. Replaced in the text, as a
|
|
768
|
+
* `var()` is, so a branch can be any colour — or another `light-dark()` —
|
|
769
|
+
* in any value a colour stands in. Null where one is not two arguments,
|
|
770
|
+
* which makes the declaration invalid.
|
|
771
|
+
*/
|
|
772
|
+
export function lightDark(
|
|
773
|
+
value: string,
|
|
774
|
+
scheme: 'light' | 'dark',
|
|
775
|
+
): string | null {
|
|
776
|
+
let out = value;
|
|
777
|
+
// a branch can hold another, which the next round replaces
|
|
778
|
+
for (let round = 0; round < 32; round += 1) {
|
|
779
|
+
LIGHT_DARK_AT.lastIndex = 0;
|
|
780
|
+
const m = LIGHT_DARK_AT.exec(out);
|
|
781
|
+
if (!m) return out;
|
|
782
|
+
const open = m.index + m[0].length;
|
|
783
|
+
const start = open - m[1].length;
|
|
784
|
+
const close = closingParen(out, open);
|
|
785
|
+
if (close < 0) return null;
|
|
786
|
+
const inner = out.slice(open, close);
|
|
787
|
+
const comma = topLevelComma(inner);
|
|
788
|
+
if (comma < 0) return null;
|
|
789
|
+
const light = inner.slice(0, comma).trim();
|
|
790
|
+
const dark = inner.slice(comma + 1).trim();
|
|
791
|
+
if (!light || !dark || topLevelComma(dark) >= 0) return null;
|
|
792
|
+
out =
|
|
793
|
+
out.slice(0, start) +
|
|
794
|
+
(scheme === 'dark' ? dark : light) +
|
|
795
|
+
out.slice(close + 1);
|
|
796
|
+
}
|
|
797
|
+
return null;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
/**
|
|
801
|
+
* The scheme an element with this `color-scheme` is drawn in (CSS Color
|
|
802
|
+
* Adjust 1, 2.2), given the one `preferred` — the palette's, which stands
|
|
803
|
+
* for the reader's preference: that one where the element supports it,
|
|
804
|
+
* else the first it names that is one, else the one `normal` gives, which
|
|
805
|
+
* is the palette's own too. Null for a value that is not a `color-scheme`.
|
|
806
|
+
*/
|
|
807
|
+
export function usedColorScheme(
|
|
808
|
+
value: string,
|
|
809
|
+
preferred: 'light' | 'dark' = 'light',
|
|
810
|
+
): 'light' | 'dark' | null {
|
|
811
|
+
const words = value.trim().toLowerCase().split(/\s+/);
|
|
812
|
+
if (words.length === 1 && words[0] === 'normal') return preferred;
|
|
813
|
+
// `[ light | dark | <custom-ident> ]+ && only?`: `only` once, first or
|
|
814
|
+
// last, and never on its own
|
|
815
|
+
const only = words.indexOf('only');
|
|
816
|
+
if (only >= 0) {
|
|
817
|
+
if (only !== 0 && only !== words.length - 1) return null;
|
|
818
|
+
words.splice(only, 1);
|
|
819
|
+
if (!words.length) return null;
|
|
820
|
+
}
|
|
821
|
+
let first: 'light' | 'dark' | null = null;
|
|
822
|
+
for (const word of words) {
|
|
823
|
+
if (!SCHEME_IDENT.test(word) || NOT_SCHEMES.has(word)) return null;
|
|
824
|
+
if (word === preferred) return preferred;
|
|
825
|
+
if (!first && (word === 'light' || word === 'dark')) first = word;
|
|
826
|
+
}
|
|
827
|
+
return first ?? preferred;
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
const SCHEME_IDENT = /^-?(?:[a-z_]|--)[\w-]*$/;
|
|
831
|
+
/** What a scheme's name cannot be: the keywords the grammar has, and the
|
|
832
|
+
* CSS-wide ones, which reach here only in a list. */
|
|
833
|
+
const NOT_SCHEMES = new Set([
|
|
834
|
+
'normal',
|
|
835
|
+
'only',
|
|
836
|
+
'inherit',
|
|
837
|
+
'initial',
|
|
838
|
+
'unset',
|
|
839
|
+
'revert',
|
|
840
|
+
'revert-layer',
|
|
841
|
+
'default',
|
|
842
|
+
]);
|
package/src/html/css/parse.ts
CHANGED
|
@@ -242,8 +242,11 @@ export function parseStylesheet(
|
|
|
242
242
|
} else if (name === 'supports' && at.block !== null) {
|
|
243
243
|
// Everything in a `@supports` block is markup this renderer either
|
|
244
244
|
// understands or ignores per-declaration, so entering it is closer
|
|
245
|
-
// to right than skipping it
|
|
246
|
-
|
|
245
|
+
// to right than skipping it — but for a condition it can answer
|
|
246
|
+
// false (`supportsCondition`)
|
|
247
|
+
if (supportsCondition(at.prelude) !== false) {
|
|
248
|
+
walk(at.block, media, layer, path);
|
|
249
|
+
}
|
|
247
250
|
} else if (name === 'layer') {
|
|
248
251
|
// `@layer a, b;` names layers, and so fixes their order, and
|
|
249
252
|
// `@layer a { … }` puts rules in one; one with no name is a layer
|
|
@@ -342,7 +345,9 @@ export function parseStylesheet(
|
|
|
342
345
|
depth + 1,
|
|
343
346
|
);
|
|
344
347
|
} else if (name === 'supports') {
|
|
345
|
-
|
|
348
|
+
if (supportsCondition(item.prelude) !== false) {
|
|
349
|
+
styleRule(selectors, item.block, media, layer, path, depth + 1);
|
|
350
|
+
}
|
|
346
351
|
} else if (name === 'layer' && LAYER_NAME.test(item.prelude.trim())) {
|
|
347
352
|
const [ranks, full] = enter(item.prelude.trim(), layer, path);
|
|
348
353
|
styleRule(selectors, item.block, media, ranks, full, depth + 1);
|
|
@@ -1775,7 +1780,7 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1775
1780
|
sawWidth = true;
|
|
1776
1781
|
continue;
|
|
1777
1782
|
}
|
|
1778
|
-
const feature =
|
|
1783
|
+
const feature = mediaFeature(term);
|
|
1779
1784
|
if (feature) {
|
|
1780
1785
|
const key = feature[1].toLowerCase();
|
|
1781
1786
|
const len = parseLength(feature[2], ZERO_UNITS);
|
|
@@ -1845,6 +1850,104 @@ export function parseMediaQuery(prelude: string): MediaCondition[] {
|
|
|
1845
1850
|
return out.length ? out : [{ staticPass: true }];
|
|
1846
1851
|
}
|
|
1847
1852
|
|
|
1853
|
+
/**
|
|
1854
|
+
* The properties whose support a `@supports` condition is answered for:
|
|
1855
|
+
* the mask's, which pages test before they draw an icon as a masked colour
|
|
1856
|
+
* and keep a background image under `not` for an engine without them.
|
|
1857
|
+
* Drawn here, and the fallback drawn too, an icon was its image in black
|
|
1858
|
+
* under the mask, over the colour it was meant to be.
|
|
1859
|
+
*/
|
|
1860
|
+
const ANSWERED = new Set(
|
|
1861
|
+
[
|
|
1862
|
+
'mask',
|
|
1863
|
+
'mask-image',
|
|
1864
|
+
'mask-repeat',
|
|
1865
|
+
'mask-position',
|
|
1866
|
+
'mask-size',
|
|
1867
|
+
'mask-origin',
|
|
1868
|
+
'mask-clip',
|
|
1869
|
+
].flatMap((name) => [name, `-webkit-${name}`]),
|
|
1870
|
+
);
|
|
1871
|
+
|
|
1872
|
+
/**
|
|
1873
|
+
* Whether a `@supports` condition holds (CSS Conditional 3, 6): true or
|
|
1874
|
+
* false where it turns on declarations of the properties this answers for
|
|
1875
|
+
* (`ANSWERED`), combined by `not`, `and` and `or`; null where it turns on
|
|
1876
|
+
* anything else, or is none, and the block is entered as every one was.
|
|
1877
|
+
* Answering more is not safe without a list of what this draws: Tailwind 4
|
|
1878
|
+
* keeps its variables' starting values under a test for engines without
|
|
1879
|
+
* `@property`, which this is, and false there would drop every shadow and
|
|
1880
|
+
* gradient it writes.
|
|
1881
|
+
*/
|
|
1882
|
+
export function supportsCondition(prelude: string): boolean | null {
|
|
1883
|
+
const text = prelude.trim();
|
|
1884
|
+
if (/^not\b/i.test(text)) {
|
|
1885
|
+
const inner = supportsCondition(text.slice(3));
|
|
1886
|
+
return inner === null ? null : !inner;
|
|
1887
|
+
}
|
|
1888
|
+
// the terms at the top level, and the one operator between them
|
|
1889
|
+
const terms: string[] = [];
|
|
1890
|
+
let op = '';
|
|
1891
|
+
let depth = 0;
|
|
1892
|
+
let start = 0;
|
|
1893
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
1894
|
+
const c = text[i];
|
|
1895
|
+
if (c === '(') depth += 1;
|
|
1896
|
+
else if (c === ')') depth -= 1;
|
|
1897
|
+
else if (depth === 0 && /\s/.test(c)) {
|
|
1898
|
+
const word = /^\s+(and|or)\s+/i.exec(text.slice(i));
|
|
1899
|
+
if (!word) continue;
|
|
1900
|
+
const next = word[1].toLowerCase();
|
|
1901
|
+
// `and` and `or` mixed without parentheses is no condition
|
|
1902
|
+
if (op && op !== next) return null;
|
|
1903
|
+
op = next;
|
|
1904
|
+
terms.push(text.slice(start, i));
|
|
1905
|
+
i += word[0].length - 1;
|
|
1906
|
+
start = i + 1;
|
|
1907
|
+
}
|
|
1908
|
+
if (depth < 0) return null;
|
|
1909
|
+
}
|
|
1910
|
+
if (terms.length) {
|
|
1911
|
+
terms.push(text.slice(start));
|
|
1912
|
+
const values = terms.map(supportsCondition);
|
|
1913
|
+
if (op === 'and') {
|
|
1914
|
+
if (values.includes(false)) return false;
|
|
1915
|
+
return values.includes(null) ? null : true;
|
|
1916
|
+
}
|
|
1917
|
+
if (values.includes(true)) return true;
|
|
1918
|
+
return values.includes(null) ? null : false;
|
|
1919
|
+
}
|
|
1920
|
+
// one term: a condition in parentheses, or a declaration in them
|
|
1921
|
+
if (!(text.startsWith('(') && text.endsWith(')'))) return null;
|
|
1922
|
+
const inner = text.slice(1, -1).trim();
|
|
1923
|
+
if (inner.startsWith('(') || /^not\b/i.test(inner)) {
|
|
1924
|
+
return supportsCondition(inner);
|
|
1925
|
+
}
|
|
1926
|
+
const declaration = /^([a-z-]+)\s*:\s*(\S[\s\S]*)$/i.exec(inner);
|
|
1927
|
+
if (!declaration || !ANSWERED.has(declaration[1].toLowerCase())) return null;
|
|
1928
|
+
return true;
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
/**
|
|
1932
|
+
* A `(name: value)` media feature: the whole term, its name, and its value
|
|
1933
|
+
* read to the parenthesis that closes the feature, or null where the term is
|
|
1934
|
+
* no such thing. A value may hold parentheses of its own: MediaWiki's
|
|
1935
|
+
* breakpoints are `(max-width: calc(640px - 1px))`, and a value read to the
|
|
1936
|
+
* first `)` found no feature there — the term was passed over as though it
|
|
1937
|
+
* said nothing, and every narrow-screen rule held at every width.
|
|
1938
|
+
*/
|
|
1939
|
+
function mediaFeature(term: string): [string, string, string] | null {
|
|
1940
|
+
const m = /^\(\s*([a-z-]+)\s*:([\s\S]*)\)$/i.exec(term);
|
|
1941
|
+
if (!m) return null;
|
|
1942
|
+
let depth = 0;
|
|
1943
|
+
for (const c of m[2]) {
|
|
1944
|
+
if (c === '(') depth += 1;
|
|
1945
|
+
else if (c === ')' && --depth < 0) return null;
|
|
1946
|
+
}
|
|
1947
|
+
const value = m[2].trim();
|
|
1948
|
+
return depth === 0 && value ? [term, m[1], value] : null;
|
|
1949
|
+
}
|
|
1950
|
+
|
|
1848
1951
|
/** A `@media` width is compared with the viewport in CSS pixels — the
|
|
1849
1952
|
* cascade divides the device width by the scale before asking — so the
|
|
1850
1953
|
* thresholds parse at scale 1 whatever panel the document lands on. */
|