@react-x11/components 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/flow/node.d.ts +14 -6
- package/dist/flow/node.d.ts.map +1 -1
- package/dist/flow/node.js +82 -64
- package/dist/flow/node.js.map +1 -1
- package/dist/html/controls.d.ts +38 -10
- package/dist/html/controls.d.ts.map +1 -1
- package/dist/html/controls.js +43 -58
- package/dist/html/controls.js.map +1 -1
- package/dist/html/css/cascade.d.ts +134 -20
- package/dist/html/css/cascade.d.ts.map +1 -1
- package/dist/html/css/cascade.js +538 -128
- 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 +40 -2
- package/dist/html/css/parse.d.ts.map +1 -1
- package/dist/html/css/parse.js +391 -13
- package/dist/html/css/parse.js.map +1 -1
- package/dist/html/css/style.d.ts +52 -1
- package/dist/html/css/style.d.ts.map +1 -1
- package/dist/html/css/style.js +328 -11
- package/dist/html/css/style.js.map +1 -1
- package/dist/html/css/ua.d.ts +11 -1
- package/dist/html/css/ua.d.ts.map +1 -1
- package/dist/html/css/ua.js +73 -7
- 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 +38 -3
- package/dist/html/dom.js.map +1 -1
- package/dist/html/fonts.d.ts +25 -1
- package/dist/html/fonts.d.ts.map +1 -1
- package/dist/html/fonts.js +99 -6
- package/dist/html/fonts.js.map +1 -1
- package/dist/html/form.d.ts +200 -0
- package/dist/html/form.d.ts.map +1 -0
- package/dist/html/form.js +801 -0
- package/dist/html/form.js.map +1 -0
- package/dist/html/index.d.ts +12 -0
- package/dist/html/index.d.ts.map +1 -1
- package/dist/html/index.js +30 -264
- 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 +186 -19
- package/dist/html/layout/block.js.map +1 -1
- package/dist/html/layout/boxes.d.ts +65 -7
- package/dist/html/layout/boxes.d.ts.map +1 -1
- package/dist/html/layout/boxes.js +87 -22
- 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/flex.js +4 -1
- package/dist/html/layout/flex.js.map +1 -1
- package/dist/html/layout/floats.d.ts +4 -0
- package/dist/html/layout/floats.d.ts.map +1 -1
- package/dist/html/layout/floats.js +26 -0
- package/dist/html/layout/floats.js.map +1 -1
- package/dist/html/layout/inline.d.ts +26 -2
- package/dist/html/layout/inline.d.ts.map +1 -1
- package/dist/html/layout/inline.js +1232 -115
- package/dist/html/layout/inline.js.map +1 -1
- package/dist/html/node.d.ts +120 -15
- package/dist/html/node.d.ts.map +1 -1
- package/dist/html/node.js +993 -174
- package/dist/html/node.js.map +1 -1
- package/dist/html/paint.d.ts +75 -2
- package/dist/html/paint.d.ts.map +1 -1
- package/dist/html/paint.js +836 -200
- 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/widgets.d.ts +29 -0
- package/dist/html/widgets.d.ts.map +1 -0
- package/dist/html/widgets.js +620 -0
- package/dist/html/widgets.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/internal/window.d.ts +22 -0
- package/dist/internal/window.d.ts.map +1 -1
- package/dist/internal/window.js +35 -8
- package/dist/internal/window.js.map +1 -1
- package/dist/richtext/node.d.ts +7 -0
- package/dist/richtext/node.d.ts.map +1 -1
- package/dist/richtext/node.js.map +1 -1
- package/package.json +6 -4
- package/src/flow/node.ts +80 -64
- package/src/html/controls.ts +79 -58
- package/src/html/css/cascade.ts +610 -146
- package/src/html/css/color.ts +87 -0
- package/src/html/css/parse.ts +431 -13
- package/src/html/css/style.ts +367 -11
- package/src/html/css/ua.ts +76 -8
- package/src/html/css/values.ts +31 -3
- package/src/html/css/vars.ts +2 -2
- package/src/html/dom.ts +41 -3
- package/src/html/fonts.ts +116 -6
- package/src/html/form.ts +962 -0
- package/src/html/index.ts +49 -300
- package/src/html/layout/block.ts +231 -16
- package/src/html/layout/boxes.ts +133 -32
- package/src/html/layout/cache.ts +8 -3
- package/src/html/layout/flex.ts +4 -1
- package/src/html/layout/floats.ts +24 -0
- package/src/html/layout/inline.ts +1445 -108
- package/src/html/node.ts +1010 -166
- package/src/html/paint.ts +980 -270
- package/src/html/surfaces.ts +21 -1
- package/src/html/widgets.ts +735 -0
- package/src/index.ts +1 -0
- package/src/internal/window.ts +36 -8
- package/src/richtext/node.ts +7 -0
package/src/html/layout/block.ts
CHANGED
|
@@ -38,6 +38,7 @@ import {
|
|
|
38
38
|
BOX_RAISES,
|
|
39
39
|
Box,
|
|
40
40
|
CLAMPED,
|
|
41
|
+
CLEARED_FROM,
|
|
41
42
|
CUT_BLOCKS,
|
|
42
43
|
GRID_TRACKS,
|
|
43
44
|
FIRST_LINE,
|
|
@@ -60,6 +61,7 @@ import type {
|
|
|
60
61
|
import { FloatContext } from './floats.js';
|
|
61
62
|
import {
|
|
62
63
|
faceExtentOf,
|
|
64
|
+
joinsBefore,
|
|
63
65
|
layoutInline,
|
|
64
66
|
lineHeightMultiplier,
|
|
65
67
|
spaceBreaksOnly,
|
|
@@ -71,7 +73,15 @@ import type { TextRun } from '../../richtext/index.js';
|
|
|
71
73
|
import { layoutFlex } from './flex.js';
|
|
72
74
|
import { finishCaptions, layoutTable } from './table.js';
|
|
73
75
|
import { collapseEdges } from './collapse.js';
|
|
74
|
-
import {
|
|
76
|
+
import {
|
|
77
|
+
clipsFor,
|
|
78
|
+
computePaintBounds,
|
|
79
|
+
FIXED_BOXES,
|
|
80
|
+
fixedToViewport,
|
|
81
|
+
hoistNegative,
|
|
82
|
+
OUT_OF_FLOW_REACH,
|
|
83
|
+
stackLayers,
|
|
84
|
+
} from '../paint.js';
|
|
75
85
|
|
|
76
86
|
export interface LayoutContext {
|
|
77
87
|
fonts: FontsLike | null;
|
|
@@ -238,19 +248,35 @@ export function layoutDocument(
|
|
|
238
248
|
// against where it *was* — which is the whole of what makes it relative —
|
|
239
249
|
// and the ink bounds are computed after that, so culling sees where boxes
|
|
240
250
|
// ended up rather than where they were laid out.
|
|
241
|
-
if (tree.relative) applyRelativeOffsets(root);
|
|
251
|
+
if (tree.relative) applyRelativeOffsets(root, viewportWidth, viewportHeight);
|
|
242
252
|
const reach = computePaintBounds(root, tree.movedInline);
|
|
243
253
|
if (tree.negative) hoistNegative(root);
|
|
244
254
|
stackLayers(root);
|
|
245
255
|
|
|
246
256
|
// The document is as tall as what overflows the root, not the root: an
|
|
247
257
|
// `html, body { height: 100% }` a window tall holds a message longer than
|
|
248
|
-
// the window, and the element sizes to all of it.
|
|
258
|
+
// the window, and the element sizes to all of it. Not what a box that
|
|
259
|
+
// clips its overflow holds: that is its own to scroll, and the root's
|
|
260
|
+
// scrollable overflow takes in a positioned box only where no box on the
|
|
261
|
+
// way to it clips it (CSS Overflow 3, 2.2): an absolute box 900px down
|
|
262
|
+
// an `overflow: hidden` card made a page a browser shows 400px tall run
|
|
263
|
+
// on to 980, blank
|
|
249
264
|
let bottom = Math.max(root.height, reach);
|
|
265
|
+
let fixed: Box[] | null = null;
|
|
250
266
|
for (const { box } of ctx.positioned) {
|
|
251
|
-
if (box.style.position
|
|
252
|
-
|
|
267
|
+
if (box.style.position === 'fixed') {
|
|
268
|
+
if (fixedToViewport(box)) (fixed ??= []).push(box);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (clipsFor(box, root).length) continue;
|
|
272
|
+
// and what it holds, where it does not clip it: a page set in an
|
|
273
|
+
// absolute wrapper 497px tall runs on below it, and the document with
|
|
274
|
+
// it — design 094 was a window tall and no taller, its text past the
|
|
275
|
+
// end of the scroll
|
|
276
|
+
bottom = Math.max(bottom, OUT_OF_FLOW_REACH.get(box) ?? box.y + box.height);
|
|
253
277
|
}
|
|
278
|
+
if (fixed) FIXED_BOXES.set(tree, fixed);
|
|
279
|
+
else FIXED_BOXES.delete(tree);
|
|
254
280
|
return {
|
|
255
281
|
width: viewportWidth,
|
|
256
282
|
height: bottom,
|
|
@@ -768,8 +794,12 @@ function layoutChildren(
|
|
|
768
794
|
// this box's first formatted line is its first child's in flow (CSS 2.1
|
|
769
795
|
// 5.12.1), which its `::first-line` is handed to
|
|
770
796
|
let firstLine = ctx.firstLine ? firstLineOf(box) : null;
|
|
771
|
-
// and the room a list item's marker takes on it (`MARKER_ROOM`)
|
|
772
|
-
|
|
797
|
+
// and the room a list item's marker takes on it (`MARKER_ROOM`): an
|
|
798
|
+
// inside marker's is handed down to the line it is on, and an outside
|
|
799
|
+
// one's is room above the baseline of the first child that has a line,
|
|
800
|
+
// which goes lower where the marker reaches higher (`MARKER_ASCENT`)
|
|
801
|
+
let pushed = MARKER_ASCENT.get(box) ?? null;
|
|
802
|
+
let marked = pushed === null ? (MARKER_ROOM.get(box) ?? null) : null;
|
|
773
803
|
const clamp = ctx.clamp ?? null;
|
|
774
804
|
|
|
775
805
|
for (const child of box.children) {
|
|
@@ -863,6 +893,7 @@ function layoutChildren(
|
|
|
863
893
|
}
|
|
864
894
|
|
|
865
895
|
const counted = clamp?.fit?.lines ?? 0;
|
|
896
|
+
const floatMark = floats.count;
|
|
866
897
|
if (
|
|
867
898
|
!floats.isEmpty &&
|
|
868
899
|
(child.kind === 'replaced' || establishesBFC(child))
|
|
@@ -871,6 +902,17 @@ function layoutChildren(
|
|
|
871
902
|
} else {
|
|
872
903
|
layoutBlockLevel(child, ctx, floats, contentLeft, childY, contentWidth);
|
|
873
904
|
}
|
|
905
|
+
if (pushed !== null) {
|
|
906
|
+
const line = firstLineIn(child);
|
|
907
|
+
if (line) {
|
|
908
|
+
const push = pushed - (line.y + line.baseline - child.y);
|
|
909
|
+
if (push > 0) {
|
|
910
|
+
translate(child, 0, push);
|
|
911
|
+
floats.moveSince(floatMark, push);
|
|
912
|
+
}
|
|
913
|
+
pushed = null;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
874
916
|
// a block with none of the flow's lines in it — an image, a box of a
|
|
875
917
|
// formatting context of its own — puts the clamp point of an `auto`
|
|
876
918
|
// clamp before it where it ends below the room
|
|
@@ -886,6 +928,8 @@ function layoutChildren(
|
|
|
886
928
|
}
|
|
887
929
|
first = false;
|
|
888
930
|
const moved = childY !== at;
|
|
931
|
+
if (moved) CLEARED_FROM.set(child, at);
|
|
932
|
+
else CLEARED_FROM.delete(child);
|
|
889
933
|
if (!moved && collapsesThrough(child)) {
|
|
890
934
|
// nothing in it parts its margins: they and the ones either side of
|
|
891
935
|
// it are one (CSS 2.1 8.3.1), still hanging for what comes next —
|
|
@@ -1030,11 +1074,20 @@ function layoutBesideFloats(
|
|
|
1030
1074
|
band = over;
|
|
1031
1075
|
continue;
|
|
1032
1076
|
}
|
|
1077
|
+
// Where no float narrows the room on either side, the box is where
|
|
1078
|
+
// it would be were there none, and one too wide for its containing
|
|
1079
|
+
// block overflows it here: below the floats it would be the same box
|
|
1080
|
+
// in the same room. Blink tests a fit only against a side a float is
|
|
1081
|
+
// on (`can_expand_outside_opportunity`, BlockLayoutAlgorithm::
|
|
1082
|
+
// LayoutNewFormattingContext). Design 209's 247px heading, which
|
|
1083
|
+
// clips its overflow, in a 240px column beside a float that ends
|
|
1084
|
+
// left of the column, went under the float, 555px down.
|
|
1085
|
+
if (lo === -Infinity && hi === Infinity) fits = true;
|
|
1033
1086
|
// What is too wide overflows at the end of the line, as far as a
|
|
1034
1087
|
// negative margin there takes it, but no further into a float or
|
|
1035
1088
|
// past the room; at the start it would run into the floats, and
|
|
1036
1089
|
// waits below them (the suite's floats-wrap-bfc-with-margin tests)
|
|
1037
|
-
if (inRoom) {
|
|
1090
|
+
else if (inRoom) {
|
|
1038
1091
|
fits = box.marginLeft + box.width + box.marginRight <= room + 0.5;
|
|
1039
1092
|
} else if (rtl) {
|
|
1040
1093
|
const limit = Math.max(contentLeft + Math.min(0, box.marginLeft), lo);
|
|
@@ -1744,6 +1797,8 @@ function layoutBox(
|
|
|
1744
1797
|
const room = markerRoom(box, box.marker, ctx.fonts);
|
|
1745
1798
|
if (room) MARKER_ROOM.set(box, room);
|
|
1746
1799
|
else MARKER_ROOM.delete(box);
|
|
1800
|
+
if (box.style.listStylePosition === 'inside') MARKER_ASCENT.delete(box);
|
|
1801
|
+
else MARKER_ASCENT.set(box, markerAscent(box, box.marker, ctx.fonts));
|
|
1747
1802
|
}
|
|
1748
1803
|
const flow = layoutChildren(box, ctx, floats, box.contentY, contentWidth);
|
|
1749
1804
|
// A list item with a marker and no line holds one, the marker's, a line
|
|
@@ -1830,6 +1885,28 @@ function markerRoom(
|
|
|
1830
1885
|
: null;
|
|
1831
1886
|
}
|
|
1832
1887
|
|
|
1888
|
+
/**
|
|
1889
|
+
* How far an outside marker reaches above the baseline it sits on. The
|
|
1890
|
+
* marker is a line of its own face, the item's or a `::marker` rule's, so a
|
|
1891
|
+
* bullet or a number reaches its face's ascent on that line, and an image,
|
|
1892
|
+
* set on the line bottom on the baseline, the higher of that and its own
|
|
1893
|
+
* height. Where the item's first line is in a block inside it — a link set
|
|
1894
|
+
* `display: block`, a paragraph — and the block's first baseline is nearer
|
|
1895
|
+
* its top than that, the block goes lower by the difference, and its line
|
|
1896
|
+
* stays the height it was: CSS 2.1 (12.5.1) leaves this to the user agent,
|
|
1897
|
+
* and Blink aligns the marker's baseline with the block's and pushes the
|
|
1898
|
+
* block down (`UnpositionedListMarker::AddToBox`). Its first line grew
|
|
1899
|
+
* instead, and design 196's links, under 18px bullets, were 21px tall where
|
|
1900
|
+
* Chrome has them 14px, 7px lower. On the item's own line the two come to
|
|
1901
|
+
* the same: the line grows by what the push would have moved it.
|
|
1902
|
+
*/
|
|
1903
|
+
const MARKER_ASCENT = new WeakMap<Box, number>();
|
|
1904
|
+
|
|
1905
|
+
function markerAscent(box: Box, marker: Marker, fonts: FontsLike): number {
|
|
1906
|
+
const ascent = strutOf(fonts, marker.style ?? box.style).ascent;
|
|
1907
|
+
return marker.image ? Math.max(marker.image.height, ascent) : ascent;
|
|
1908
|
+
}
|
|
1909
|
+
|
|
1833
1910
|
/** The first line box anywhere under a box, in layout order. */
|
|
1834
1911
|
function firstLineIn(box: Box): LineBox | null {
|
|
1835
1912
|
if (box.lines?.length) return box.lines[0];
|
|
@@ -2827,10 +2904,19 @@ function wordBound(box: Box, fonts: FontsLike, measured: boolean): number {
|
|
|
2827
2904
|
let last: object | null = null;
|
|
2828
2905
|
for (const line of box.lines) {
|
|
2829
2906
|
if (unbroken) widest = Math.max(widest, line.width);
|
|
2907
|
+
// a word across inline boxes' edges is in two fragments of text, and
|
|
2908
|
+
// as wide as its parts in both and the edges between: bound by the
|
|
2909
|
+
// widest word of each, a float in a narrow room took a letter of a
|
|
2910
|
+
// word of letters in bordered spans for its least width, and was cut
|
|
2911
|
+
// to the room (`joinsBefore`)
|
|
2912
|
+
let chain = 0;
|
|
2830
2913
|
for (const text of line.texts) {
|
|
2831
|
-
|
|
2914
|
+
const joins = joinsBefore(text);
|
|
2915
|
+
if (joins === undefined && text.layout === last) continue;
|
|
2832
2916
|
last = text.layout;
|
|
2833
|
-
|
|
2917
|
+
const own = widestWord(fonts, text.layout, measured);
|
|
2918
|
+
chain = joins === undefined ? own : chain + joins + own;
|
|
2919
|
+
widest = Math.max(widest, chain);
|
|
2834
2920
|
}
|
|
2835
2921
|
for (const placed of line.atomics) {
|
|
2836
2922
|
const b = placed.box;
|
|
@@ -4103,9 +4189,15 @@ export function establishesBFC(box: Box): boolean {
|
|
|
4103
4189
|
|
|
4104
4190
|
/** What a box's `position: relative` offset moves it by, applied after
|
|
4105
4191
|
* layout so it does not affect anything else's position — which is the
|
|
4106
|
-
* whole of what makes it *relative* — and its translation with it
|
|
4107
|
-
|
|
4108
|
-
|
|
4192
|
+
* whole of what makes it *relative* — and its translation with it, and
|
|
4193
|
+
* where `position: sticky` puts one at rest (`stickyOffset`). */
|
|
4194
|
+
export function applyRelativeOffsets(
|
|
4195
|
+
box: Box,
|
|
4196
|
+
viewportWidth: number,
|
|
4197
|
+
viewportHeight: number,
|
|
4198
|
+
): void {
|
|
4199
|
+
for (const child of box.children)
|
|
4200
|
+
applyRelativeOffsets(child, viewportWidth, viewportHeight);
|
|
4109
4201
|
const style = box.style;
|
|
4110
4202
|
if (transformed(style) && box.kind !== 'inline') {
|
|
4111
4203
|
// a transform moves the box it is on, and an inline box is none
|
|
@@ -4116,7 +4208,10 @@ export function applyRelativeOffsets(box: Box): void {
|
|
|
4116
4208
|
}
|
|
4117
4209
|
}
|
|
4118
4210
|
if (style.position !== 'relative' && style.position !== 'sticky') return;
|
|
4119
|
-
const [dx, dy] =
|
|
4211
|
+
const [dx, dy] =
|
|
4212
|
+
style.position === 'sticky'
|
|
4213
|
+
? stickyOffset(box, viewportWidth, viewportHeight)
|
|
4214
|
+
: relativeOffset(box);
|
|
4120
4215
|
if (!(dx || dy)) return;
|
|
4121
4216
|
translate(box, dx, dy);
|
|
4122
4217
|
if (box.kind !== 'inline') {
|
|
@@ -4137,10 +4232,10 @@ export function transformed(style: ComputedStyle): boolean {
|
|
|
4137
4232
|
|
|
4138
4233
|
/** How far `translate` and a `transform` move a box: a percentage is of
|
|
4139
4234
|
* its own border box (CSS Transforms 1, 7). */
|
|
4140
|
-
function translationOf(box: Box): [number, number] {
|
|
4235
|
+
function translationOf(box: Box, style = box.style): [number, number] {
|
|
4141
4236
|
let dx = 0;
|
|
4142
4237
|
let dy = 0;
|
|
4143
|
-
for (const moved of [
|
|
4238
|
+
for (const moved of [style.translate, style.transformTranslate]) {
|
|
4144
4239
|
if (!moved) continue;
|
|
4145
4240
|
dx += resolve(moved[0], box.width, 0);
|
|
4146
4241
|
dy += resolve(moved[1], box.height, 0);
|
|
@@ -4148,6 +4243,21 @@ function translationOf(box: Box): [number, number] {
|
|
|
4148
4243
|
return [dx, dy];
|
|
4149
4244
|
}
|
|
4150
4245
|
|
|
4246
|
+
/**
|
|
4247
|
+
* Move a laid-out box, and everything in it, by how far its translation
|
|
4248
|
+
* changed when its style went from `was` to the one it has — what
|
|
4249
|
+
* `applyRelativeOffsets` moves it by now, less what it moved it by then.
|
|
4250
|
+
* For a style changed in place, where nothing else moves (a `transform` is
|
|
4251
|
+
* what makes that true). Not for an inline box, whose text is what moves.
|
|
4252
|
+
*/
|
|
4253
|
+
export function retranslate(box: Box, was: ComputedStyle): void {
|
|
4254
|
+
const [x0, y0] = translationOf(box, was);
|
|
4255
|
+
const [x1, y1] = translationOf(box);
|
|
4256
|
+
if (x1 === x0 && y1 === y0) return;
|
|
4257
|
+
translate(box, x1 - x0, y1 - y0);
|
|
4258
|
+
movedOffLine(box);
|
|
4259
|
+
}
|
|
4260
|
+
|
|
4151
4261
|
/** How far `position: relative` moves a box. */
|
|
4152
4262
|
function relativeOffset(box: Box): [number, number] {
|
|
4153
4263
|
const style = box.style;
|
|
@@ -4166,6 +4276,111 @@ function relativeOffset(box: Box): [number, number] {
|
|
|
4166
4276
|
return [dx, dy];
|
|
4167
4277
|
}
|
|
4168
4278
|
|
|
4279
|
+
/**
|
|
4280
|
+
* Where `position: sticky` puts a box at rest (CSS Positioned Layout 3,
|
|
4281
|
+
* 3.4): moved only as far as keeps its border box inside the sticky view
|
|
4282
|
+
* rectangle — its nearest scroll container's scrollport, scrolled to its
|
|
4283
|
+
* start, less its insets — and no further than keeps its margin box in its
|
|
4284
|
+
* containing block, `top` winning over `bottom` and the start side over the
|
|
4285
|
+
* end. Nothing here scrolls a box the document holds, so for one of those
|
|
4286
|
+
* this is where the box stays. The viewport does scroll, and the document
|
|
4287
|
+
* with it, and a sticky box does not follow: against the viewport only its
|
|
4288
|
+
* `top` and its start side are kept, which at rest put a box as a browser
|
|
4289
|
+
* does, while a `bottom: 0` would pin a footer to the middle of the page
|
|
4290
|
+
* once it was scrolled. Taken as `relative`, a box stood its `top` below
|
|
4291
|
+
* where a browser starts it: Wikipedia's contents, 24px low.
|
|
4292
|
+
*/
|
|
4293
|
+
function stickyOffset(
|
|
4294
|
+
box: Box,
|
|
4295
|
+
viewportWidth: number,
|
|
4296
|
+
viewportHeight: number,
|
|
4297
|
+
): [number, number] {
|
|
4298
|
+
const style = box.style;
|
|
4299
|
+
// the scroll container a sticky box keeps to: the nearest box that
|
|
4300
|
+
// scrolls, or the viewport, whose are the root's and the body's overflow
|
|
4301
|
+
let port = box.parent;
|
|
4302
|
+
while (port && !scrolls(port.style)) port = port.parent;
|
|
4303
|
+
const tag = port?.el?.name;
|
|
4304
|
+
const inside = !!port && tag !== 'html' && tag !== 'body';
|
|
4305
|
+
let cb = box.parent;
|
|
4306
|
+
while (cb && cb.kind === 'inline') cb = cb.parent;
|
|
4307
|
+
if (!cb) return [0, 0];
|
|
4308
|
+
// the scrollport, at its start: a scroll container's padding box, or
|
|
4309
|
+
// the viewport, which the document's top is at
|
|
4310
|
+
const px = port && inside ? port.x + port.borderLeft : 0;
|
|
4311
|
+
const py = port && inside ? port.y + port.borderTop : 0;
|
|
4312
|
+
const pw =
|
|
4313
|
+
port && inside
|
|
4314
|
+
? port.width - port.borderLeft - port.borderRight
|
|
4315
|
+
: viewportWidth;
|
|
4316
|
+
const ph =
|
|
4317
|
+
port && inside
|
|
4318
|
+
? port.height - port.borderTop - port.borderBottom
|
|
4319
|
+
: viewportHeight;
|
|
4320
|
+
const dx = stuck(
|
|
4321
|
+
box.x,
|
|
4322
|
+
box.width,
|
|
4323
|
+
box.marginLeft,
|
|
4324
|
+
box.marginRight,
|
|
4325
|
+
cb.contentX,
|
|
4326
|
+
cb.contentWidth,
|
|
4327
|
+
px,
|
|
4328
|
+
pw,
|
|
4329
|
+
resolveOrNull(style.left, pw),
|
|
4330
|
+
inside ? resolveOrNull(style.right, pw) : null,
|
|
4331
|
+
// `left` wins where both hold, or `right` in a right-to-left block
|
|
4332
|
+
cb.style.direction !== 'rtl',
|
|
4333
|
+
);
|
|
4334
|
+
const dy = stuck(
|
|
4335
|
+
box.y,
|
|
4336
|
+
box.height,
|
|
4337
|
+
box.marginTop,
|
|
4338
|
+
box.marginBottom,
|
|
4339
|
+
cb.contentY,
|
|
4340
|
+
cb.contentHeight,
|
|
4341
|
+
py,
|
|
4342
|
+
ph,
|
|
4343
|
+
resolveOrNull(style.top, ph),
|
|
4344
|
+
inside ? resolveOrNull(style.bottom, ph) : null,
|
|
4345
|
+
true,
|
|
4346
|
+
);
|
|
4347
|
+
return [dx, dy];
|
|
4348
|
+
}
|
|
4349
|
+
|
|
4350
|
+
/**
|
|
4351
|
+
* How far a sticky box moves along one axis: toward the end until its
|
|
4352
|
+
* start edge is `start` inside the scrollport's, or toward the start until
|
|
4353
|
+
* its end edge is `end` inside the scrollport's, each no further than its
|
|
4354
|
+
* margin box stays in its containing block — and where both would move
|
|
4355
|
+
* it, `startWins` says which does.
|
|
4356
|
+
*/
|
|
4357
|
+
function stuck(
|
|
4358
|
+
at: number,
|
|
4359
|
+
size: number,
|
|
4360
|
+
marginStart: number,
|
|
4361
|
+
marginEnd: number,
|
|
4362
|
+
cbAt: number,
|
|
4363
|
+
cbSize: number,
|
|
4364
|
+
portAt: number,
|
|
4365
|
+
portSize: number,
|
|
4366
|
+
start: number | null,
|
|
4367
|
+
end: number | null,
|
|
4368
|
+
startWins: boolean,
|
|
4369
|
+
): number {
|
|
4370
|
+
let byStart = 0;
|
|
4371
|
+
if (start !== null && Number.isFinite(start) && at < portAt + start) {
|
|
4372
|
+
const room = cbAt + cbSize - (at + size + marginEnd);
|
|
4373
|
+
byStart = Math.max(0, Math.min(portAt + start - at, room));
|
|
4374
|
+
}
|
|
4375
|
+
let byEnd = 0;
|
|
4376
|
+
if (end !== null && Number.isFinite(end)) {
|
|
4377
|
+
const over = at + size - (portAt + portSize - end);
|
|
4378
|
+
const room = at - marginStart - cbAt;
|
|
4379
|
+
if (over > 0) byEnd = -Math.max(0, Math.min(over, room));
|
|
4380
|
+
}
|
|
4381
|
+
return startWins ? byStart || byEnd : byEnd || byStart;
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4169
4384
|
/**
|
|
4170
4385
|
* Move an inline box's text where `position: relative` puts the box. Its
|
|
4171
4386
|
* text is on its block's lines, laid out apart from the text around it
|