@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/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,
|
|
@@ -58,6 +60,7 @@ import type {
|
|
|
58
60
|
import { FloatContext } from './floats.js';
|
|
59
61
|
import {
|
|
60
62
|
faceExtentOf,
|
|
63
|
+
joinsBefore,
|
|
61
64
|
layoutInline,
|
|
62
65
|
lineHeightMultiplier,
|
|
63
66
|
spaceBreaksOnly,
|
|
@@ -69,7 +72,15 @@ import type { TextRun } from '../../richtext/index.js';
|
|
|
69
72
|
import { layoutFlex } from './flex.js';
|
|
70
73
|
import { finishCaptions, layoutTable } from './table.js';
|
|
71
74
|
import { collapseEdges } from './collapse.js';
|
|
72
|
-
import {
|
|
75
|
+
import {
|
|
76
|
+
clipsFor,
|
|
77
|
+
computePaintBounds,
|
|
78
|
+
FIXED_BOXES,
|
|
79
|
+
fixedToViewport,
|
|
80
|
+
hoistNegative,
|
|
81
|
+
OUT_OF_FLOW_REACH,
|
|
82
|
+
stackLayers,
|
|
83
|
+
} from '../paint.js';
|
|
73
84
|
|
|
74
85
|
export interface LayoutContext {
|
|
75
86
|
fonts: FontsLike | null;
|
|
@@ -180,7 +191,9 @@ export function layoutDocument(
|
|
|
180
191
|
// there — or `<p>hi</p>` would stand 8px lower than the same paragraph in
|
|
181
192
|
// `<body>`.
|
|
182
193
|
resolveEdges(root, viewportWidth);
|
|
194
|
+
const body = fragmentBodyHeight(tree, viewportHeight);
|
|
183
195
|
const leading = root.marginTop;
|
|
196
|
+
const trailing = root.marginBottom;
|
|
184
197
|
root.padRight += root.marginRight;
|
|
185
198
|
root.padBottom += root.marginBottom;
|
|
186
199
|
root.padLeft += root.marginLeft;
|
|
@@ -188,6 +201,7 @@ export function layoutDocument(
|
|
|
188
201
|
root.marginRight = 0;
|
|
189
202
|
root.marginBottom = 0;
|
|
190
203
|
root.marginLeft = 0;
|
|
204
|
+
if (body) handPercentBase(root, body.inner);
|
|
191
205
|
|
|
192
206
|
const contentWidth = Math.max(0, viewportWidth - root.horizontalExtra);
|
|
193
207
|
const floats = new FloatContext(root.contentX, root.contentX + contentWidth);
|
|
@@ -195,7 +209,7 @@ export function layoutDocument(
|
|
|
195
209
|
// height on the root element resolves against (CSS 2.1 10.1, 10.5); a
|
|
196
210
|
// fragment has no root element, and the box standing in for its body has
|
|
197
211
|
// the body's `auto` height to give
|
|
198
|
-
let readsViewportHeight =
|
|
212
|
+
let readsViewportHeight = body?.viewport === true;
|
|
199
213
|
for (const child of root.children) {
|
|
200
214
|
if (child.el?.name !== 'html') continue;
|
|
201
215
|
child.percentHeightBase = viewportHeight;
|
|
@@ -218,7 +232,9 @@ export function layoutDocument(
|
|
|
218
232
|
);
|
|
219
233
|
const floatBottom =
|
|
220
234
|
floats.bottom === -Infinity ? 0 : floats.bottom - root.contentY;
|
|
221
|
-
root.height =
|
|
235
|
+
root.height = body
|
|
236
|
+
? leading + body.outer + trailing
|
|
237
|
+
: Math.max(flow.height, floatBottom) + root.verticalExtra;
|
|
222
238
|
|
|
223
239
|
// Positioned boxes last, and in the order they were found, so a later one
|
|
224
240
|
// can be positioned against an earlier one's resolved rectangle.
|
|
@@ -231,19 +247,35 @@ export function layoutDocument(
|
|
|
231
247
|
// against where it *was* — which is the whole of what makes it relative —
|
|
232
248
|
// and the ink bounds are computed after that, so culling sees where boxes
|
|
233
249
|
// ended up rather than where they were laid out.
|
|
234
|
-
if (tree.relative) applyRelativeOffsets(root);
|
|
250
|
+
if (tree.relative) applyRelativeOffsets(root, viewportWidth, viewportHeight);
|
|
235
251
|
const reach = computePaintBounds(root, tree.movedInline);
|
|
236
252
|
if (tree.negative) hoistNegative(root);
|
|
237
253
|
stackLayers(root);
|
|
238
254
|
|
|
239
255
|
// The document is as tall as what overflows the root, not the root: an
|
|
240
256
|
// `html, body { height: 100% }` a window tall holds a message longer than
|
|
241
|
-
// the window, and the element sizes to all of it.
|
|
257
|
+
// the window, and the element sizes to all of it. Not what a box that
|
|
258
|
+
// clips its overflow holds: that is its own to scroll, and the root's
|
|
259
|
+
// scrollable overflow takes in a positioned box only where no box on the
|
|
260
|
+
// way to it clips it (CSS Overflow 3, 2.2): an absolute box 900px down
|
|
261
|
+
// an `overflow: hidden` card made a page a browser shows 400px tall run
|
|
262
|
+
// on to 980, blank
|
|
242
263
|
let bottom = Math.max(root.height, reach);
|
|
264
|
+
let fixed: Box[] | null = null;
|
|
243
265
|
for (const { box } of ctx.positioned) {
|
|
244
|
-
if (box.style.position
|
|
245
|
-
|
|
266
|
+
if (box.style.position === 'fixed') {
|
|
267
|
+
if (fixedToViewport(box)) (fixed ??= []).push(box);
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (clipsFor(box, root).length) continue;
|
|
271
|
+
// and what it holds, where it does not clip it: a page set in an
|
|
272
|
+
// absolute wrapper 497px tall runs on below it, and the document with
|
|
273
|
+
// it — design 094 was a window tall and no taller, its text past the
|
|
274
|
+
// end of the scroll
|
|
275
|
+
bottom = Math.max(bottom, OUT_OF_FLOW_REACH.get(box) ?? box.y + box.height);
|
|
246
276
|
}
|
|
277
|
+
if (fixed) FIXED_BOXES.set(tree, fixed);
|
|
278
|
+
else FIXED_BOXES.delete(tree);
|
|
247
279
|
return {
|
|
248
280
|
width: viewportWidth,
|
|
249
281
|
height: bottom,
|
|
@@ -251,6 +283,42 @@ export function layoutDocument(
|
|
|
251
283
|
};
|
|
252
284
|
}
|
|
253
285
|
|
|
286
|
+
/**
|
|
287
|
+
* How tall the body a fragment's root box stands in for says it is, where
|
|
288
|
+
* that is definite (CSS 2.1 10.5): a length, `100vh` among them, or a
|
|
289
|
+
* percentage of the `<html>` implied around it where that has a height of
|
|
290
|
+
* its own — a length, or a percentage of the viewport, as `html, body {
|
|
291
|
+
* height: 100% }` fills a window. Its border box, and its content box,
|
|
292
|
+
* which what it holds takes its percentages of; `viewport` where the
|
|
293
|
+
* answer read the viewport's height. Null where the document has an
|
|
294
|
+
* `<html>` or a `<body>`, and where the body's height is `auto`, and the
|
|
295
|
+
* box is as tall as what is in it. Measured before its margins are folded
|
|
296
|
+
* into its padding.
|
|
297
|
+
*/
|
|
298
|
+
function fragmentBodyHeight(
|
|
299
|
+
tree: BoxTree,
|
|
300
|
+
viewportHeight: number,
|
|
301
|
+
): { outer: number; inner: number; viewport: boolean } | null {
|
|
302
|
+
const html = tree.impliedHtml;
|
|
303
|
+
if (!html) return null;
|
|
304
|
+
const root = tree.root;
|
|
305
|
+
const style = root.style;
|
|
306
|
+
const base = resolveOrNull(html.height, viewportHeight);
|
|
307
|
+
const set = resolveOrNull(style.height, base ?? NaN);
|
|
308
|
+
if (set === null) return null;
|
|
309
|
+
root.percentHeightBase = base ?? NaN;
|
|
310
|
+
const extra = root.verticalExtra;
|
|
311
|
+
const outer = clampHeight(
|
|
312
|
+
root,
|
|
313
|
+
style.boxSizing === 'border-box' ? Math.max(set, extra) : set + extra,
|
|
314
|
+
);
|
|
315
|
+
return {
|
|
316
|
+
outer,
|
|
317
|
+
inner: Math.max(0, outer - extra),
|
|
318
|
+
viewport: isPct(style.height) && isPct(html.height),
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
|
|
254
322
|
/** No margin at all, shared: most joins leave a strut as it was. */
|
|
255
323
|
const NO_MARGIN: MarginStrut = Object.freeze({ pos: 0, neg: 0 });
|
|
256
324
|
|
|
@@ -725,6 +793,12 @@ function layoutChildren(
|
|
|
725
793
|
// this box's first formatted line is its first child's in flow (CSS 2.1
|
|
726
794
|
// 5.12.1), which its `::first-line` is handed to
|
|
727
795
|
let firstLine = ctx.firstLine ? firstLineOf(box) : null;
|
|
796
|
+
// and the room a list item's marker takes on it (`MARKER_ROOM`): an
|
|
797
|
+
// inside marker's is handed down to the line it is on, and an outside
|
|
798
|
+
// one's is room above the baseline of the first child that has a line,
|
|
799
|
+
// which goes lower where the marker reaches higher (`MARKER_ASCENT`)
|
|
800
|
+
let pushed = MARKER_ASCENT.get(box) ?? null;
|
|
801
|
+
let marked = pushed === null ? (MARKER_ROOM.get(box) ?? null) : null;
|
|
728
802
|
const clamp = ctx.clamp ?? null;
|
|
729
803
|
|
|
730
804
|
for (const child of box.children) {
|
|
@@ -743,6 +817,10 @@ function layoutChildren(
|
|
|
743
817
|
if (!FIRST_LINE.has(child)) HANDED.set(child, firstLine);
|
|
744
818
|
firstLine = null;
|
|
745
819
|
}
|
|
820
|
+
if (marked && !child.outOfFlow && !child.isFloat) {
|
|
821
|
+
MARKER_ROOM.set(child, marked);
|
|
822
|
+
marked = null;
|
|
823
|
+
}
|
|
746
824
|
if (child.outOfFlow) {
|
|
747
825
|
placeStatic(
|
|
748
826
|
child,
|
|
@@ -814,6 +892,7 @@ function layoutChildren(
|
|
|
814
892
|
}
|
|
815
893
|
|
|
816
894
|
const counted = clamp?.fit?.lines ?? 0;
|
|
895
|
+
const floatMark = floats.count;
|
|
817
896
|
if (
|
|
818
897
|
!floats.isEmpty &&
|
|
819
898
|
(child.kind === 'replaced' || establishesBFC(child))
|
|
@@ -822,6 +901,17 @@ function layoutChildren(
|
|
|
822
901
|
} else {
|
|
823
902
|
layoutBlockLevel(child, ctx, floats, contentLeft, childY, contentWidth);
|
|
824
903
|
}
|
|
904
|
+
if (pushed !== null) {
|
|
905
|
+
const line = firstLineIn(child);
|
|
906
|
+
if (line) {
|
|
907
|
+
const push = pushed - (line.y + line.baseline - child.y);
|
|
908
|
+
if (push > 0) {
|
|
909
|
+
translate(child, 0, push);
|
|
910
|
+
floats.moveSince(floatMark, push);
|
|
911
|
+
}
|
|
912
|
+
pushed = null;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
825
915
|
// a block with none of the flow's lines in it — an image, a box of a
|
|
826
916
|
// formatting context of its own — puts the clamp point of an `auto`
|
|
827
917
|
// clamp before it where it ends below the room
|
|
@@ -870,12 +960,15 @@ function layoutChildren(
|
|
|
870
960
|
// escapes, and the caller merges it into the box's own bottom margin so
|
|
871
961
|
// the next sibling still sees it. Dropping it here was the bug that made
|
|
872
962
|
// `<div><p>…</p></div><p>…</p>` set the two paragraphs solid.
|
|
963
|
+
// A percentage of a height that depends on content computes to `auto`
|
|
964
|
+
// (CSS 2.1 10.5), and lets it through as `auto` does: a `height: 100%`
|
|
965
|
+
// wrapper in an `auto` body kept its last child's margin inside it
|
|
873
966
|
if (
|
|
874
967
|
!first &&
|
|
875
968
|
!cleared &&
|
|
876
969
|
!box.borderBottom &&
|
|
877
970
|
!box.padBottom &&
|
|
878
|
-
box
|
|
971
|
+
specifiedHeight(box) === null &&
|
|
879
972
|
!establishesBFC(box)
|
|
880
973
|
) {
|
|
881
974
|
const height = y - contentTop;
|
|
@@ -978,11 +1071,20 @@ function layoutBesideFloats(
|
|
|
978
1071
|
band = over;
|
|
979
1072
|
continue;
|
|
980
1073
|
}
|
|
1074
|
+
// Where no float narrows the room on either side, the box is where
|
|
1075
|
+
// it would be were there none, and one too wide for its containing
|
|
1076
|
+
// block overflows it here: below the floats it would be the same box
|
|
1077
|
+
// in the same room. Blink tests a fit only against a side a float is
|
|
1078
|
+
// on (`can_expand_outside_opportunity`, BlockLayoutAlgorithm::
|
|
1079
|
+
// LayoutNewFormattingContext). Design 209's 247px heading, which
|
|
1080
|
+
// clips its overflow, in a 240px column beside a float that ends
|
|
1081
|
+
// left of the column, went under the float, 555px down.
|
|
1082
|
+
if (lo === -Infinity && hi === Infinity) fits = true;
|
|
981
1083
|
// What is too wide overflows at the end of the line, as far as a
|
|
982
1084
|
// negative margin there takes it, but no further into a float or
|
|
983
1085
|
// past the room; at the start it would run into the floats, and
|
|
984
1086
|
// waits below them (the suite's floats-wrap-bfc-with-margin tests)
|
|
985
|
-
if (inRoom) {
|
|
1087
|
+
else if (inRoom) {
|
|
986
1088
|
fits = box.marginLeft + box.width + box.marginRight <= room + 0.5;
|
|
987
1089
|
} else if (rtl) {
|
|
988
1090
|
const limit = Math.max(contentLeft + Math.min(0, box.marginLeft), lo);
|
|
@@ -1063,6 +1165,7 @@ function layoutInlineContent(
|
|
|
1063
1165
|
floats,
|
|
1064
1166
|
originX: contentLeft,
|
|
1065
1167
|
clipText: ctx.clipText,
|
|
1168
|
+
firstStrut: MARKER_ROOM.get(box),
|
|
1066
1169
|
floatBoxes: floated
|
|
1067
1170
|
? {
|
|
1068
1171
|
size: (child: Box) => sizeFloat(child, ctx, contentWidth),
|
|
@@ -1260,7 +1363,8 @@ function makesNothing(box: Box): boolean {
|
|
|
1260
1363
|
}
|
|
1261
1364
|
|
|
1262
1365
|
/** Whether a `::first-line` style sets its line's text otherwise than its
|
|
1263
|
-
* block's: in another face, size or line height,
|
|
1366
|
+
* block's: in another face, size or line height, spaced apart, or in
|
|
1367
|
+
* capitals. */
|
|
1264
1368
|
function setsFonts(line: ComputedStyle, block: ComputedStyle): boolean {
|
|
1265
1369
|
return (
|
|
1266
1370
|
line.fontFamily !== block.fontFamily ||
|
|
@@ -1275,7 +1379,8 @@ function setsFonts(line: ComputedStyle, block: ComputedStyle): boolean {
|
|
|
1275
1379
|
line.fontVariantLigatures !== block.fontVariantLigatures ||
|
|
1276
1380
|
line.fontVariantPosition !== block.fontVariantPosition ||
|
|
1277
1381
|
line.fontKerning !== block.fontKerning ||
|
|
1278
|
-
line.fontFeatureSettings !== block.fontFeatureSettings
|
|
1382
|
+
line.fontFeatureSettings !== block.fontFeatureSettings ||
|
|
1383
|
+
line.textTransform !== block.textTransform
|
|
1279
1384
|
);
|
|
1280
1385
|
}
|
|
1281
1386
|
|
|
@@ -1685,13 +1790,28 @@ function layoutBox(
|
|
|
1685
1790
|
: outerFloats;
|
|
1686
1791
|
// the line a pass before gave a marker, which this one may not
|
|
1687
1792
|
if (box.marker) box.lines = null;
|
|
1793
|
+
if (box.marker && ctx.fonts) {
|
|
1794
|
+
const room = markerRoom(box, box.marker, ctx.fonts);
|
|
1795
|
+
if (room) MARKER_ROOM.set(box, room);
|
|
1796
|
+
else MARKER_ROOM.delete(box);
|
|
1797
|
+
if (box.style.listStylePosition === 'inside') MARKER_ASCENT.delete(box);
|
|
1798
|
+
else MARKER_ASCENT.set(box, markerAscent(box, box.marker, ctx.fonts));
|
|
1799
|
+
}
|
|
1688
1800
|
const flow = layoutChildren(box, ctx, floats, box.contentY, contentWidth);
|
|
1689
1801
|
// A list item with a marker and no line holds one, the marker's, a line
|
|
1690
1802
|
// of its own face tall: an empty `<li>` is a line tall in a browser, and
|
|
1691
1803
|
// an inline-block around one sits on its marker's baseline.
|
|
1692
1804
|
let height = flow.height;
|
|
1693
1805
|
if (box.marker && ctx.fonts && !firstLineIn(box)) {
|
|
1694
|
-
const
|
|
1806
|
+
const own = strutOf(ctx.fonts, box.style);
|
|
1807
|
+
// as tall as the marker on it, as a first line with text is
|
|
1808
|
+
const room = MARKER_ROOM.get(box);
|
|
1809
|
+
const strut = room
|
|
1810
|
+
? {
|
|
1811
|
+
ascent: Math.max(own.ascent, room.ascent),
|
|
1812
|
+
descent: Math.max(own.descent, room.descent),
|
|
1813
|
+
}
|
|
1814
|
+
: own;
|
|
1695
1815
|
const line = strut.ascent + strut.descent;
|
|
1696
1816
|
box.lines = [
|
|
1697
1817
|
{
|
|
@@ -1735,6 +1855,55 @@ function layoutBox(
|
|
|
1735
1855
|
if (box.marker) layoutMarker(box, box.marker, ctx);
|
|
1736
1856
|
}
|
|
1737
1857
|
|
|
1858
|
+
/**
|
|
1859
|
+
* The room a list item's marker takes about its first line's baseline,
|
|
1860
|
+
* handed to the box that line is in: an image's height above it, its
|
|
1861
|
+
* bottom on the baseline as an inline image's is, and a marker in a face
|
|
1862
|
+
* of its own — a `::marker` rule's — its face at its line height. CSS 2.1
|
|
1863
|
+
* (12.5.1) leaves where an outside marker goes to the user agent; Blink
|
|
1864
|
+
* and Gecko both set it on the item's first line and make the line as tall
|
|
1865
|
+
* as it, and design 032's bullets, images taller than its 10px text, set
|
|
1866
|
+
* every item's first line lower than ours did.
|
|
1867
|
+
*/
|
|
1868
|
+
const MARKER_ROOM = new WeakMap<Box, InlineDecoration>();
|
|
1869
|
+
|
|
1870
|
+
function markerRoom(
|
|
1871
|
+
box: Box,
|
|
1872
|
+
marker: Marker,
|
|
1873
|
+
fonts: FontsLike,
|
|
1874
|
+
): InlineDecoration | null {
|
|
1875
|
+
if (marker.image) {
|
|
1876
|
+
return marker.image.height > 0
|
|
1877
|
+
? { ascent: marker.image.height, descent: 0 }
|
|
1878
|
+
: null;
|
|
1879
|
+
}
|
|
1880
|
+
return marker.style && marker.style !== box.style
|
|
1881
|
+
? strutOf(fonts, marker.style)
|
|
1882
|
+
: null;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
/**
|
|
1886
|
+
* How far an outside marker reaches above the baseline it sits on. The
|
|
1887
|
+
* marker is a line of its own face, the item's or a `::marker` rule's, so a
|
|
1888
|
+
* bullet or a number reaches its face's ascent on that line, and an image,
|
|
1889
|
+
* set on the line bottom on the baseline, the higher of that and its own
|
|
1890
|
+
* height. Where the item's first line is in a block inside it — a link set
|
|
1891
|
+
* `display: block`, a paragraph — and the block's first baseline is nearer
|
|
1892
|
+
* its top than that, the block goes lower by the difference, and its line
|
|
1893
|
+
* stays the height it was: CSS 2.1 (12.5.1) leaves this to the user agent,
|
|
1894
|
+
* and Blink aligns the marker's baseline with the block's and pushes the
|
|
1895
|
+
* block down (`UnpositionedListMarker::AddToBox`). Its first line grew
|
|
1896
|
+
* instead, and design 196's links, under 18px bullets, were 21px tall where
|
|
1897
|
+
* Chrome has them 14px, 7px lower. On the item's own line the two come to
|
|
1898
|
+
* the same: the line grows by what the push would have moved it.
|
|
1899
|
+
*/
|
|
1900
|
+
const MARKER_ASCENT = new WeakMap<Box, number>();
|
|
1901
|
+
|
|
1902
|
+
function markerAscent(box: Box, marker: Marker, fonts: FontsLike): number {
|
|
1903
|
+
const ascent = strutOf(fonts, marker.style ?? box.style).ascent;
|
|
1904
|
+
return marker.image ? Math.max(marker.image.height, ascent) : ascent;
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1738
1907
|
/** The first line box anywhere under a box, in layout order. */
|
|
1739
1908
|
function firstLineIn(box: Box): LineBox | null {
|
|
1740
1909
|
if (box.lines?.length) return box.lines[0];
|
|
@@ -2732,10 +2901,19 @@ function wordBound(box: Box, fonts: FontsLike, measured: boolean): number {
|
|
|
2732
2901
|
let last: object | null = null;
|
|
2733
2902
|
for (const line of box.lines) {
|
|
2734
2903
|
if (unbroken) widest = Math.max(widest, line.width);
|
|
2904
|
+
// a word across inline boxes' edges is in two fragments of text, and
|
|
2905
|
+
// as wide as its parts in both and the edges between: bound by the
|
|
2906
|
+
// widest word of each, a float in a narrow room took a letter of a
|
|
2907
|
+
// word of letters in bordered spans for its least width, and was cut
|
|
2908
|
+
// to the room (`joinsBefore`)
|
|
2909
|
+
let chain = 0;
|
|
2735
2910
|
for (const text of line.texts) {
|
|
2736
|
-
|
|
2911
|
+
const joins = joinsBefore(text);
|
|
2912
|
+
if (joins === undefined && text.layout === last) continue;
|
|
2737
2913
|
last = text.layout;
|
|
2738
|
-
|
|
2914
|
+
const own = widestWord(fonts, text.layout, measured);
|
|
2915
|
+
chain = joins === undefined ? own : chain + joins + own;
|
|
2916
|
+
widest = Math.max(widest, chain);
|
|
2739
2917
|
}
|
|
2740
2918
|
for (const placed of line.atomics) {
|
|
2741
2919
|
const b = placed.box;
|
|
@@ -4008,19 +4186,35 @@ export function establishesBFC(box: Box): boolean {
|
|
|
4008
4186
|
|
|
4009
4187
|
/** What a box's `position: relative` offset moves it by, applied after
|
|
4010
4188
|
* layout so it does not affect anything else's position — which is the
|
|
4011
|
-
* whole of what makes it *relative* — and its translation with it
|
|
4012
|
-
|
|
4013
|
-
|
|
4189
|
+
* whole of what makes it *relative* — and its translation with it, and
|
|
4190
|
+
* where `position: sticky` puts one at rest (`stickyOffset`). */
|
|
4191
|
+
export function applyRelativeOffsets(
|
|
4192
|
+
box: Box,
|
|
4193
|
+
viewportWidth: number,
|
|
4194
|
+
viewportHeight: number,
|
|
4195
|
+
): void {
|
|
4196
|
+
for (const child of box.children)
|
|
4197
|
+
applyRelativeOffsets(child, viewportWidth, viewportHeight);
|
|
4014
4198
|
const style = box.style;
|
|
4015
4199
|
if (transformed(style) && box.kind !== 'inline') {
|
|
4016
4200
|
// a transform moves the box it is on, and an inline box is none
|
|
4017
4201
|
const [dx, dy] = translationOf(box);
|
|
4018
|
-
if (dx || dy)
|
|
4202
|
+
if (dx || dy) {
|
|
4203
|
+
translate(box, dx, dy);
|
|
4204
|
+
movedOffLine(box);
|
|
4205
|
+
}
|
|
4019
4206
|
}
|
|
4020
4207
|
if (style.position !== 'relative' && style.position !== 'sticky') return;
|
|
4021
|
-
const [dx, dy] =
|
|
4208
|
+
const [dx, dy] =
|
|
4209
|
+
style.position === 'sticky'
|
|
4210
|
+
? stickyOffset(box, viewportWidth, viewportHeight)
|
|
4211
|
+
: relativeOffset(box);
|
|
4212
|
+
if (!(dx || dy)) return;
|
|
4022
4213
|
translate(box, dx, dy);
|
|
4023
|
-
if (box.kind !== 'inline'
|
|
4214
|
+
if (box.kind !== 'inline') {
|
|
4215
|
+
movedOffLine(box);
|
|
4216
|
+
return;
|
|
4217
|
+
}
|
|
4024
4218
|
offsetInline(box, dx, dy);
|
|
4025
4219
|
// the blocks it was broken around move with it (9.2.1.1)
|
|
4026
4220
|
const blocks = box.cut ? CUT_BLOCKS.get(box) : undefined;
|
|
@@ -4035,10 +4229,10 @@ export function transformed(style: ComputedStyle): boolean {
|
|
|
4035
4229
|
|
|
4036
4230
|
/** How far `translate` and a `transform` move a box: a percentage is of
|
|
4037
4231
|
* its own border box (CSS Transforms 1, 7). */
|
|
4038
|
-
function translationOf(box: Box): [number, number] {
|
|
4232
|
+
function translationOf(box: Box, style = box.style): [number, number] {
|
|
4039
4233
|
let dx = 0;
|
|
4040
4234
|
let dy = 0;
|
|
4041
|
-
for (const moved of [
|
|
4235
|
+
for (const moved of [style.translate, style.transformTranslate]) {
|
|
4042
4236
|
if (!moved) continue;
|
|
4043
4237
|
dx += resolve(moved[0], box.width, 0);
|
|
4044
4238
|
dy += resolve(moved[1], box.height, 0);
|
|
@@ -4046,6 +4240,21 @@ function translationOf(box: Box): [number, number] {
|
|
|
4046
4240
|
return [dx, dy];
|
|
4047
4241
|
}
|
|
4048
4242
|
|
|
4243
|
+
/**
|
|
4244
|
+
* Move a laid-out box, and everything in it, by how far its translation
|
|
4245
|
+
* changed when its style went from `was` to the one it has — what
|
|
4246
|
+
* `applyRelativeOffsets` moves it by now, less what it moved it by then.
|
|
4247
|
+
* For a style changed in place, where nothing else moves (a `transform` is
|
|
4248
|
+
* what makes that true). Not for an inline box, whose text is what moves.
|
|
4249
|
+
*/
|
|
4250
|
+
export function retranslate(box: Box, was: ComputedStyle): void {
|
|
4251
|
+
const [x0, y0] = translationOf(box, was);
|
|
4252
|
+
const [x1, y1] = translationOf(box);
|
|
4253
|
+
if (x1 === x0 && y1 === y0) return;
|
|
4254
|
+
translate(box, x1 - x0, y1 - y0);
|
|
4255
|
+
movedOffLine(box);
|
|
4256
|
+
}
|
|
4257
|
+
|
|
4049
4258
|
/** How far `position: relative` moves a box. */
|
|
4050
4259
|
function relativeOffset(box: Box): [number, number] {
|
|
4051
4260
|
const style = box.style;
|
|
@@ -4064,6 +4273,111 @@ function relativeOffset(box: Box): [number, number] {
|
|
|
4064
4273
|
return [dx, dy];
|
|
4065
4274
|
}
|
|
4066
4275
|
|
|
4276
|
+
/**
|
|
4277
|
+
* Where `position: sticky` puts a box at rest (CSS Positioned Layout 3,
|
|
4278
|
+
* 3.4): moved only as far as keeps its border box inside the sticky view
|
|
4279
|
+
* rectangle — its nearest scroll container's scrollport, scrolled to its
|
|
4280
|
+
* start, less its insets — and no further than keeps its margin box in its
|
|
4281
|
+
* containing block, `top` winning over `bottom` and the start side over the
|
|
4282
|
+
* end. Nothing here scrolls a box the document holds, so for one of those
|
|
4283
|
+
* this is where the box stays. The viewport does scroll, and the document
|
|
4284
|
+
* with it, and a sticky box does not follow: against the viewport only its
|
|
4285
|
+
* `top` and its start side are kept, which at rest put a box as a browser
|
|
4286
|
+
* does, while a `bottom: 0` would pin a footer to the middle of the page
|
|
4287
|
+
* once it was scrolled. Taken as `relative`, a box stood its `top` below
|
|
4288
|
+
* where a browser starts it: Wikipedia's contents, 24px low.
|
|
4289
|
+
*/
|
|
4290
|
+
function stickyOffset(
|
|
4291
|
+
box: Box,
|
|
4292
|
+
viewportWidth: number,
|
|
4293
|
+
viewportHeight: number,
|
|
4294
|
+
): [number, number] {
|
|
4295
|
+
const style = box.style;
|
|
4296
|
+
// the scroll container a sticky box keeps to: the nearest box that
|
|
4297
|
+
// scrolls, or the viewport, whose are the root's and the body's overflow
|
|
4298
|
+
let port = box.parent;
|
|
4299
|
+
while (port && !scrolls(port.style)) port = port.parent;
|
|
4300
|
+
const tag = port?.el?.name;
|
|
4301
|
+
const inside = !!port && tag !== 'html' && tag !== 'body';
|
|
4302
|
+
let cb = box.parent;
|
|
4303
|
+
while (cb && cb.kind === 'inline') cb = cb.parent;
|
|
4304
|
+
if (!cb) return [0, 0];
|
|
4305
|
+
// the scrollport, at its start: a scroll container's padding box, or
|
|
4306
|
+
// the viewport, which the document's top is at
|
|
4307
|
+
const px = port && inside ? port.x + port.borderLeft : 0;
|
|
4308
|
+
const py = port && inside ? port.y + port.borderTop : 0;
|
|
4309
|
+
const pw =
|
|
4310
|
+
port && inside
|
|
4311
|
+
? port.width - port.borderLeft - port.borderRight
|
|
4312
|
+
: viewportWidth;
|
|
4313
|
+
const ph =
|
|
4314
|
+
port && inside
|
|
4315
|
+
? port.height - port.borderTop - port.borderBottom
|
|
4316
|
+
: viewportHeight;
|
|
4317
|
+
const dx = stuck(
|
|
4318
|
+
box.x,
|
|
4319
|
+
box.width,
|
|
4320
|
+
box.marginLeft,
|
|
4321
|
+
box.marginRight,
|
|
4322
|
+
cb.contentX,
|
|
4323
|
+
cb.contentWidth,
|
|
4324
|
+
px,
|
|
4325
|
+
pw,
|
|
4326
|
+
resolveOrNull(style.left, pw),
|
|
4327
|
+
inside ? resolveOrNull(style.right, pw) : null,
|
|
4328
|
+
// `left` wins where both hold, or `right` in a right-to-left block
|
|
4329
|
+
cb.style.direction !== 'rtl',
|
|
4330
|
+
);
|
|
4331
|
+
const dy = stuck(
|
|
4332
|
+
box.y,
|
|
4333
|
+
box.height,
|
|
4334
|
+
box.marginTop,
|
|
4335
|
+
box.marginBottom,
|
|
4336
|
+
cb.contentY,
|
|
4337
|
+
cb.contentHeight,
|
|
4338
|
+
py,
|
|
4339
|
+
ph,
|
|
4340
|
+
resolveOrNull(style.top, ph),
|
|
4341
|
+
inside ? resolveOrNull(style.bottom, ph) : null,
|
|
4342
|
+
true,
|
|
4343
|
+
);
|
|
4344
|
+
return [dx, dy];
|
|
4345
|
+
}
|
|
4346
|
+
|
|
4347
|
+
/**
|
|
4348
|
+
* How far a sticky box moves along one axis: toward the end until its
|
|
4349
|
+
* start edge is `start` inside the scrollport's, or toward the start until
|
|
4350
|
+
* its end edge is `end` inside the scrollport's, each no further than its
|
|
4351
|
+
* margin box stays in its containing block — and where both would move
|
|
4352
|
+
* it, `startWins` says which does.
|
|
4353
|
+
*/
|
|
4354
|
+
function stuck(
|
|
4355
|
+
at: number,
|
|
4356
|
+
size: number,
|
|
4357
|
+
marginStart: number,
|
|
4358
|
+
marginEnd: number,
|
|
4359
|
+
cbAt: number,
|
|
4360
|
+
cbSize: number,
|
|
4361
|
+
portAt: number,
|
|
4362
|
+
portSize: number,
|
|
4363
|
+
start: number | null,
|
|
4364
|
+
end: number | null,
|
|
4365
|
+
startWins: boolean,
|
|
4366
|
+
): number {
|
|
4367
|
+
let byStart = 0;
|
|
4368
|
+
if (start !== null && Number.isFinite(start) && at < portAt + start) {
|
|
4369
|
+
const room = cbAt + cbSize - (at + size + marginEnd);
|
|
4370
|
+
byStart = Math.max(0, Math.min(portAt + start - at, room));
|
|
4371
|
+
}
|
|
4372
|
+
let byEnd = 0;
|
|
4373
|
+
if (end !== null && Number.isFinite(end)) {
|
|
4374
|
+
const over = at + size - (portAt + portSize - end);
|
|
4375
|
+
const room = at - marginStart - cbAt;
|
|
4376
|
+
if (over > 0) byEnd = -Math.max(0, Math.min(over, room));
|
|
4377
|
+
}
|
|
4378
|
+
return startWins ? byStart || byEnd : byEnd || byStart;
|
|
4379
|
+
}
|
|
4380
|
+
|
|
4067
4381
|
/**
|
|
4068
4382
|
* Move an inline box's text where `position: relative` puts the box. Its
|
|
4069
4383
|
* text is on its block's lines, laid out apart from the text around it
|
|
@@ -4089,10 +4403,34 @@ function offsetInline(box: Box, dx: number, dy: number): void {
|
|
|
4089
4403
|
y: (was?.y ?? 0) + dy,
|
|
4090
4404
|
});
|
|
4091
4405
|
SHIFTED_LINES.add(line);
|
|
4406
|
+
markMovedOff(lines, line);
|
|
4407
|
+
}
|
|
4408
|
+
}
|
|
4409
|
+
}
|
|
4410
|
+
|
|
4411
|
+
/** Mark the line an inline-block or an image a box moved off was placed
|
|
4412
|
+
* on, where it was on one: the line stays, and the box is drawn where it
|
|
4413
|
+
* went (`MOVED_OFF_LINES`). */
|
|
4414
|
+
function movedOffLine(box: Box): void {
|
|
4415
|
+
let block = box.parent;
|
|
4416
|
+
while (block && block.kind === 'inline') block = block.parent;
|
|
4417
|
+
const lines = block?.lines;
|
|
4418
|
+
if (!lines) return;
|
|
4419
|
+
for (const line of lines) {
|
|
4420
|
+
for (const placed of line.atomics) {
|
|
4421
|
+
if (placed.box !== box) continue;
|
|
4422
|
+
markMovedOff(lines, line);
|
|
4423
|
+
return;
|
|
4092
4424
|
}
|
|
4093
4425
|
}
|
|
4094
4426
|
}
|
|
4095
4427
|
|
|
4428
|
+
function markMovedOff(lines: LineBox[], line: LineBox): void {
|
|
4429
|
+
const moved = MOVED_OFF_LINES.get(lines);
|
|
4430
|
+
if (moved) moved.add(line);
|
|
4431
|
+
else MOVED_OFF_LINES.set(lines, new Set([line]));
|
|
4432
|
+
}
|
|
4433
|
+
|
|
4096
4434
|
/** Whether `inner` is `outer` or inside it. */
|
|
4097
4435
|
function holds(outer: Box, inner: Box): boolean {
|
|
4098
4436
|
for (let at: Box | null = inner; at; at = at.parent) {
|