@react-x11/components 0.10.0 → 0.11.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/html/css/cascade.d.ts +32 -10
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +72 -27
- package/dist/html/css/cascade.js.map +1 -1
- package/dist/html/css/style.d.ts +7 -4
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +37 -6
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.js +8 -5
- package/dist/html/css/ua.js.map +1 -1
- package/dist/html/layout/block.d.ts.map +1 -1
- package/dist/html/layout/block.js +127 -9
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +27 -0
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +30 -2
- package/dist/html/layout/boxes.js.map +1 -1
- package/dist/html/layout/inline.d.ts +3 -0
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +236 -14
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +118 -57
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +203 -37
- package/dist/html/paint.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/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/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 +1 -1
- 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/html/css/cascade.ts +117 -39
- package/src/html/css/style.ts +43 -15
- package/src/html/css/ua.ts +8 -5
- package/src/html/layout/block.ts +134 -8
- package/src/html/layout/boxes.ts +43 -2
- package/src/html/layout/inline.ts +257 -13
- package/src/html/node.ts +145 -79
- package/src/html/paint.ts +236 -45
- package/src/html/svg.ts +30 -19
- package/src/internal/codelines.ts +200 -0
- 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/terminal-output/index.ts +127 -34
- package/src/terminal-output/runs.ts +16 -2
package/src/html/css/cascade.ts
CHANGED
|
@@ -514,6 +514,46 @@ export interface PointerState {
|
|
|
514
514
|
|
|
515
515
|
const NO_POINTER: PointerState = { hovered: new Set(), active: new Set() };
|
|
516
516
|
|
|
517
|
+
/**
|
|
518
|
+
* The face a style's font-relative units are measured in: its family list,
|
|
519
|
+
* size, weight and slant, the four that pick one. A family's faces can be
|
|
520
|
+
* different fonts altogether — a page's `@font-face` rules may set its
|
|
521
|
+
* bold in another file — so an `ex` in bold text is the bold face's.
|
|
522
|
+
*/
|
|
523
|
+
export interface MetricFace {
|
|
524
|
+
family: string;
|
|
525
|
+
size: number;
|
|
526
|
+
weight: number;
|
|
527
|
+
style: ComputedStyle['fontStyle'];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/** What a face measures, where the fonts can say. */
|
|
531
|
+
export type FaceMetric = (face: MetricFace) => number | null;
|
|
532
|
+
|
|
533
|
+
/** What a style's face is read from: a computed style, or the part of one
|
|
534
|
+
* a declaration's font-relative units are measured against. */
|
|
535
|
+
type FaceSource = Pick<
|
|
536
|
+
ComputedStyle,
|
|
537
|
+
'fontFamily' | 'fontSize' | 'fontWeight' | 'fontStyle'
|
|
538
|
+
>;
|
|
539
|
+
|
|
540
|
+
/** …and its line height, for `lh`. */
|
|
541
|
+
type LineSource = FaceSource &
|
|
542
|
+
Pick<ComputedStyle, 'lineHeight' | 'lineHeightIsLength'>;
|
|
543
|
+
|
|
544
|
+
function faceOf(style: FaceSource): MetricFace {
|
|
545
|
+
return {
|
|
546
|
+
family: style.fontFamily,
|
|
547
|
+
size: style.fontSize,
|
|
548
|
+
weight: style.fontWeight,
|
|
549
|
+
style: style.fontStyle,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function faceKey(style: FaceSource): string {
|
|
554
|
+
return `${style.fontFamily}\u0001${style.fontSize}\u0001${style.fontWeight}\u0001${style.fontStyle}`;
|
|
555
|
+
}
|
|
556
|
+
|
|
517
557
|
/**
|
|
518
558
|
* A cascade over a fixed set of stylesheets. Rebuilt when the sheets change;
|
|
519
559
|
* re-run when the DOM, the viewport band or the pointer state does.
|
|
@@ -564,17 +604,15 @@ export class Cascade {
|
|
|
564
604
|
/** The counter styles the sheets define, over the predefined ones. */
|
|
565
605
|
readonly counterStyles: CounterStyles;
|
|
566
606
|
|
|
567
|
-
/** A
|
|
568
|
-
private _xHeightOf:
|
|
607
|
+
/** A face's x-height, for `ex`, where the fonts can say. */
|
|
608
|
+
private _xHeightOf: FaceMetric | null;
|
|
569
609
|
private _xHeights = new Map<string, number>();
|
|
570
|
-
/** The advance of a
|
|
571
|
-
private _zeroWidthOf:
|
|
572
|
-
((family: string, size: number) => number | null) | null;
|
|
610
|
+
/** The advance of a face's "0", for `ch`, likewise. */
|
|
611
|
+
private _zeroWidthOf: FaceMetric | null;
|
|
573
612
|
private _zeroWidths = new Map<string, number>();
|
|
574
|
-
/** A
|
|
575
|
-
*
|
|
576
|
-
private _normalLineOf:
|
|
577
|
-
((family: string, size: number) => number | null) | null;
|
|
613
|
+
/** A face's own line height, for `lh` where `line-height` is `normal`,
|
|
614
|
+
* likewise. */
|
|
615
|
+
private _normalLineOf: FaceMetric | null;
|
|
578
616
|
private _normalLines = new Map<string, number>();
|
|
579
617
|
/** Whether any declaration sets a custom property or reads one: without
|
|
580
618
|
* one, no element's style asks about them. */
|
|
@@ -599,9 +637,9 @@ export class Cascade {
|
|
|
599
637
|
viewportWidth: number,
|
|
600
638
|
viewportHeight: number,
|
|
601
639
|
scale = 1,
|
|
602
|
-
xHeight:
|
|
603
|
-
zeroWidth:
|
|
604
|
-
normalLine:
|
|
640
|
+
xHeight: FaceMetric | null = null,
|
|
641
|
+
zeroWidth: FaceMetric | null = null,
|
|
642
|
+
normalLine: FaceMetric | null = null,
|
|
605
643
|
families: FontFamilies | null = null,
|
|
606
644
|
) {
|
|
607
645
|
this._xHeightOf = xHeight;
|
|
@@ -1082,13 +1120,13 @@ export class Cascade {
|
|
|
1082
1120
|
return style;
|
|
1083
1121
|
}
|
|
1084
1122
|
|
|
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 =
|
|
1123
|
+
/** The x-height of a style's font: the font's own, asked once per face,
|
|
1124
|
+
* or half an em. */
|
|
1125
|
+
private _exOf(style: FaceSource): number {
|
|
1126
|
+
const key = faceKey(style);
|
|
1089
1127
|
let ex = this._xHeights.get(key);
|
|
1090
1128
|
if (ex === undefined) {
|
|
1091
|
-
ex = this._xHeightOf?.(style
|
|
1129
|
+
ex = this._xHeightOf?.(faceOf(style)) ?? NaN;
|
|
1092
1130
|
if (!(ex > 0)) ex = style.fontSize * 0.5;
|
|
1093
1131
|
this._xHeights.set(key, ex);
|
|
1094
1132
|
}
|
|
@@ -1096,12 +1134,12 @@ export class Cascade {
|
|
|
1096
1134
|
}
|
|
1097
1135
|
|
|
1098
1136
|
/** 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 =
|
|
1137
|
+
* face, or half an em. */
|
|
1138
|
+
private _chOf(style: FaceSource): number {
|
|
1139
|
+
const key = faceKey(style);
|
|
1102
1140
|
let ch = this._zeroWidths.get(key);
|
|
1103
1141
|
if (ch === undefined) {
|
|
1104
|
-
ch = this._zeroWidthOf?.(style
|
|
1142
|
+
ch = this._zeroWidthOf?.(faceOf(style)) ?? NaN;
|
|
1105
1143
|
if (!(ch > 0)) ch = style.fontSize * 0.5;
|
|
1106
1144
|
this._zeroWidths.set(key, ch);
|
|
1107
1145
|
}
|
|
@@ -1109,16 +1147,16 @@ export class Cascade {
|
|
|
1109
1147
|
}
|
|
1110
1148
|
|
|
1111
1149
|
/** 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:
|
|
1150
|
+
* its font's own, asked once per face, or 1.2em. */
|
|
1151
|
+
private _lineHeightOf(style: LineSource): number {
|
|
1114
1152
|
const set = style.lineHeight;
|
|
1115
1153
|
if (set !== 'normal') {
|
|
1116
1154
|
return style.lineHeightIsLength ? set : set * style.fontSize;
|
|
1117
1155
|
}
|
|
1118
|
-
const key =
|
|
1156
|
+
const key = faceKey(style);
|
|
1119
1157
|
let line = this._normalLines.get(key);
|
|
1120
1158
|
if (line === undefined) {
|
|
1121
|
-
line = this._normalLineOf?.(style
|
|
1159
|
+
line = this._normalLineOf?.(faceOf(style)) ?? NaN;
|
|
1122
1160
|
if (!(line > 0)) line = style.fontSize * 1.2;
|
|
1123
1161
|
this._normalLines.set(key, line);
|
|
1124
1162
|
}
|
|
@@ -1154,12 +1192,20 @@ export class Cascade {
|
|
|
1154
1192
|
lh: () => this._lineHeightOf(parentStyle),
|
|
1155
1193
|
rlh: () => this._lineHeightOf(this.initial),
|
|
1156
1194
|
};
|
|
1157
|
-
//
|
|
1195
|
+
// The family, the weight and the slant go with the size: together they
|
|
1196
|
+
// pick the face an `ex`, a `ch` or an `lh` in any declaration is
|
|
1197
|
+
// measured in, however the declarations are ordered. A `width: 10ex`
|
|
1198
|
+
// in a sheet under an inline `font-weight: 900` was measured in the
|
|
1199
|
+
// family's regular face.
|
|
1158
1200
|
let sized = false;
|
|
1159
1201
|
let keyword = false;
|
|
1160
1202
|
for (const c of candidates) {
|
|
1161
1203
|
for (const d of pick(c)) {
|
|
1162
|
-
if (
|
|
1204
|
+
if (
|
|
1205
|
+
d.prop === 'font-family' ||
|
|
1206
|
+
d.prop === 'font-weight' ||
|
|
1207
|
+
d.prop === 'font-style'
|
|
1208
|
+
) {
|
|
1163
1209
|
this._apply(style, parentStyle, d, ctxParent);
|
|
1164
1210
|
} else if (d.prop === 'font-size' || d.prop === 'font') {
|
|
1165
1211
|
// NaN until a size is set, so one the declaration did not take
|
|
@@ -1188,21 +1234,43 @@ export class Cascade {
|
|
|
1188
1234
|
) {
|
|
1189
1235
|
style.fontSize *= mono ? FIXED_SIZE : 1 / FIXED_SIZE;
|
|
1190
1236
|
}
|
|
1237
|
+
// The face as the first pass settled it. The second applies every
|
|
1238
|
+
// declaration again in cascade order, the font's among them, so the
|
|
1239
|
+
// style passes through the fonts of every rule on the way: read from
|
|
1240
|
+
// it, the `10ex` in `div { font-family: foo; width: 10ex }` was `foo`'s
|
|
1241
|
+
// under an inline `font-family: Ahem` that came after it.
|
|
1242
|
+
const face: FaceSource = {
|
|
1243
|
+
fontFamily: style.fontFamily,
|
|
1244
|
+
fontSize: style.fontSize,
|
|
1245
|
+
fontWeight: style.fontWeight,
|
|
1246
|
+
fontStyle: style.fontStyle,
|
|
1247
|
+
};
|
|
1248
|
+
let line: LineSource | null = null;
|
|
1191
1249
|
const ctx: UnitContext = {
|
|
1192
1250
|
...ctxParent,
|
|
1193
1251
|
em: style.fontSize,
|
|
1194
|
-
ex: () => this._exOf(
|
|
1195
|
-
ch: () => this._chOf(
|
|
1196
|
-
lh: () => this._lineHeightOf(style),
|
|
1252
|
+
ex: () => this._exOf(face),
|
|
1253
|
+
ch: () => this._chOf(face),
|
|
1254
|
+
lh: () => this._lineHeightOf(line ?? style),
|
|
1197
1255
|
};
|
|
1198
1256
|
// the line height, which an `lh` in any other declaration reads, ahead
|
|
1199
|
-
// of them
|
|
1257
|
+
// of them — a `font` sets one too — and read as it is then, for the
|
|
1258
|
+
// same reason as the face: set again with the rest, in its place among
|
|
1259
|
+
// them. A `font` sets the size as well, which the first pass settled.
|
|
1200
1260
|
if (this._lh) {
|
|
1201
1261
|
for (const c of candidates) {
|
|
1202
1262
|
for (const d of pick(c)) {
|
|
1203
|
-
if (d.prop === 'line-height'
|
|
1263
|
+
if (d.prop === 'line-height' || d.prop === 'font') {
|
|
1264
|
+
this._apply(style, parentStyle, d, ctx);
|
|
1265
|
+
}
|
|
1204
1266
|
}
|
|
1205
1267
|
}
|
|
1268
|
+
style.fontSize = face.fontSize;
|
|
1269
|
+
line = {
|
|
1270
|
+
...face,
|
|
1271
|
+
lineHeight: style.lineHeight,
|
|
1272
|
+
lineHeightIsLength: style.lineHeightIsLength,
|
|
1273
|
+
};
|
|
1206
1274
|
}
|
|
1207
1275
|
const settled = style.fontSize;
|
|
1208
1276
|
for (const c of candidates) {
|
|
@@ -1283,22 +1351,32 @@ export class Cascade {
|
|
|
1283
1351
|
* that implied `<html>` instead, so an `html { font-size }` still reaches
|
|
1284
1352
|
* the body. Taken as the initial style, the rule matched nothing and the
|
|
1285
1353
|
* whole page came out at the theme's size.
|
|
1354
|
+
*
|
|
1355
|
+
* `html` is the implied `<html>`'s own style where the root box stands in
|
|
1356
|
+
* for the body inside it, and null otherwise: what an `html { … }` or a
|
|
1357
|
+
* `:root { … }` rule gives it that is not inherited has no box to be
|
|
1358
|
+
* drawn on, and its background is the canvas's (`paintDocument`).
|
|
1286
1359
|
*/
|
|
1287
|
-
rootStyle(
|
|
1360
|
+
rootStyle(
|
|
1361
|
+
hasBody: boolean,
|
|
1362
|
+
hasHtml = true,
|
|
1363
|
+
): { style: ComputedStyle; html: ComputedStyle | null } {
|
|
1288
1364
|
const style = copyStyle(this.initial);
|
|
1289
1365
|
style.display = 'block';
|
|
1290
|
-
if (hasBody && hasHtml) return style;
|
|
1366
|
+
if (hasBody && hasHtml) return { style, html: null };
|
|
1291
1367
|
const synthetic = new DomElement('body', {}, []);
|
|
1292
1368
|
let parent = style;
|
|
1369
|
+
let html: ComputedStyle | null = null;
|
|
1293
1370
|
if (!hasHtml) {
|
|
1294
|
-
const
|
|
1295
|
-
synthetic.parent =
|
|
1296
|
-
parent = this.styleFor(
|
|
1297
|
-
if (hasBody) return asRoot(parent);
|
|
1371
|
+
const element = new DomElement('html', {}, hasBody ? [] : [synthetic]);
|
|
1372
|
+
synthetic.parent = element;
|
|
1373
|
+
parent = this.styleFor(element, style, false);
|
|
1374
|
+
if (hasBody) return { style: asRoot(parent), html: null };
|
|
1375
|
+
html = parent;
|
|
1298
1376
|
}
|
|
1299
1377
|
// Only the box the body would have drawn is taken, not its layout role:
|
|
1300
1378
|
// the root is still the initial containing block.
|
|
1301
|
-
return asRoot(this.styleFor(synthetic, parent, false));
|
|
1379
|
+
return { style: asRoot(this.styleFor(synthetic, parent, false)), html };
|
|
1302
1380
|
}
|
|
1303
1381
|
|
|
1304
1382
|
/** The rules of `index` that match `el`, pushed onto `out` as candidates,
|
package/src/html/css/style.ts
CHANGED
|
@@ -497,9 +497,9 @@ export interface ComputedStyle {
|
|
|
497
497
|
/** `background-clip` and `background-origin` (CSS Backgrounds 3, 3.7 and
|
|
498
498
|
* 3.8): the box a layer is painted in and the box it is placed in, the
|
|
499
499
|
* colour painted in the bottom layer's; each a list as the four above. */
|
|
500
|
-
backgroundClip:
|
|
500
|
+
backgroundClip: BackgroundClip;
|
|
501
501
|
backgroundOrigin: VisualBox;
|
|
502
|
-
backgroundClips:
|
|
502
|
+
backgroundClips: BackgroundClip[] | null;
|
|
503
503
|
backgroundOrigins: VisualBox[] | null;
|
|
504
504
|
/** `border-image` and its longhands (CSS Backgrounds 3, 6): one object,
|
|
505
505
|
* replaced whole when any of them changes, and `NO_BORDER_IMAGE` where
|
|
@@ -1799,14 +1799,29 @@ export function applyDeclaration(
|
|
|
1799
1799
|
}
|
|
1800
1800
|
case 'background-clip':
|
|
1801
1801
|
case '-webkit-background-clip': {
|
|
1802
|
-
// `text` the first layer's alone, which paints through the text
|
|
1802
|
+
// `text` the first layer's alone, which paints through the text —
|
|
1803
|
+
// and with `border-area` through its border as well, in either order
|
|
1804
|
+
// (CSS Backgrounds 4, 2.1)
|
|
1803
1805
|
const clips = layerValues(value, (part) => {
|
|
1804
1806
|
const v = part.toLowerCase();
|
|
1805
|
-
|
|
1807
|
+
if (v === 'text' || isBackgroundClip(v)) return v;
|
|
1808
|
+
const [a, b, more] = splitValue(v);
|
|
1809
|
+
return !more &&
|
|
1810
|
+
((a === 'border-area' && b === 'text') ||
|
|
1811
|
+
(a === 'text' && b === 'border-area'))
|
|
1812
|
+
? 'border-area text'
|
|
1813
|
+
: null;
|
|
1806
1814
|
});
|
|
1807
1815
|
if (!clips) return;
|
|
1808
|
-
style.backgroundClipText =
|
|
1809
|
-
|
|
1816
|
+
style.backgroundClipText =
|
|
1817
|
+
clips[0] === 'text' || clips[0] === 'border-area text';
|
|
1818
|
+
const boxes = clips.map((c): BackgroundClip =>
|
|
1819
|
+
c === 'text'
|
|
1820
|
+
? 'border-box'
|
|
1821
|
+
: c === 'border-area text'
|
|
1822
|
+
? 'border-area'
|
|
1823
|
+
: (c as BackgroundClip),
|
|
1824
|
+
);
|
|
1810
1825
|
style.backgroundClip = boxes[0];
|
|
1811
1826
|
style.backgroundClips = boxes.length > 1 ? boxes : null;
|
|
1812
1827
|
return;
|
|
@@ -2877,12 +2892,6 @@ export function settleOverflow(style: ComputedStyle): void {
|
|
|
2877
2892
|
else style.overflowX = x === 'visible' ? 'auto' : 'hidden';
|
|
2878
2893
|
}
|
|
2879
2894
|
|
|
2880
|
-
/**
|
|
2881
|
-
* Whether a box is a scroll container: its `overflow` is neither `visible`
|
|
2882
|
-
* nor `clip`. `clip` cuts what overflows as `hidden` does, but makes no
|
|
2883
|
-
* formatting context and keeps a flex or grid item's automatic minimum
|
|
2884
|
-
* (CSS Overflow 3, 3.1) — so only this asks it apart from `hidden`.
|
|
2885
|
-
*/
|
|
2886
2895
|
/** A `<visual-box>` (CSS Box 4): the edge of a box a property names. */
|
|
2887
2896
|
export type VisualBox = 'border-box' | 'padding-box' | 'content-box';
|
|
2888
2897
|
|
|
@@ -2890,6 +2899,20 @@ function isVisualBox(v: string): v is VisualBox {
|
|
|
2890
2899
|
return v === 'border-box' || v === 'padding-box' || v === 'content-box';
|
|
2891
2900
|
}
|
|
2892
2901
|
|
|
2902
|
+
/** Where a background layer is painted: inside one of the box's edges, or
|
|
2903
|
+
* `border-area`, in what its border paints (CSS Backgrounds 4, 2.1). */
|
|
2904
|
+
export type BackgroundClip = VisualBox | 'border-area';
|
|
2905
|
+
|
|
2906
|
+
function isBackgroundClip(v: string): v is BackgroundClip {
|
|
2907
|
+
return v === 'border-area' || isVisualBox(v);
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
/**
|
|
2911
|
+
* Whether a box is a scroll container: its `overflow` is neither `visible`
|
|
2912
|
+
* nor `clip`. `clip` cuts what overflows as `hidden` does, but makes no
|
|
2913
|
+
* formatting context and keeps a flex or grid item's automatic minimum
|
|
2914
|
+
* (CSS Overflow 3, 3.1) — so only this asks it apart from `hidden`.
|
|
2915
|
+
*/
|
|
2893
2916
|
export function scrolls(style: ComputedStyle): boolean {
|
|
2894
2917
|
const x = style.overflowX;
|
|
2895
2918
|
const y = style.overflowY;
|
|
@@ -3485,7 +3508,7 @@ interface BackgroundLayer {
|
|
|
3485
3508
|
size: ComputedStyle['backgroundSize'];
|
|
3486
3509
|
attachment: ComputedStyle['backgroundAttachment'];
|
|
3487
3510
|
origin: VisualBox;
|
|
3488
|
-
clip:
|
|
3511
|
+
clip: BackgroundClip;
|
|
3489
3512
|
position: [Len, Len] | null;
|
|
3490
3513
|
}
|
|
3491
3514
|
|
|
@@ -3545,9 +3568,14 @@ function readBackgroundLayer(
|
|
|
3545
3568
|
layer.attachment = v;
|
|
3546
3569
|
i += 1;
|
|
3547
3570
|
} else if (isVisualBox(v)) {
|
|
3548
|
-
// one box is both the origin and the clip, and a second the clip
|
|
3549
|
-
|
|
3571
|
+
// one box is both the origin and the clip, and a second the clip —
|
|
3572
|
+
// which `border-area` is instead of, and the one box the origin
|
|
3573
|
+
if ((boxes += 1) > (seen & 32 ? 1 : 2)) return null;
|
|
3550
3574
|
if (boxes === 1) layer.origin = v;
|
|
3575
|
+
if (!(seen & 32)) layer.clip = v;
|
|
3576
|
+
i += 1;
|
|
3577
|
+
} else if (v === 'border-area') {
|
|
3578
|
+
if (boxes > 1 || !once(32)) return null;
|
|
3551
3579
|
layer.clip = v;
|
|
3552
3580
|
i += 1;
|
|
3553
3581
|
} else if (isPositionPart(v, ctx)) {
|
package/src/html/css/ua.ts
CHANGED
|
@@ -44,8 +44,11 @@ export function uaStylesheet(look: RootLook): Stylesheet {
|
|
|
44
44
|
}
|
|
45
45
|
|
|
46
46
|
/**
|
|
47
|
-
* Margins are in `em
|
|
48
|
-
* `body` scales its whitespace with its text the way a browser's does.
|
|
47
|
+
* Margins above and below are in `em`, so a document that sets `font-size`
|
|
48
|
+
* on `body` scales its whitespace with its text the way a browser's does.
|
|
49
|
+
* The indents of lists, definitions and figures are the HTML standard's
|
|
50
|
+
* 40px (15.3.3, 15.3.8), whatever the size: a list in a 10px sidebar is
|
|
51
|
+
* indented as far as one in the body.
|
|
49
52
|
*/
|
|
50
53
|
function uaText(look: RootLook): string {
|
|
51
54
|
const mono = look.monoFamily;
|
|
@@ -81,7 +84,7 @@ h4 { font-size: 1em; font-weight: bold; margin: 1.33em 0; }
|
|
|
81
84
|
h5 { font-size: 0.83em; font-weight: bold; margin: 1.67em 0; }
|
|
82
85
|
h6 { font-size: 0.67em; font-weight: bold; margin: 2.33em 0; }
|
|
83
86
|
|
|
84
|
-
ul, ol { margin: 1em 0; padding-left:
|
|
87
|
+
ul, ol { margin: 1em 0; padding-left: 40px; }
|
|
85
88
|
ul { list-style-type: disc; }
|
|
86
89
|
ol { list-style-type: decimal; }
|
|
87
90
|
ol, ul, menu, dir { counter-reset: list-item; }
|
|
@@ -92,7 +95,7 @@ ul ul ul, ol ol ul, ul ol ul, ol ul ul { list-style-type: square; }
|
|
|
92
95
|
ul ul, ul ol, ol ul, ol ol { margin: 0; }
|
|
93
96
|
|
|
94
97
|
dl { margin: 1em 0; }
|
|
95
|
-
dd { margin-left:
|
|
98
|
+
dd { margin-left: 40px; }
|
|
96
99
|
dt { font-weight: bold; }
|
|
97
100
|
|
|
98
101
|
blockquote {
|
|
@@ -150,7 +153,7 @@ img { display: inline-block; }
|
|
|
150
153
|
iframe { border: 2px inset; }
|
|
151
154
|
/* a poster is drawn within the video's box at its own ratio (HTML 15.4.1) */
|
|
152
155
|
video { object-fit: contain; }
|
|
153
|
-
figure { margin: 1em
|
|
156
|
+
figure { margin: 1em 40px; }
|
|
154
157
|
|
|
155
158
|
table { display: table; border-collapse: separate; border-spacing: 2px; box-sizing: border-box; }
|
|
156
159
|
caption { display: table-caption; text-align: center; }
|
package/src/html/layout/block.ts
CHANGED
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
GRID_TRACKS,
|
|
43
43
|
FIRST_LINE,
|
|
44
44
|
INLINE_OFFSETS,
|
|
45
|
+
MOVED_OFF_LINES,
|
|
45
46
|
LINE_BOX_RAISES,
|
|
46
47
|
SHIFTED_LINES,
|
|
47
48
|
TEXT_SHIFTS,
|
|
@@ -51,6 +52,7 @@ import type {
|
|
|
51
52
|
BoxTree,
|
|
52
53
|
FirstLineStyler,
|
|
53
54
|
Intrinsic,
|
|
55
|
+
InlineDecoration,
|
|
54
56
|
LineBox,
|
|
55
57
|
MarginStrut,
|
|
56
58
|
Marker,
|
|
@@ -180,7 +182,9 @@ export function layoutDocument(
|
|
|
180
182
|
// there — or `<p>hi</p>` would stand 8px lower than the same paragraph in
|
|
181
183
|
// `<body>`.
|
|
182
184
|
resolveEdges(root, viewportWidth);
|
|
185
|
+
const body = fragmentBodyHeight(tree, viewportHeight);
|
|
183
186
|
const leading = root.marginTop;
|
|
187
|
+
const trailing = root.marginBottom;
|
|
184
188
|
root.padRight += root.marginRight;
|
|
185
189
|
root.padBottom += root.marginBottom;
|
|
186
190
|
root.padLeft += root.marginLeft;
|
|
@@ -188,6 +192,7 @@ export function layoutDocument(
|
|
|
188
192
|
root.marginRight = 0;
|
|
189
193
|
root.marginBottom = 0;
|
|
190
194
|
root.marginLeft = 0;
|
|
195
|
+
if (body) handPercentBase(root, body.inner);
|
|
191
196
|
|
|
192
197
|
const contentWidth = Math.max(0, viewportWidth - root.horizontalExtra);
|
|
193
198
|
const floats = new FloatContext(root.contentX, root.contentX + contentWidth);
|
|
@@ -195,7 +200,7 @@ export function layoutDocument(
|
|
|
195
200
|
// height on the root element resolves against (CSS 2.1 10.1, 10.5); a
|
|
196
201
|
// fragment has no root element, and the box standing in for its body has
|
|
197
202
|
// the body's `auto` height to give
|
|
198
|
-
let readsViewportHeight =
|
|
203
|
+
let readsViewportHeight = body?.viewport === true;
|
|
199
204
|
for (const child of root.children) {
|
|
200
205
|
if (child.el?.name !== 'html') continue;
|
|
201
206
|
child.percentHeightBase = viewportHeight;
|
|
@@ -218,7 +223,9 @@ export function layoutDocument(
|
|
|
218
223
|
);
|
|
219
224
|
const floatBottom =
|
|
220
225
|
floats.bottom === -Infinity ? 0 : floats.bottom - root.contentY;
|
|
221
|
-
root.height =
|
|
226
|
+
root.height = body
|
|
227
|
+
? leading + body.outer + trailing
|
|
228
|
+
: Math.max(flow.height, floatBottom) + root.verticalExtra;
|
|
222
229
|
|
|
223
230
|
// Positioned boxes last, and in the order they were found, so a later one
|
|
224
231
|
// can be positioned against an earlier one's resolved rectangle.
|
|
@@ -251,6 +258,42 @@ export function layoutDocument(
|
|
|
251
258
|
};
|
|
252
259
|
}
|
|
253
260
|
|
|
261
|
+
/**
|
|
262
|
+
* How tall the body a fragment's root box stands in for says it is, where
|
|
263
|
+
* that is definite (CSS 2.1 10.5): a length, `100vh` among them, or a
|
|
264
|
+
* percentage of the `<html>` implied around it where that has a height of
|
|
265
|
+
* its own — a length, or a percentage of the viewport, as `html, body {
|
|
266
|
+
* height: 100% }` fills a window. Its border box, and its content box,
|
|
267
|
+
* which what it holds takes its percentages of; `viewport` where the
|
|
268
|
+
* answer read the viewport's height. Null where the document has an
|
|
269
|
+
* `<html>` or a `<body>`, and where the body's height is `auto`, and the
|
|
270
|
+
* box is as tall as what is in it. Measured before its margins are folded
|
|
271
|
+
* into its padding.
|
|
272
|
+
*/
|
|
273
|
+
function fragmentBodyHeight(
|
|
274
|
+
tree: BoxTree,
|
|
275
|
+
viewportHeight: number,
|
|
276
|
+
): { outer: number; inner: number; viewport: boolean } | null {
|
|
277
|
+
const html = tree.impliedHtml;
|
|
278
|
+
if (!html) return null;
|
|
279
|
+
const root = tree.root;
|
|
280
|
+
const style = root.style;
|
|
281
|
+
const base = resolveOrNull(html.height, viewportHeight);
|
|
282
|
+
const set = resolveOrNull(style.height, base ?? NaN);
|
|
283
|
+
if (set === null) return null;
|
|
284
|
+
root.percentHeightBase = base ?? NaN;
|
|
285
|
+
const extra = root.verticalExtra;
|
|
286
|
+
const outer = clampHeight(
|
|
287
|
+
root,
|
|
288
|
+
style.boxSizing === 'border-box' ? Math.max(set, extra) : set + extra,
|
|
289
|
+
);
|
|
290
|
+
return {
|
|
291
|
+
outer,
|
|
292
|
+
inner: Math.max(0, outer - extra),
|
|
293
|
+
viewport: isPct(style.height) && isPct(html.height),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
|
|
254
297
|
/** No margin at all, shared: most joins leave a strut as it was. */
|
|
255
298
|
const NO_MARGIN: MarginStrut = Object.freeze({ pos: 0, neg: 0 });
|
|
256
299
|
|
|
@@ -725,6 +768,8 @@ function layoutChildren(
|
|
|
725
768
|
// this box's first formatted line is its first child's in flow (CSS 2.1
|
|
726
769
|
// 5.12.1), which its `::first-line` is handed to
|
|
727
770
|
let firstLine = ctx.firstLine ? firstLineOf(box) : null;
|
|
771
|
+
// and the room a list item's marker takes on it (`MARKER_ROOM`)
|
|
772
|
+
let marked = MARKER_ROOM.get(box) ?? null;
|
|
728
773
|
const clamp = ctx.clamp ?? null;
|
|
729
774
|
|
|
730
775
|
for (const child of box.children) {
|
|
@@ -743,6 +788,10 @@ function layoutChildren(
|
|
|
743
788
|
if (!FIRST_LINE.has(child)) HANDED.set(child, firstLine);
|
|
744
789
|
firstLine = null;
|
|
745
790
|
}
|
|
791
|
+
if (marked && !child.outOfFlow && !child.isFloat) {
|
|
792
|
+
MARKER_ROOM.set(child, marked);
|
|
793
|
+
marked = null;
|
|
794
|
+
}
|
|
746
795
|
if (child.outOfFlow) {
|
|
747
796
|
placeStatic(
|
|
748
797
|
child,
|
|
@@ -870,12 +919,15 @@ function layoutChildren(
|
|
|
870
919
|
// escapes, and the caller merges it into the box's own bottom margin so
|
|
871
920
|
// the next sibling still sees it. Dropping it here was the bug that made
|
|
872
921
|
// `<div><p>…</p></div><p>…</p>` set the two paragraphs solid.
|
|
922
|
+
// A percentage of a height that depends on content computes to `auto`
|
|
923
|
+
// (CSS 2.1 10.5), and lets it through as `auto` does: a `height: 100%`
|
|
924
|
+
// wrapper in an `auto` body kept its last child's margin inside it
|
|
873
925
|
if (
|
|
874
926
|
!first &&
|
|
875
927
|
!cleared &&
|
|
876
928
|
!box.borderBottom &&
|
|
877
929
|
!box.padBottom &&
|
|
878
|
-
box
|
|
930
|
+
specifiedHeight(box) === null &&
|
|
879
931
|
!establishesBFC(box)
|
|
880
932
|
) {
|
|
881
933
|
const height = y - contentTop;
|
|
@@ -1063,6 +1115,7 @@ function layoutInlineContent(
|
|
|
1063
1115
|
floats,
|
|
1064
1116
|
originX: contentLeft,
|
|
1065
1117
|
clipText: ctx.clipText,
|
|
1118
|
+
firstStrut: MARKER_ROOM.get(box),
|
|
1066
1119
|
floatBoxes: floated
|
|
1067
1120
|
? {
|
|
1068
1121
|
size: (child: Box) => sizeFloat(child, ctx, contentWidth),
|
|
@@ -1260,7 +1313,8 @@ function makesNothing(box: Box): boolean {
|
|
|
1260
1313
|
}
|
|
1261
1314
|
|
|
1262
1315
|
/** Whether a `::first-line` style sets its line's text otherwise than its
|
|
1263
|
-
* block's: in another face, size or line height,
|
|
1316
|
+
* block's: in another face, size or line height, spaced apart, or in
|
|
1317
|
+
* capitals. */
|
|
1264
1318
|
function setsFonts(line: ComputedStyle, block: ComputedStyle): boolean {
|
|
1265
1319
|
return (
|
|
1266
1320
|
line.fontFamily !== block.fontFamily ||
|
|
@@ -1275,7 +1329,8 @@ function setsFonts(line: ComputedStyle, block: ComputedStyle): boolean {
|
|
|
1275
1329
|
line.fontVariantLigatures !== block.fontVariantLigatures ||
|
|
1276
1330
|
line.fontVariantPosition !== block.fontVariantPosition ||
|
|
1277
1331
|
line.fontKerning !== block.fontKerning ||
|
|
1278
|
-
line.fontFeatureSettings !== block.fontFeatureSettings
|
|
1332
|
+
line.fontFeatureSettings !== block.fontFeatureSettings ||
|
|
1333
|
+
line.textTransform !== block.textTransform
|
|
1279
1334
|
);
|
|
1280
1335
|
}
|
|
1281
1336
|
|
|
@@ -1685,13 +1740,26 @@ function layoutBox(
|
|
|
1685
1740
|
: outerFloats;
|
|
1686
1741
|
// the line a pass before gave a marker, which this one may not
|
|
1687
1742
|
if (box.marker) box.lines = null;
|
|
1743
|
+
if (box.marker && ctx.fonts) {
|
|
1744
|
+
const room = markerRoom(box, box.marker, ctx.fonts);
|
|
1745
|
+
if (room) MARKER_ROOM.set(box, room);
|
|
1746
|
+
else MARKER_ROOM.delete(box);
|
|
1747
|
+
}
|
|
1688
1748
|
const flow = layoutChildren(box, ctx, floats, box.contentY, contentWidth);
|
|
1689
1749
|
// A list item with a marker and no line holds one, the marker's, a line
|
|
1690
1750
|
// of its own face tall: an empty `<li>` is a line tall in a browser, and
|
|
1691
1751
|
// an inline-block around one sits on its marker's baseline.
|
|
1692
1752
|
let height = flow.height;
|
|
1693
1753
|
if (box.marker && ctx.fonts && !firstLineIn(box)) {
|
|
1694
|
-
const
|
|
1754
|
+
const own = strutOf(ctx.fonts, box.style);
|
|
1755
|
+
// as tall as the marker on it, as a first line with text is
|
|
1756
|
+
const room = MARKER_ROOM.get(box);
|
|
1757
|
+
const strut = room
|
|
1758
|
+
? {
|
|
1759
|
+
ascent: Math.max(own.ascent, room.ascent),
|
|
1760
|
+
descent: Math.max(own.descent, room.descent),
|
|
1761
|
+
}
|
|
1762
|
+
: own;
|
|
1695
1763
|
const line = strut.ascent + strut.descent;
|
|
1696
1764
|
box.lines = [
|
|
1697
1765
|
{
|
|
@@ -1735,6 +1803,33 @@ function layoutBox(
|
|
|
1735
1803
|
if (box.marker) layoutMarker(box, box.marker, ctx);
|
|
1736
1804
|
}
|
|
1737
1805
|
|
|
1806
|
+
/**
|
|
1807
|
+
* The room a list item's marker takes about its first line's baseline,
|
|
1808
|
+
* handed to the box that line is in: an image's height above it, its
|
|
1809
|
+
* bottom on the baseline as an inline image's is, and a marker in a face
|
|
1810
|
+
* of its own — a `::marker` rule's — its face at its line height. CSS 2.1
|
|
1811
|
+
* (12.5.1) leaves where an outside marker goes to the user agent; Blink
|
|
1812
|
+
* and Gecko both set it on the item's first line and make the line as tall
|
|
1813
|
+
* as it, and design 032's bullets, images taller than its 10px text, set
|
|
1814
|
+
* every item's first line lower than ours did.
|
|
1815
|
+
*/
|
|
1816
|
+
const MARKER_ROOM = new WeakMap<Box, InlineDecoration>();
|
|
1817
|
+
|
|
1818
|
+
function markerRoom(
|
|
1819
|
+
box: Box,
|
|
1820
|
+
marker: Marker,
|
|
1821
|
+
fonts: FontsLike,
|
|
1822
|
+
): InlineDecoration | null {
|
|
1823
|
+
if (marker.image) {
|
|
1824
|
+
return marker.image.height > 0
|
|
1825
|
+
? { ascent: marker.image.height, descent: 0 }
|
|
1826
|
+
: null;
|
|
1827
|
+
}
|
|
1828
|
+
return marker.style && marker.style !== box.style
|
|
1829
|
+
? strutOf(fonts, marker.style)
|
|
1830
|
+
: null;
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1738
1833
|
/** The first line box anywhere under a box, in layout order. */
|
|
1739
1834
|
function firstLineIn(box: Box): LineBox | null {
|
|
1740
1835
|
if (box.lines?.length) return box.lines[0];
|
|
@@ -4015,12 +4110,19 @@ export function applyRelativeOffsets(box: Box): void {
|
|
|
4015
4110
|
if (transformed(style) && box.kind !== 'inline') {
|
|
4016
4111
|
// a transform moves the box it is on, and an inline box is none
|
|
4017
4112
|
const [dx, dy] = translationOf(box);
|
|
4018
|
-
if (dx || dy)
|
|
4113
|
+
if (dx || dy) {
|
|
4114
|
+
translate(box, dx, dy);
|
|
4115
|
+
movedOffLine(box);
|
|
4116
|
+
}
|
|
4019
4117
|
}
|
|
4020
4118
|
if (style.position !== 'relative' && style.position !== 'sticky') return;
|
|
4021
4119
|
const [dx, dy] = relativeOffset(box);
|
|
4120
|
+
if (!(dx || dy)) return;
|
|
4022
4121
|
translate(box, dx, dy);
|
|
4023
|
-
if (box.kind !== 'inline'
|
|
4122
|
+
if (box.kind !== 'inline') {
|
|
4123
|
+
movedOffLine(box);
|
|
4124
|
+
return;
|
|
4125
|
+
}
|
|
4024
4126
|
offsetInline(box, dx, dy);
|
|
4025
4127
|
// the blocks it was broken around move with it (9.2.1.1)
|
|
4026
4128
|
const blocks = box.cut ? CUT_BLOCKS.get(box) : undefined;
|
|
@@ -4089,10 +4191,34 @@ function offsetInline(box: Box, dx: number, dy: number): void {
|
|
|
4089
4191
|
y: (was?.y ?? 0) + dy,
|
|
4090
4192
|
});
|
|
4091
4193
|
SHIFTED_LINES.add(line);
|
|
4194
|
+
markMovedOff(lines, line);
|
|
4092
4195
|
}
|
|
4093
4196
|
}
|
|
4094
4197
|
}
|
|
4095
4198
|
|
|
4199
|
+
/** Mark the line an inline-block or an image a box moved off was placed
|
|
4200
|
+
* on, where it was on one: the line stays, and the box is drawn where it
|
|
4201
|
+
* went (`MOVED_OFF_LINES`). */
|
|
4202
|
+
function movedOffLine(box: Box): void {
|
|
4203
|
+
let block = box.parent;
|
|
4204
|
+
while (block && block.kind === 'inline') block = block.parent;
|
|
4205
|
+
const lines = block?.lines;
|
|
4206
|
+
if (!lines) return;
|
|
4207
|
+
for (const line of lines) {
|
|
4208
|
+
for (const placed of line.atomics) {
|
|
4209
|
+
if (placed.box !== box) continue;
|
|
4210
|
+
markMovedOff(lines, line);
|
|
4211
|
+
return;
|
|
4212
|
+
}
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
|
|
4216
|
+
function markMovedOff(lines: LineBox[], line: LineBox): void {
|
|
4217
|
+
const moved = MOVED_OFF_LINES.get(lines);
|
|
4218
|
+
if (moved) moved.add(line);
|
|
4219
|
+
else MOVED_OFF_LINES.set(lines, new Set([line]));
|
|
4220
|
+
}
|
|
4221
|
+
|
|
4096
4222
|
/** Whether `inner` is `outer` or inside it. */
|
|
4097
4223
|
function holds(outer: Box, inner: Box): boolean {
|
|
4098
4224
|
for (let at: Box | null = inner; at; at = at.parent) {
|