@jsenv/navi 0.29.432 → 0.29.434
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/dev/jsenv_navi.js +198 -76
- package/dist/dev/jsenv_navi.js.map +8 -8
- package/dist/jsenv_navi.js +198 -76
- package/dist/jsenv_navi.js.map +8 -8
- package/docs/AI_INSTRUCTIONS.md +4 -3
- package/docs/route_transitions.md +4 -0
- package/docs/scroll.md +38 -5
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -11086,9 +11086,16 @@ const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
|
11086
11086
|
* (recallScrollerPosition). What a push means for them is what it means for
|
|
11087
11087
|
* the document — an arrival opens at the top (see startAtTop): the page
|
|
11088
11088
|
* arrived at has its named positions dropped before its lists render, so a
|
|
11089
|
-
* list recalls only on the way back. A scroller that
|
|
11090
|
-
* stays
|
|
11091
|
-
*
|
|
11089
|
+
* list recalls only on the way back. A scroller that outlives a navigation —
|
|
11090
|
+
* its page stays under a new address, or it lives outside the page that
|
|
11091
|
+
* changed — is shown under the new address too, and is carried to it (see
|
|
11092
|
+
* carryScrollersOnceRendered). A scroller that goes while its page stays — a
|
|
11093
|
+
* popup closing over the same address — has nothing to come back to, and says
|
|
11094
|
+
* so as it leaves (forgetScrollerUnlessPageLeft).
|
|
11095
|
+
*
|
|
11096
|
+
* Every position is kept under the address the routing shows, which can be
|
|
11097
|
+
* ahead of the browser's: a replace waiting for its state to settle has moved
|
|
11098
|
+
* the page and not yet `window.location` (see readShownUrl).
|
|
11092
11099
|
*
|
|
11093
11100
|
* What is NOT covered, and cannot be from here: a page whose height depends on
|
|
11094
11101
|
* something still loading. Its content is not there at the moment it is put
|
|
@@ -11109,12 +11116,28 @@ const [publishRouteRender, observeRouteRender] = createPubSub();
|
|
|
11109
11116
|
const STORAGE_KEY = "navi_scroll_positions";
|
|
11110
11117
|
|
|
11111
11118
|
const positionByUrl = new Map();
|
|
11119
|
+
// Where the document stands, as its scroll events last said, whoever it
|
|
11120
|
+
// belongs to: what a url shown without the document moving is given (see
|
|
11121
|
+
// stayInPlace). Null until the restoration is installed — nothing is recorded
|
|
11122
|
+
// before.
|
|
11123
|
+
let documentOffset = null;
|
|
11112
11124
|
// url -> (scroller name -> position). The position is whatever the scroller
|
|
11113
11125
|
// handed in: what it can put itself back on, in its own terms.
|
|
11114
11126
|
const scrollerPositionsByUrl = new Map();
|
|
11115
|
-
//
|
|
11116
|
-
// a page that stays from one
|
|
11117
|
-
|
|
11127
|
+
// What each named scroller still mounted last said: its position and the url
|
|
11128
|
+
// it said it under. What tells a scroller leaving a page that stays from one
|
|
11129
|
+
// leaving with its page, and what one outliving a navigation is carried with.
|
|
11130
|
+
const saidByScrollerName = new Map();
|
|
11131
|
+
|
|
11132
|
+
// The address the page is shown under, as the routing knows it (see
|
|
11133
|
+
// installScrollRestoration): `window.location` lags behind a replace waiting
|
|
11134
|
+
// for its state to settle (replaceAddressWhenSettled in either integration),
|
|
11135
|
+
// and a scroll keyed by it meanwhile is written for the address being left.
|
|
11136
|
+
// Resolved, as an address handed to navTo can be kept as it was given.
|
|
11137
|
+
let readRoutingAddress = () => window.location.href;
|
|
11138
|
+
const readShownUrl = () => {
|
|
11139
|
+
return new URL(readRoutingAddress(), window.location.href).href;
|
|
11140
|
+
};
|
|
11118
11141
|
|
|
11119
11142
|
// Read once, the first time anyone needs the positions: the document's
|
|
11120
11143
|
// restoration is installed by the routing, and a list remembering itself may
|
|
@@ -11215,40 +11238,38 @@ const holdDocumentScroll = () => {
|
|
|
11215
11238
|
};
|
|
11216
11239
|
|
|
11217
11240
|
let installed = false;
|
|
11218
|
-
const installScrollRestoration = () => {
|
|
11241
|
+
const installScrollRestoration = ({ readAddress }) => {
|
|
11219
11242
|
if (installed) {
|
|
11220
11243
|
return;
|
|
11221
11244
|
}
|
|
11222
11245
|
installed = true;
|
|
11246
|
+
readRoutingAddress = readAddress;
|
|
11223
11247
|
if (!("scrollRestoration" in window.history)) {
|
|
11224
11248
|
return;
|
|
11225
11249
|
}
|
|
11226
11250
|
window.history.scrollRestoration = "manual";
|
|
11227
11251
|
loadStore();
|
|
11252
|
+
documentOffset = { x: window.scrollX, y: window.scrollY };
|
|
11228
11253
|
// Read as it happens rather than when leaving: a traverse changes the url
|
|
11229
11254
|
// before anything here is told, so a position read then would be read for
|
|
11230
11255
|
// the wrong page.
|
|
11231
11256
|
window.addEventListener(
|
|
11232
11257
|
"scroll",
|
|
11233
11258
|
() => {
|
|
11259
|
+
documentOffset = { x: window.scrollX, y: window.scrollY };
|
|
11234
11260
|
if (suspendCount) {
|
|
11235
11261
|
return;
|
|
11236
11262
|
}
|
|
11237
|
-
positionByUrl.set(
|
|
11238
|
-
x: window.scrollX,
|
|
11239
|
-
y: window.scrollY,
|
|
11240
|
-
});
|
|
11263
|
+
positionByUrl.set(readShownUrl(), documentOffset);
|
|
11241
11264
|
},
|
|
11242
11265
|
{ passive: true },
|
|
11243
11266
|
);
|
|
11244
|
-
const
|
|
11267
|
+
const urlOnLoad = readShownUrl();
|
|
11268
|
+
const positionOnLoad = positionByUrl.get(urlOnLoad);
|
|
11245
11269
|
if (!positionOnLoad) {
|
|
11246
|
-
//
|
|
11247
|
-
//
|
|
11248
|
-
positionByUrl.set(
|
|
11249
|
-
x: window.scrollX,
|
|
11250
|
-
y: window.scrollY,
|
|
11251
|
-
});
|
|
11270
|
+
// The first page, where the browser landed it: no scroll event says so
|
|
11271
|
+
// (see stayInPlace).
|
|
11272
|
+
positionByUrl.set(urlOnLoad, documentOffset);
|
|
11252
11273
|
return;
|
|
11253
11274
|
}
|
|
11254
11275
|
// What a reload asks for, now that the browser has been told not to do it.
|
|
@@ -11301,7 +11322,9 @@ const restoreScrollPosition = (url) => {
|
|
|
11301
11322
|
// clamp is a scroll recorded under the url already returned to.
|
|
11302
11323
|
const restoreScrollPositionOnReturn = (url) => {
|
|
11303
11324
|
const arrival = ++arrivalCount;
|
|
11304
|
-
const
|
|
11325
|
+
const href = new URL(url, window.location.href).href;
|
|
11326
|
+
carryScrollersOnceRendered(href);
|
|
11327
|
+
const position = positionByUrl.get(href);
|
|
11305
11328
|
if (!position) {
|
|
11306
11329
|
return;
|
|
11307
11330
|
}
|
|
@@ -11339,14 +11362,17 @@ const restoreScrollPositionOnReturn = (url) => {
|
|
|
11339
11362
|
// app that scrolls an element of its own scrolls it itself.
|
|
11340
11363
|
const startAtTop = (url, { from } = {}) => {
|
|
11341
11364
|
if (!isArrival(url, { from })) {
|
|
11365
|
+
stayInPlace(url);
|
|
11342
11366
|
return;
|
|
11343
11367
|
}
|
|
11344
11368
|
arrivalCount++;
|
|
11369
|
+
const href = new URL(url, window.location.href).href;
|
|
11370
|
+
carryScrollersOnceRendered(href);
|
|
11345
11371
|
// Written down as well as done: a document already at its top fires no
|
|
11346
11372
|
// scroll event, and a page left without ever being scrolled would have no
|
|
11347
11373
|
// position to come back to — the return would keep the offset of the page
|
|
11348
11374
|
// being left.
|
|
11349
|
-
positionByUrl.set(
|
|
11375
|
+
positionByUrl.set(href, { x: 0, y: 0 });
|
|
11350
11376
|
window.scrollTo({ top: 0, left: 0, behavior: "instant" });
|
|
11351
11377
|
};
|
|
11352
11378
|
const isArrival = (url, { from }) => {
|
|
@@ -11360,6 +11386,53 @@ const isArrival = (url, { from }) => {
|
|
|
11360
11386
|
return true;
|
|
11361
11387
|
};
|
|
11362
11388
|
|
|
11389
|
+
// A url the page is shown under without changing: a replace, a push that
|
|
11390
|
+
// keeps the page (see startAtTop). Nothing scrolls, so nothing says where the
|
|
11391
|
+
// document stands under it — written down here, or a return to it would find
|
|
11392
|
+
// nothing and keep the offset of the page being left.
|
|
11393
|
+
//
|
|
11394
|
+
// The offset as its scroll events last told it, not read again: a read here
|
|
11395
|
+
// would lay the page out in the middle of a routing, once per address a
|
|
11396
|
+
// settling param writes. Deaf with the recording: a row of tabs replaces the
|
|
11397
|
+
// url as it travels, and the offset belongs to nobody until the row says where
|
|
11398
|
+
// its tab lands (see arriveAtScrollPosition).
|
|
11399
|
+
const stayInPlace = (url) => {
|
|
11400
|
+
const href = new URL(url, window.location.href).href;
|
|
11401
|
+
carryScrollersOnceRendered(href);
|
|
11402
|
+
if (documentOffset !== null && !suspendCount) {
|
|
11403
|
+
positionByUrl.set(href, documentOffset);
|
|
11404
|
+
}
|
|
11405
|
+
};
|
|
11406
|
+
|
|
11407
|
+
// A scroller that outlives a navigation — its page stays under the new url, or
|
|
11408
|
+
// it lives outside the page that changed (a sidebar list kept by every page) —
|
|
11409
|
+
// is shown under that url as well, and says nothing there unless it moves: a
|
|
11410
|
+
// return to it would find no position and open the list at its top. So it is
|
|
11411
|
+
// carried, with the position it last said.
|
|
11412
|
+
//
|
|
11413
|
+
// Told apart once the navigation has rendered: until then a list about to be
|
|
11414
|
+
// swapped out is as mounted as one that stays, and carried, its position is
|
|
11415
|
+
// what a list of the same name arriving would read. By then one that left has
|
|
11416
|
+
// said so (forgetScrollerUnlessPageLeft), and one that arrived has read its
|
|
11417
|
+
// own and speaks under this url. One still mounted on its way out (a page kept
|
|
11418
|
+
// hidden while its route transition plays) is carried, and forgets the copy as
|
|
11419
|
+
// it unmounts under this url.
|
|
11420
|
+
const carryScrollersOnceRendered = (url) => {
|
|
11421
|
+
whenPageRendered(() => {
|
|
11422
|
+
if (readShownUrl() !== url) {
|
|
11423
|
+
// Another navigation came first: what is mounted is shown under its url,
|
|
11424
|
+
// and its own carry says so.
|
|
11425
|
+
return;
|
|
11426
|
+
}
|
|
11427
|
+
for (const [name, said] of saidByScrollerName) {
|
|
11428
|
+
if (said.url === url) {
|
|
11429
|
+
continue;
|
|
11430
|
+
}
|
|
11431
|
+
writeScrollerPosition(url, name, said.position);
|
|
11432
|
+
}
|
|
11433
|
+
});
|
|
11434
|
+
};
|
|
11435
|
+
|
|
11363
11436
|
// The same arrival, for the page's own scrollers. The document is scrolled to
|
|
11364
11437
|
// its top once the page is there; a list opens where it decides to in its
|
|
11365
11438
|
// first render, so what it must not find is dropped before the routing
|
|
@@ -11400,19 +11473,21 @@ const scrollTo = ({ x, y }) => {
|
|
|
11400
11473
|
|
|
11401
11474
|
const rememberScrollerPosition = (name, position) => {
|
|
11402
11475
|
loadStore();
|
|
11403
|
-
|
|
11476
|
+
writeScrollerPosition(readShownUrl(), name, position);
|
|
11477
|
+
};
|
|
11478
|
+
const writeScrollerPosition = (url, name, position) => {
|
|
11404
11479
|
let positionByName = scrollerPositionsByUrl.get(url);
|
|
11405
11480
|
if (!positionByName) {
|
|
11406
11481
|
positionByName = new Map();
|
|
11407
11482
|
scrollerPositionsByUrl.set(url, positionByName);
|
|
11408
11483
|
}
|
|
11409
11484
|
positionByName.set(name, position);
|
|
11410
|
-
|
|
11485
|
+
saidByScrollerName.set(name, { url, position });
|
|
11411
11486
|
};
|
|
11412
11487
|
|
|
11413
11488
|
const recallScrollerPosition = (name) => {
|
|
11414
11489
|
loadStore();
|
|
11415
|
-
const positionByName = scrollerPositionsByUrl.get(
|
|
11490
|
+
const positionByName = scrollerPositionsByUrl.get(readShownUrl());
|
|
11416
11491
|
if (!positionByName) {
|
|
11417
11492
|
return undefined;
|
|
11418
11493
|
}
|
|
@@ -11422,17 +11497,18 @@ const recallScrollerPosition = (name) => {
|
|
|
11422
11497
|
// Said by a scroller as it unmounts. Its page is being left when the url is
|
|
11423
11498
|
// already another one — the history is written before the page it names is
|
|
11424
11499
|
// taken down — and then its position is kept for the way back. The url still
|
|
11425
|
-
// being the one it
|
|
11426
|
-
// goes (a popup closing, a section folding): there is no coming back to
|
|
11427
|
-
// place that was not left, and a position kept would greet the next mount
|
|
11500
|
+
// being the one it was last kept under means the page stays and the scroller
|
|
11501
|
+
// alone goes (a popup closing, a section folding): there is no coming back to
|
|
11502
|
+
// a place that was not left, and a position kept would greet the next mount
|
|
11428
11503
|
// under this address as a return.
|
|
11429
11504
|
const forgetScrollerUnlessPageLeft = (name) => {
|
|
11430
|
-
const
|
|
11431
|
-
if (
|
|
11505
|
+
const said = saidByScrollerName.get(name);
|
|
11506
|
+
if (said === undefined) {
|
|
11432
11507
|
return;
|
|
11433
11508
|
}
|
|
11434
|
-
|
|
11435
|
-
|
|
11509
|
+
saidByScrollerName.delete(name);
|
|
11510
|
+
const { url } = said;
|
|
11511
|
+
if (url !== readShownUrl()) {
|
|
11436
11512
|
return;
|
|
11437
11513
|
}
|
|
11438
11514
|
const positionByName = scrollerPositionsByUrl.get(url);
|
|
@@ -11835,14 +11911,18 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11835
11911
|
// also waits for the page it returns to: an offset is clamped to whatever
|
|
11836
11912
|
// page the document holds when it is written.
|
|
11837
11913
|
//
|
|
11838
|
-
// A replace
|
|
11839
|
-
// settling, a state written — and moving the reader
|
|
11840
|
-
// them out of a page they never left.
|
|
11841
|
-
//
|
|
11842
|
-
//
|
|
11843
|
-
//
|
|
11914
|
+
// A replace leaves the document where it is: it is the same place said
|
|
11915
|
+
// differently — a param settling, a state written — and moving the reader
|
|
11916
|
+
// for it would throw them out of a page they never left. Where they stay
|
|
11917
|
+
// is written down for the new address (see stayInPlace). A push that keeps
|
|
11918
|
+
// the pathname is read the same way, by startAtTop itself. The one replace
|
|
11919
|
+
// that IS an arrival is a row of tabs travelling, and the row says so for
|
|
11920
|
+
// itself (see route_travel.jsx) — from inside the routing above, which is
|
|
11921
|
+
// why the replace is written down after it.
|
|
11844
11922
|
if (navigationType === "push") {
|
|
11845
11923
|
whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
|
|
11924
|
+
} else if (navigationType === "replace") {
|
|
11925
|
+
stayInPlace(url);
|
|
11846
11926
|
} else if (navigationType === "traverse") {
|
|
11847
11927
|
restoreScrollPositionOnReturn(url);
|
|
11848
11928
|
}
|
|
@@ -11956,7 +12036,7 @@ const setupBrowserIntegrationViaHistory = ({
|
|
|
11956
12036
|
// The browser's own scroll restoration is taken over here rather than left
|
|
11957
12037
|
// to whoever navigates: it is a decision about the document, and the entry
|
|
11958
12038
|
// being left must be recorded from the first pixel scrolled.
|
|
11959
|
-
installScrollRestoration();
|
|
12039
|
+
installScrollRestoration({ readAddress });
|
|
11960
12040
|
|
|
11961
12041
|
// The document is leaving, or may be killed in the background: what the
|
|
11962
12042
|
// entry says about it has to be what the document says (see
|
|
@@ -32470,6 +32550,20 @@ const toHex = ([r, g, b]) =>
|
|
|
32470
32550
|
// sure to be understood in, whatever getComputedStyle answered with.
|
|
32471
32551
|
let colorContext = null;
|
|
32472
32552
|
const parseColor = (color) => {
|
|
32553
|
+
const [r, g, b, a] = drawColor(color);
|
|
32554
|
+
if (a === 0 || a === 255) {
|
|
32555
|
+
return [r, g, b, a / 255];
|
|
32556
|
+
}
|
|
32557
|
+
// The canvas keeps a pixel premultiplied by its alpha, in bytes: the
|
|
32558
|
+
// channels of a translucent colour come back rounded at that alpha —
|
|
32559
|
+
// rgba(12, 23, 44, 0.45) reads 11, 22, 44. Made opaque, they read whole.
|
|
32560
|
+
const [rOpaque, gOpaque, bOpaque] = drawColor(
|
|
32561
|
+
`color(from ${color} srgb r g b / 1)`,
|
|
32562
|
+
);
|
|
32563
|
+
return [rOpaque, gOpaque, bOpaque, a / 255];
|
|
32564
|
+
};
|
|
32565
|
+
|
|
32566
|
+
const drawColor = (color) => {
|
|
32473
32567
|
if (!colorContext) {
|
|
32474
32568
|
const canvas = document.createElement("canvas");
|
|
32475
32569
|
canvas.width = 1;
|
|
@@ -32482,8 +32576,7 @@ const parseColor = (color) => {
|
|
|
32482
32576
|
colorContext.fillStyle = "transparent";
|
|
32483
32577
|
colorContext.fillStyle = color;
|
|
32484
32578
|
colorContext.fillRect(0, 0, 1, 1);
|
|
32485
|
-
|
|
32486
|
-
return [r, g, b, a / 255];
|
|
32579
|
+
return colorContext.getImageData(0, 0, 1, 1).data;
|
|
32487
32580
|
};
|
|
32488
32581
|
|
|
32489
32582
|
// The title belongs to the last Head to arrive, whatever order they leave in:
|
|
@@ -48458,6 +48551,15 @@ const applySpacingOnTextChildren = (children, spacing, defaultSpace) => {
|
|
|
48458
48551
|
}
|
|
48459
48552
|
return childrenWithGap;
|
|
48460
48553
|
};
|
|
48554
|
+
const isSpacingWidth = spacing => {
|
|
48555
|
+
if (typeof spacing === "number") {
|
|
48556
|
+
return true;
|
|
48557
|
+
}
|
|
48558
|
+
if (typeof spacing !== "string") {
|
|
48559
|
+
return false;
|
|
48560
|
+
}
|
|
48561
|
+
return isSizeSpacingKey(spacing) || hasCSSSizeUnit(spacing) || spacing.startsWith("var(");
|
|
48562
|
+
};
|
|
48461
48563
|
const outsideTextFlowSet = new Set();
|
|
48462
48564
|
const markAsOutsideTextFlow = jsxElement => {
|
|
48463
48565
|
outsideTextFlowSet.add(jsxElement);
|
|
@@ -48725,7 +48827,11 @@ const TextUI = props => {
|
|
|
48725
48827
|
}
|
|
48726
48828
|
const shouldPreserveSpacing = rest.as === "pre" || rest.flex || rest.grid;
|
|
48727
48829
|
if (shouldPreserveSpacing) {
|
|
48728
|
-
|
|
48830
|
+
// Between flex/grid items spacing is a gap, so only a width carries over: a
|
|
48831
|
+
// separator (the default space, a text, an element) or "pre" has no gap.
|
|
48832
|
+
if (isSpacingWidth(spacing)) {
|
|
48833
|
+
boxProps.spacing = spacing;
|
|
48834
|
+
}
|
|
48729
48835
|
} else {
|
|
48730
48836
|
children = applySpacingOnTextChildren(children, resolvedSpacing, defaultSpace);
|
|
48731
48837
|
}
|
|
@@ -70336,32 +70442,43 @@ document.addEventListener(
|
|
|
70336
70442
|
);
|
|
70337
70443
|
|
|
70338
70444
|
/*
|
|
70339
|
-
* Whether a
|
|
70340
|
-
*
|
|
70341
|
-
*
|
|
70342
|
-
*
|
|
70343
|
-
*
|
|
70344
|
-
*
|
|
70345
|
-
*
|
|
70346
|
-
*
|
|
70347
|
-
* from
|
|
70348
|
-
*
|
|
70349
|
-
*
|
|
70350
|
-
*
|
|
70351
|
-
*
|
|
70352
|
-
*
|
|
70353
|
-
*
|
|
70354
|
-
*
|
|
70355
|
-
*
|
|
70356
|
-
*
|
|
70357
|
-
*
|
|
70358
|
-
*
|
|
70445
|
+
* Whether writing a scroll position now would cost the user the scroll in
|
|
70446
|
+
* progress. A write is an instant scroll, and the browser abandons for it any
|
|
70447
|
+
* scroll it is playing on its own, short of where that one was going: a key
|
|
70448
|
+
* (Home, PageDown, Space), a smooth `scrollTo` or `scrollIntoView`, the status
|
|
70449
|
+
* bar tap on iOS, the fling a finger leaves behind once it lifts. A scroll
|
|
70450
|
+
* following a hand is not abandoned: the wheel's next turn carries on, and on
|
|
70451
|
+
* most engines a finger keeps dragging from wherever the write left it.
|
|
70452
|
+
*
|
|
70453
|
+
* Not on iOS. Its finger places the content from where the drag began, so a
|
|
70454
|
+
* write under it is undone at the finger's next move — or, when the finger
|
|
70455
|
+
* lifts first, lands after the lift and stops the fling dead. And a fling
|
|
70456
|
+
* stops on any write during its momentum, as the status bar's scroll to the
|
|
70457
|
+
* top does (Safari 26.5 on the simulator: flings of 1700px stopped 130 to 340px
|
|
70458
|
+
* after the lift, 3 in 3; a write 50ms before the lift stopped 2 flings in 3).
|
|
70459
|
+
* No finger there is a hand a write could hold to. Chrome flings on through a
|
|
70460
|
+
* write, and loses nothing by not getting one.
|
|
70461
|
+
*
|
|
70462
|
+
* A scroll is followed from its first "scroll" event to its "scrollend", and
|
|
70463
|
+
* is a hand's while a finger is down, or when a wheel turned around the time it
|
|
70464
|
+
* started or since. A finger is down from its touchstart to its touchend,
|
|
70465
|
+
* heard on what it touched: a list removes the items scrolled out of its window
|
|
70466
|
+
* while the finger is still on one of them, and the touchend of a removed
|
|
70467
|
+
* element never reaches the document.
|
|
70468
|
+
*
|
|
70469
|
+
* In a browser without "scrollend", no write is said to interrupt anything: a
|
|
70470
|
+
* scroll would never be seen ending.
|
|
70359
70471
|
*/
|
|
70360
70472
|
|
|
70361
70473
|
// The scroll a wheel turn causes starts in the frame the turn is read, or the
|
|
70362
70474
|
// next one. A turn further from it moved nothing (a wheel at an edge) and is no
|
|
70363
70475
|
// hand on a scroll that came from elsewhere.
|
|
70364
70476
|
const WHEEL_REACH = 100;
|
|
70477
|
+
// iOS's touch handling, the one engine whose finger drops writes (see above).
|
|
70478
|
+
const FINGER_KEEPS_WRITES = !window.CSS.supports(
|
|
70479
|
+
"-webkit-touch-callout",
|
|
70480
|
+
"none",
|
|
70481
|
+
);
|
|
70365
70482
|
|
|
70366
70483
|
const flightMap = new WeakMap();
|
|
70367
70484
|
let fingersDown = 0;
|
|
@@ -70371,13 +70488,13 @@ let wheelTimeStamp = -Infinity;
|
|
|
70371
70488
|
* @param {Element} scrollerEl
|
|
70372
70489
|
* @returns {boolean}
|
|
70373
70490
|
*/
|
|
70374
|
-
const
|
|
70491
|
+
const wouldInterruptScroll = (scrollerEl) => {
|
|
70375
70492
|
const flight = flightMap.get(scrollerEl);
|
|
70376
70493
|
if (!flight) {
|
|
70377
70494
|
return false;
|
|
70378
70495
|
}
|
|
70379
|
-
if (
|
|
70380
|
-
return
|
|
70496
|
+
if (fingersDown > 0) {
|
|
70497
|
+
return !FINGER_KEEPS_WRITES;
|
|
70381
70498
|
}
|
|
70382
70499
|
if (wheelTimeStamp > flight.timeStamp - WHEEL_REACH) {
|
|
70383
70500
|
return false;
|
|
@@ -70407,10 +70524,7 @@ if ("onscrollend" in window) {
|
|
|
70407
70524
|
if (flightMap.has(scroller)) {
|
|
70408
70525
|
return;
|
|
70409
70526
|
}
|
|
70410
|
-
flightMap.set(scroller, {
|
|
70411
|
-
timeStamp: e.timeStamp,
|
|
70412
|
-
fingerDown: fingersDown > 0,
|
|
70413
|
-
});
|
|
70527
|
+
flightMap.set(scroller, { timeStamp: e.timeStamp });
|
|
70414
70528
|
},
|
|
70415
70529
|
{ capture: true, passive: true },
|
|
70416
70530
|
);
|
|
@@ -74097,6 +74211,13 @@ const useListScrollSync = ({
|
|
|
74097
74211
|
if (delta > -0.5 && delta < 0.5) {
|
|
74098
74212
|
return;
|
|
74099
74213
|
}
|
|
74214
|
+
if (wouldInterruptScroll(scrollerEl)) {
|
|
74215
|
+
// As the anchoring's write would (see holdAnchorStill). The document's
|
|
74216
|
+
// box is the scroller of a list scrolling the page, and it resizes with
|
|
74217
|
+
// every item drawn taller than its filler: while the page scrolls, that
|
|
74218
|
+
// is at every window move.
|
|
74219
|
+
return;
|
|
74220
|
+
}
|
|
74100
74221
|
anchorRef.current = null;
|
|
74101
74222
|
if (horizontal) {
|
|
74102
74223
|
scrollerEl.scrollLeft += delta;
|
|
@@ -74194,13 +74315,12 @@ const useListScrollSync = ({
|
|
|
74194
74315
|
return;
|
|
74195
74316
|
}
|
|
74196
74317
|
anchorRef.current = null;
|
|
74197
|
-
if (
|
|
74318
|
+
if (wouldInterruptScroll(scrollerEl)) {
|
|
74198
74319
|
// Nobody is looking at items standing still: they are passing by, on
|
|
74199
|
-
// their way somewhere the browser
|
|
74200
|
-
//
|
|
74201
|
-
//
|
|
74202
|
-
//
|
|
74203
|
-
// of it moves, and the glide lands.
|
|
74320
|
+
// their way somewhere the browser takes them (a key, a smooth scrollTo,
|
|
74321
|
+
// the status bar, a fling). The write would stop that scroll short of
|
|
74322
|
+
// where it was going. Left alone, the items drawn above push the rest by
|
|
74323
|
+
// `drift` while all of it moves, and the scroll lands.
|
|
74204
74324
|
return;
|
|
74205
74325
|
}
|
|
74206
74326
|
scrolledByListRef.current = true;
|
|
@@ -78050,9 +78170,11 @@ const ListResolved = /*#__PURE__*/createComponentResolver([ListFirstResolver, Li
|
|
|
78050
78170
|
* does, and for a list that scrolls itself the page's own restoration cannot
|
|
78051
78171
|
* see. The position is kept under the list's `id` and the page's url, for
|
|
78052
78172
|
* the session (a reload comes back too); a list without an `id` of its own
|
|
78053
|
-
* has nothing to be remembered by
|
|
78054
|
-
* `
|
|
78055
|
-
*
|
|
78173
|
+
* has nothing to be remembered by, and two lists mounted at once under one
|
|
78174
|
+
* `id` — a popup over the page, the page a route transition is leaving —
|
|
78175
|
+
* overwrite each other (see docs/scroll.md). A fresh arrival at the page
|
|
78176
|
+
* opens at `defaultScrolled` either way, and so does a list the caller holds
|
|
78177
|
+
* through `scrolled`.
|
|
78056
78178
|
* @param {boolean} [props.deselectable]
|
|
78057
78179
|
* A single-select list allowed to hold nothing: the selected item, pressed
|
|
78058
78180
|
* again, lets go. Without it the list is a radio group — a choice, once
|