@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.
@@ -11537,6 +11537,11 @@ const [publishRouteRender, observeRouteRender] = createPubSub();
11537
11537
  const STORAGE_KEY = "navi_scroll_positions";
11538
11538
 
11539
11539
  const positionByUrl = new Map();
11540
+ // Where the document stands, as its scroll events last said, whoever it
11541
+ // belongs to: what a url shown without the document moving is given (see
11542
+ // stayInPlace). Null until the restoration is installed — nothing is recorded
11543
+ // before.
11544
+ let documentOffset = null;
11540
11545
  // url -> (scroller name -> position). The position is whatever the scroller
11541
11546
  // handed in: what it can put itself back on, in its own terms.
11542
11547
  const scrollerPositionsByUrl = new Map();
@@ -11653,30 +11658,25 @@ const installScrollRestoration = () => {
11653
11658
  }
11654
11659
  window.history.scrollRestoration = "manual";
11655
11660
  loadStore();
11661
+ documentOffset = { x: window.scrollX, y: window.scrollY };
11656
11662
  // Read as it happens rather than when leaving: a traverse changes the url
11657
11663
  // before anything here is told, so a position read then would be read for
11658
11664
  // the wrong page.
11659
11665
  window.addEventListener(
11660
11666
  "scroll",
11661
11667
  () => {
11668
+ documentOffset = { x: window.scrollX, y: window.scrollY };
11662
11669
  if (suspendCount) {
11663
11670
  return;
11664
11671
  }
11665
- positionByUrl.set(window.location.href, {
11666
- x: window.scrollX,
11667
- y: window.scrollY,
11668
- });
11672
+ positionByUrl.set(window.location.href, documentOffset);
11669
11673
  },
11670
11674
  { passive: true },
11671
11675
  );
11672
11676
  const positionOnLoad = positionByUrl.get(window.location.href);
11673
11677
  if (!positionOnLoad) {
11674
- // Where the browser landed the first page, written down for the reason
11675
- // startAtTop writes its top: no scroll event says so.
11676
- positionByUrl.set(window.location.href, {
11677
- x: window.scrollX,
11678
- y: window.scrollY,
11679
- });
11678
+ // The first page, where the browser landed it.
11679
+ stayInPlace(window.location.href);
11680
11680
  return;
11681
11681
  }
11682
11682
  // What a reload asks for, now that the browser has been told not to do it.
@@ -11767,6 +11767,7 @@ const restoreScrollPositionOnReturn = (url) => {
11767
11767
  // app that scrolls an element of its own scrolls it itself.
11768
11768
  const startAtTop = (url, { from } = {}) => {
11769
11769
  if (!isArrival(url, { from })) {
11770
+ stayInPlace(url);
11770
11771
  return;
11771
11772
  }
11772
11773
  arrivalCount++;
@@ -11788,6 +11789,23 @@ const isArrival = (url, { from }) => {
11788
11789
  return true;
11789
11790
  };
11790
11791
 
11792
+ // A url the document is shown under without moving: a replace, a push that
11793
+ // keeps the page (see startAtTop), the first page loaded. No scroll event says
11794
+ // where it stands, so it is written down here, or a return to it would find
11795
+ // nothing and keep the offset of the page being left. The offset the scroll
11796
+ // events last told, not read again: a read here would lay the page out in the
11797
+ // middle of a routing, once per address a settling param writes.
11798
+ //
11799
+ // Deaf with the recording: a row of tabs replaces the url as it travels, and
11800
+ // the offset belongs to nobody until the row says where its tab lands (see
11801
+ // arriveAtScrollPosition).
11802
+ const stayInPlace = (url) => {
11803
+ if (documentOffset === null || suspendCount) {
11804
+ return;
11805
+ }
11806
+ positionByUrl.set(new URL(url, window.location.href).href, documentOffset);
11807
+ };
11808
+
11791
11809
  // The same arrival, for the page's own scrollers. The document is scrolled to
11792
11810
  // its top once the page is there; a list opens where it decides to in its
11793
11811
  // first render, so what it must not find is dropped before the routing
@@ -12263,14 +12281,18 @@ const setupBrowserIntegrationViaHistory = ({
12263
12281
  // also waits for the page it returns to: an offset is clamped to whatever
12264
12282
  // page the document holds when it is written.
12265
12283
  //
12266
- // A replace gets neither: it is the same place said differently — a param
12267
- // settling, a state written — and moving the reader for it would throw
12268
- // them out of a page they never left. A push that keeps the pathname is
12269
- // read the same way, by startAtTop itself. The one replace that IS an
12270
- // arrival is a row of tabs travelling, and the row says so for itself (see
12271
- // route_travel.jsx).
12284
+ // A replace leaves the document where it is: it is the same place said
12285
+ // differently — a param settling, a state written — and moving the reader
12286
+ // for it would throw them out of a page they never left. Where they stay
12287
+ // is written down for the new address (see stayInPlace). A push that keeps
12288
+ // the pathname is read the same way, by startAtTop itself. The one replace
12289
+ // that IS an arrival is a row of tabs travelling, and the row says so for
12290
+ // itself (see route_travel.jsx) — from inside the routing above, which is
12291
+ // why the replace is written down after it.
12272
12292
  if (navigationType === "push") {
12273
12293
  whenRenderingResumes(() => startAtTop(url, { from: urlLeft }));
12294
+ } else if (navigationType === "replace") {
12295
+ stayInPlace(url);
12274
12296
  } else if (navigationType === "traverse") {
12275
12297
  restoreScrollPositionOnReturn(url);
12276
12298
  }
@@ -71723,32 +71745,43 @@ document.addEventListener(
71723
71745
  );
71724
71746
 
71725
71747
  /*
71726
- * Whether a scroller is gliding: moving on its own toward a place it was sent
71727
- * to, with no hand on it — a key (Home, PageDown, Space), a smooth `scrollTo`
71728
- * or `scrollIntoView`, the status bar tap on iOS. The other scrolls follow a
71729
- * hand: a finger on the screen, a wheel turning.
71730
- *
71731
- * It matters to whoever writes a scroll position while one plays: the write is
71732
- * an instant scroll, and the browser abandons the glide for it, short of where
71733
- * it was going. A hand is not abandoned: its next report moves the scroller on
71734
- * from wherever the write left it.
71735
- *
71736
- * A scroll is followed from its first "scroll" event to its "scrollend". It is
71737
- * a hand's when a finger was down as it started (the fling a finger leaves
71738
- * behind is still its own), when one is down now, or when a wheel turned
71739
- * around the time it started or since. A finger is down from its
71740
- * touchstart to its touchend, heard on what it touched: a list removes the
71741
- * items scrolled out of its window while the finger is still on one of them,
71742
- * and the touchend of a removed element never reaches the document.
71743
- *
71744
- * In a browser without "scrollend", nothing is said to glide: a scroll would
71745
- * never be seen ending.
71748
+ * Whether writing a scroll position now would cost the user the scroll in
71749
+ * progress. A write is an instant scroll, and the browser abandons for it any
71750
+ * scroll it is playing on its own, short of where that one was going: a key
71751
+ * (Home, PageDown, Space), a smooth `scrollTo` or `scrollIntoView`, the status
71752
+ * bar tap on iOS, the fling a finger leaves behind once it lifts. A scroll
71753
+ * following a hand is not abandoned: the wheel's next turn carries on, and on
71754
+ * most engines a finger keeps dragging from wherever the write left it.
71755
+ *
71756
+ * Not on iOS. Its finger places the content from where the drag began, so a
71757
+ * write under it is undone at the finger's next move — or, when the finger
71758
+ * lifts first, lands after the lift and stops the fling dead. And a fling
71759
+ * stops on any write during its momentum, as the status bar's scroll to the
71760
+ * top does (Safari 26.5 on the simulator: flings of 1700px stopped 130 to 340px
71761
+ * after the lift, 3 in 3; a write 50ms before the lift stopped 2 flings in 3).
71762
+ * No finger there is a hand a write could hold to. Chrome flings on through a
71763
+ * write, and loses nothing by not getting one.
71764
+ *
71765
+ * A scroll is followed from its first "scroll" event to its "scrollend", and
71766
+ * is a hand's while a finger is down, or when a wheel turned around the time it
71767
+ * started or since. A finger is down from its touchstart to its touchend,
71768
+ * heard on what it touched: a list removes the items scrolled out of its window
71769
+ * while the finger is still on one of them, and the touchend of a removed
71770
+ * element never reaches the document.
71771
+ *
71772
+ * In a browser without "scrollend", no write is said to interrupt anything: a
71773
+ * scroll would never be seen ending.
71746
71774
  */
71747
71775
 
71748
71776
  // The scroll a wheel turn causes starts in the frame the turn is read, or the
71749
71777
  // next one. A turn further from it moved nothing (a wheel at an edge) and is no
71750
71778
  // hand on a scroll that came from elsewhere.
71751
71779
  const WHEEL_REACH = 100;
71780
+ // iOS's touch handling, the one engine whose finger drops writes (see above).
71781
+ const FINGER_KEEPS_WRITES = !window.CSS.supports(
71782
+ "-webkit-touch-callout",
71783
+ "none",
71784
+ );
71752
71785
 
71753
71786
  const flightMap = new WeakMap();
71754
71787
  let fingersDown = 0;
@@ -71758,13 +71791,13 @@ let wheelTimeStamp = -Infinity;
71758
71791
  * @param {Element} scrollerEl
71759
71792
  * @returns {boolean}
71760
71793
  */
71761
- const isScrollGliding = (scrollerEl) => {
71794
+ const wouldInterruptScroll = (scrollerEl) => {
71762
71795
  const flight = flightMap.get(scrollerEl);
71763
71796
  if (!flight) {
71764
71797
  return false;
71765
71798
  }
71766
- if (flight.fingerDown || fingersDown > 0) {
71767
- return false;
71799
+ if (fingersDown > 0) {
71800
+ return !FINGER_KEEPS_WRITES;
71768
71801
  }
71769
71802
  if (wheelTimeStamp > flight.timeStamp - WHEEL_REACH) {
71770
71803
  return false;
@@ -71794,10 +71827,7 @@ if ("onscrollend" in window) {
71794
71827
  if (flightMap.has(scroller)) {
71795
71828
  return;
71796
71829
  }
71797
- flightMap.set(scroller, {
71798
- timeStamp: e.timeStamp,
71799
- fingerDown: fingersDown > 0,
71800
- });
71830
+ flightMap.set(scroller, { timeStamp: e.timeStamp });
71801
71831
  },
71802
71832
  { capture: true, passive: true },
71803
71833
  );
@@ -75487,6 +75517,13 @@ const useListScrollSync = ({
75487
75517
  if (delta > -0.5 && delta < 0.5) {
75488
75518
  return;
75489
75519
  }
75520
+ if (wouldInterruptScroll(scrollerEl)) {
75521
+ // As the anchoring's write would (see holdAnchorStill). The document's
75522
+ // box is the scroller of a list scrolling the page, and it resizes with
75523
+ // every item drawn taller than its filler: while the page scrolls, that
75524
+ // is at every window move.
75525
+ return;
75526
+ }
75490
75527
  anchorRef.current = null;
75491
75528
  if (horizontal) {
75492
75529
  scrollerEl.scrollLeft += delta;
@@ -75584,13 +75621,12 @@ const useListScrollSync = ({
75584
75621
  return;
75585
75622
  }
75586
75623
  anchorRef.current = null;
75587
- if (isScrollGliding(scrollerEl)) {
75624
+ if (wouldInterruptScroll(scrollerEl)) {
75588
75625
  // Nobody is looking at items standing still: they are passing by, on
75589
- // their way somewhere the browser was asked to take them (a key, a smooth
75590
- // scrollTo, the status bar). The write would be an instant scroll, and
75591
- // the browser abandons the glide for it, short of where it was going.
75592
- // Left alone, the items drawn above push the rest by `drift` while all
75593
- // of it moves, and the glide lands.
75626
+ // their way somewhere the browser takes them (a key, a smooth scrollTo,
75627
+ // the status bar, a fling). The write would stop that scroll short of
75628
+ // where it was going. Left alone, the items drawn above push the rest by
75629
+ // `drift` while all of it moves, and the scroll lands.
75594
75630
  return;
75595
75631
  }
75596
75632
  scrolledByListRef.current = true;