@jsenv/navi 0.29.432 → 0.29.433

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.
@@ -11109,6 +11109,11 @@ const [publishRouteRender, observeRouteRender] = createPubSub();
11109
11109
  const STORAGE_KEY = "navi_scroll_positions";
11110
11110
 
11111
11111
  const positionByUrl = new Map();
11112
+ // Where the document stands, as its scroll events last said, whoever it
11113
+ // belongs to: what a url shown without the document moving is given (see
11114
+ // stayInPlace). Null until the restoration is installed — nothing is recorded
11115
+ // before.
11116
+ let documentOffset = null;
11112
11117
  // url -> (scroller name -> position). The position is whatever the scroller
11113
11118
  // handed in: what it can put itself back on, in its own terms.
11114
11119
  const scrollerPositionsByUrl = new Map();
@@ -11225,30 +11230,25 @@ const installScrollRestoration = () => {
11225
11230
  }
11226
11231
  window.history.scrollRestoration = "manual";
11227
11232
  loadStore();
11233
+ documentOffset = { x: window.scrollX, y: window.scrollY };
11228
11234
  // Read as it happens rather than when leaving: a traverse changes the url
11229
11235
  // before anything here is told, so a position read then would be read for
11230
11236
  // the wrong page.
11231
11237
  window.addEventListener(
11232
11238
  "scroll",
11233
11239
  () => {
11240
+ documentOffset = { x: window.scrollX, y: window.scrollY };
11234
11241
  if (suspendCount) {
11235
11242
  return;
11236
11243
  }
11237
- positionByUrl.set(window.location.href, {
11238
- x: window.scrollX,
11239
- y: window.scrollY,
11240
- });
11244
+ positionByUrl.set(window.location.href, documentOffset);
11241
11245
  },
11242
11246
  { passive: true },
11243
11247
  );
11244
11248
  const positionOnLoad = positionByUrl.get(window.location.href);
11245
11249
  if (!positionOnLoad) {
11246
- // Where the browser landed the first page, written down for the reason
11247
- // startAtTop writes its top: no scroll event says so.
11248
- positionByUrl.set(window.location.href, {
11249
- x: window.scrollX,
11250
- y: window.scrollY,
11251
- });
11250
+ // The first page, where the browser landed it.
11251
+ stayInPlace(window.location.href);
11252
11252
  return;
11253
11253
  }
11254
11254
  // What a reload asks for, now that the browser has been told not to do it.
@@ -11339,6 +11339,7 @@ const restoreScrollPositionOnReturn = (url) => {
11339
11339
  // app that scrolls an element of its own scrolls it itself.
11340
11340
  const startAtTop = (url, { from } = {}) => {
11341
11341
  if (!isArrival(url, { from })) {
11342
+ stayInPlace(url);
11342
11343
  return;
11343
11344
  }
11344
11345
  arrivalCount++;
@@ -11360,6 +11361,23 @@ const isArrival = (url, { from }) => {
11360
11361
  return true;
11361
11362
  };
11362
11363
 
11364
+ // A url the document is shown under without moving: a replace, a push that
11365
+ // keeps the page (see startAtTop), the first page loaded. No scroll event says
11366
+ // where it stands, so it is written down here, or a return to it would find
11367
+ // nothing and keep the offset of the page being left. The offset the scroll
11368
+ // events last told, not read again: a read here would lay the page out in the
11369
+ // middle of a routing, once per address a settling param writes.
11370
+ //
11371
+ // Deaf with the recording: a row of tabs replaces the url as it travels, and
11372
+ // the offset belongs to nobody until the row says where its tab lands (see
11373
+ // arriveAtScrollPosition).
11374
+ const stayInPlace = (url) => {
11375
+ if (documentOffset === null || suspendCount) {
11376
+ return;
11377
+ }
11378
+ positionByUrl.set(new URL(url, window.location.href).href, documentOffset);
11379
+ };
11380
+
11363
11381
  // The same arrival, for the page's own scrollers. The document is scrolled to
11364
11382
  // its top once the page is there; a list opens where it decides to in its
11365
11383
  // first render, so what it must not find is dropped before the routing
@@ -11835,14 +11853,18 @@ const setupBrowserIntegrationViaHistory = ({
11835
11853
  // also waits for the page it returns to: an offset is clamped to whatever
11836
11854
  // page the document holds when it is written.
11837
11855
  //
11838
- // A replace gets neither: it is the same place said differently — a param
11839
- // settling, a state written — and moving the reader for it would throw
11840
- // them out of a page they never left. A push that keeps the pathname is
11841
- // read the same way, by startAtTop itself. The one replace that IS an
11842
- // arrival is a row of tabs travelling, and the row says so for itself (see
11843
- // route_travel.jsx).
11856
+ // A replace leaves the document where it is: it is the same place said
11857
+ // differently — a param settling, a state written — and moving the reader
11858
+ // for it would throw them out of a page they never left. Where they stay
11859
+ // is written down for the new address (see stayInPlace). A push that keeps
11860
+ // the pathname is read the same way, by startAtTop itself. The one replace
11861
+ // that IS an arrival is a row of tabs travelling, and the row says so for
11862
+ // itself (see route_travel.jsx) — from inside the routing above, which is
11863
+ // why the replace is written down after it.
11844
11864
  if (navigationType === "push") {
11845
11865
  whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
11866
+ } else if (navigationType === "replace") {
11867
+ stayInPlace(url);
11846
11868
  } else if (navigationType === "traverse") {
11847
11869
  restoreScrollPositionOnReturn(url);
11848
11870
  }
@@ -70336,32 +70358,43 @@ document.addEventListener(
70336
70358
  );
70337
70359
 
70338
70360
  /*
70339
- * Whether a scroller is gliding: moving on its own toward a place it was sent
70340
- * to, with no hand on it — a key (Home, PageDown, Space), a smooth `scrollTo`
70341
- * or `scrollIntoView`, the status bar tap on iOS. The other scrolls follow a
70342
- * hand: a finger on the screen, a wheel turning.
70343
- *
70344
- * It matters to whoever writes a scroll position while one plays: the write is
70345
- * an instant scroll, and the browser abandons the glide for it, short of where
70346
- * it was going. A hand is not abandoned: its next report moves the scroller on
70347
- * from wherever the write left it.
70348
- *
70349
- * A scroll is followed from its first "scroll" event to its "scrollend". It is
70350
- * a hand's when a finger was down as it started (the fling a finger leaves
70351
- * behind is still its own), when one is down now, or when a wheel turned
70352
- * around the time it started or since. A finger is down from its
70353
- * touchstart to its touchend, heard on what it touched: a list removes the
70354
- * items scrolled out of its window while the finger is still on one of them,
70355
- * and the touchend of a removed element never reaches the document.
70356
- *
70357
- * In a browser without "scrollend", nothing is said to glide: a scroll would
70358
- * never be seen ending.
70361
+ * Whether writing a scroll position now would cost the user the scroll in
70362
+ * progress. A write is an instant scroll, and the browser abandons for it any
70363
+ * scroll it is playing on its own, short of where that one was going: a key
70364
+ * (Home, PageDown, Space), a smooth `scrollTo` or `scrollIntoView`, the status
70365
+ * bar tap on iOS, the fling a finger leaves behind once it lifts. A scroll
70366
+ * following a hand is not abandoned: the wheel's next turn carries on, and on
70367
+ * most engines a finger keeps dragging from wherever the write left it.
70368
+ *
70369
+ * Not on iOS. Its finger places the content from where the drag began, so a
70370
+ * write under it is undone at the finger's next move — or, when the finger
70371
+ * lifts first, lands after the lift and stops the fling dead. And a fling
70372
+ * stops on any write during its momentum, as the status bar's scroll to the
70373
+ * top does (Safari 26.5 on the simulator: flings of 1700px stopped 130 to 340px
70374
+ * after the lift, 3 in 3; a write 50ms before the lift stopped 2 flings in 3).
70375
+ * No finger there is a hand a write could hold to. Chrome flings on through a
70376
+ * write, and loses nothing by not getting one.
70377
+ *
70378
+ * A scroll is followed from its first "scroll" event to its "scrollend", and
70379
+ * is a hand's while a finger is down, or when a wheel turned around the time it
70380
+ * started or since. A finger is down from its touchstart to its touchend,
70381
+ * heard on what it touched: a list removes the items scrolled out of its window
70382
+ * while the finger is still on one of them, and the touchend of a removed
70383
+ * element never reaches the document.
70384
+ *
70385
+ * In a browser without "scrollend", no write is said to interrupt anything: a
70386
+ * scroll would never be seen ending.
70359
70387
  */
70360
70388
 
70361
70389
  // The scroll a wheel turn causes starts in the frame the turn is read, or the
70362
70390
  // next one. A turn further from it moved nothing (a wheel at an edge) and is no
70363
70391
  // hand on a scroll that came from elsewhere.
70364
70392
  const WHEEL_REACH = 100;
70393
+ // iOS's touch handling, the one engine whose finger drops writes (see above).
70394
+ const FINGER_KEEPS_WRITES = !window.CSS.supports(
70395
+ "-webkit-touch-callout",
70396
+ "none",
70397
+ );
70365
70398
 
70366
70399
  const flightMap = new WeakMap();
70367
70400
  let fingersDown = 0;
@@ -70371,13 +70404,13 @@ let wheelTimeStamp = -Infinity;
70371
70404
  * @param {Element} scrollerEl
70372
70405
  * @returns {boolean}
70373
70406
  */
70374
- const isScrollGliding = (scrollerEl) => {
70407
+ const wouldInterruptScroll = (scrollerEl) => {
70375
70408
  const flight = flightMap.get(scrollerEl);
70376
70409
  if (!flight) {
70377
70410
  return false;
70378
70411
  }
70379
- if (flight.fingerDown || fingersDown > 0) {
70380
- return false;
70412
+ if (fingersDown > 0) {
70413
+ return !FINGER_KEEPS_WRITES;
70381
70414
  }
70382
70415
  if (wheelTimeStamp > flight.timeStamp - WHEEL_REACH) {
70383
70416
  return false;
@@ -70407,10 +70440,7 @@ if ("onscrollend" in window) {
70407
70440
  if (flightMap.has(scroller)) {
70408
70441
  return;
70409
70442
  }
70410
- flightMap.set(scroller, {
70411
- timeStamp: e.timeStamp,
70412
- fingerDown: fingersDown > 0,
70413
- });
70443
+ flightMap.set(scroller, { timeStamp: e.timeStamp });
70414
70444
  },
70415
70445
  { capture: true, passive: true },
70416
70446
  );
@@ -74097,6 +74127,13 @@ const useListScrollSync = ({
74097
74127
  if (delta > -0.5 && delta < 0.5) {
74098
74128
  return;
74099
74129
  }
74130
+ if (wouldInterruptScroll(scrollerEl)) {
74131
+ // As the anchoring's write would (see holdAnchorStill). The document's
74132
+ // box is the scroller of a list scrolling the page, and it resizes with
74133
+ // every item drawn taller than its filler: while the page scrolls, that
74134
+ // is at every window move.
74135
+ return;
74136
+ }
74100
74137
  anchorRef.current = null;
74101
74138
  if (horizontal) {
74102
74139
  scrollerEl.scrollLeft += delta;
@@ -74194,13 +74231,12 @@ const useListScrollSync = ({
74194
74231
  return;
74195
74232
  }
74196
74233
  anchorRef.current = null;
74197
- if (isScrollGliding(scrollerEl)) {
74234
+ if (wouldInterruptScroll(scrollerEl)) {
74198
74235
  // Nobody is looking at items standing still: they are passing by, on
74199
- // their way somewhere the browser was asked to take them (a key, a smooth
74200
- // scrollTo, the status bar). The write would be an instant scroll, and
74201
- // the browser abandons the glide for it, short of where it was going.
74202
- // Left alone, the items drawn above push the rest by `drift` while all
74203
- // of it moves, and the glide lands.
74236
+ // their way somewhere the browser takes them (a key, a smooth scrollTo,
74237
+ // the status bar, a fling). The write would stop that scroll short of
74238
+ // where it was going. Left alone, the items drawn above push the rest by
74239
+ // `drift` while all of it moves, and the scroll lands.
74204
74240
  return;
74205
74241
  }
74206
74242
  scrolledByListRef.current = true;