@jsenv/navi 0.29.515 → 0.29.516

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.
@@ -462,32 +462,33 @@ const MountAfterPaint = ({
462
462
  /**
463
463
  * Builds, now, every box still waiting whose top is above `bottom` (in the
464
464
  * document's coordinates): what the document will show once scrolled so that
465
- * `bottom` is the bottom of the screen. Boxes built push those below them
466
- * further down, so the ones measured above `bottom` are all the ones that can
467
- * be shown there, and a few more at worst.
465
+ * `bottom` is the bottom of the screen.
466
+ *
467
+ * Each box is read once those before it have their height. A box has no height
468
+ * until it is built, so read together they would all stand at the end of what
469
+ * is built, above `bottom` as soon as the offset is worth a screen: a page of
470
+ * sections would be built whole before its first frame. A box found below
471
+ * `bottom` waits: a build only pushes it further down, and where it stands
472
+ * already makes the document tall enough for the offset.
473
+ *
474
+ * The walk follows the set live, in mount order: a box mounted by a build is
475
+ * read too. One standing higher than boxes built before it (mounted later) can
476
+ * leave a few built that the offset does not show, never one missing that it
477
+ * does.
468
478
  *
469
479
  * Renders synchronously, so it must be called outside a Preact commit: a render
470
480
  * flushed from a layout effect runs inside the commit still walking the tree.
471
481
  */
472
482
  const buildDeferredBoxesAbove = bottom => {
473
- if (deferredBoxes.size === 0) {
474
- return;
475
- }
476
- const toBuild = [];
477
483
  for (const deferredBox of deferredBoxes) {
478
484
  const top = readTopInWindow(deferredBox.getElement());
479
- if (top !== null && top + window.scrollY < bottom) {
480
- toBuild.push(deferredBox);
485
+ if (top === null || top + window.scrollY >= bottom) {
486
+ continue;
481
487
  }
482
- }
483
- if (toBuild.length === 0) {
484
- return;
485
- }
486
- flushSyncRendering(() => {
487
- for (const deferredBox of toBuild) {
488
+ flushSyncRendering(() => {
488
489
  deferredBox.build();
489
- }
490
- });
490
+ });
491
+ }
491
492
  };
492
493
 
493
494
  // null for a box nothing draws (a closed popup, a hidden tab): no frame shows
@@ -10434,6 +10435,12 @@ const startAtTop = (url, { from } = {}) => {
10434
10435
  // position to come back to — the return would keep the offset of the page
10435
10436
  // being left.
10436
10437
  positionByUrl.set(href, { x: 0, y: 0 });
10438
+ // A list holding the document on the page arriving has placed it already: a
10439
+ // navigation holding the rendering gets here after that render, and nothing
10440
+ // owes the list another commit to put it back (see holdDocumentScroll).
10441
+ if (isDocumentHeld()) {
10442
+ return;
10443
+ }
10437
10444
  window.scrollTo({ top: 0, left: 0, behavior: "instant" });
10438
10445
  };
10439
10446
  // Also what decides whether a push carries the state of the page it keeps
@@ -13422,10 +13429,17 @@ const isRenderingPageChange = () => renderingPageChange;
13422
13429
  * outside any render — by a subscription deciding whether to re-render, by
13423
13430
  * the scroll restoration when a position is due — and it reads the routes
13424
13431
  * untracked, so asking subscribes nobody to them.
13425
- * - `leavingSignal` — true once the page is hidden and kept while its movement
13426
- * plays: still mounted, so what it fills or moves outside its own nodes (a
13427
- * slot in a bar, the document a list scrolls) steps out rather than showing
13428
- * with, or moving, the page arriving. Every box of it measures 0×0 by then.
13432
+ * - `hasLeft()` — whether the page, or a page it sits in, is kept while leaving.
13433
+ * Written by the container's render, before anything below it renders, and
13434
+ * final. `isShown()` can answer false for a page that stays on screen (the
13435
+ * address coming back, a container first rendered while the routes are
13436
+ * frozen), so what cannot be undone asks this one.
13437
+ * - `leavingSignal` — true once the page, or a page it sits in, is hidden and
13438
+ * kept while the movement plays: still mounted, so what it fills or moves
13439
+ * outside its own nodes (a slot in a bar, the document a list scrolls) steps
13440
+ * out rather than showing with, or moving, the page arriving. Every box of it
13441
+ * measures 0×0 by then. Set after the render keeping the page, so that render
13442
+ * and what it builds can only ask `hasLeft()`.
13429
13443
  */
13430
13444
  const RoutePageContext = createContext(null);
13431
13445
 
@@ -36635,7 +36649,7 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
36635
36649
  continue;
36636
36650
  }
36637
36651
  entry.hidden = true;
36638
- entry.page.leavingSignal.value = true;
36652
+ entry.page.ownLeavingSignal.value = true;
36639
36653
  // The page renders on its own while kept (a component reading a signal
36640
36654
  // the address moved), never through this container: a top node it
36641
36655
  // replaces lands between the markers unmarked, a text it writes shows
@@ -36723,11 +36737,21 @@ const hideNodesBetween = (start, end, observer) => {
36723
36737
  // page it sits in is shown itself. Never again once kept while leaving: coming
36724
36738
  // back to its route mounts the page anew.
36725
36739
  const createRoutePage = (readShownBranch, parentPage) => {
36740
+ const ownLeavingSignal = signal(false);
36726
36741
  const page = {
36727
36742
  branchId: null,
36728
36743
  element: undefined,
36729
36744
  left: false,
36730
- leavingSignal: signal(false),
36745
+ ownLeavingSignal,
36746
+ // A page nested in one that left is hidden with it, even while its own
36747
+ // container goes on picking it (a route matching on both sides).
36748
+ leavingSignal: parentPage ? computed(() => ownLeavingSignal.value || parentPage.leavingSignal.value) : ownLeavingSignal,
36749
+ hasLeft: () => {
36750
+ if (page.left) {
36751
+ return true;
36752
+ }
36753
+ return parentPage ? parentPage.hasLeft() : false;
36754
+ },
36731
36755
  isShown: () => {
36732
36756
  if (page.left) {
36733
36757
  return false;
@@ -85597,27 +85621,35 @@ const useListScrollSync = ({
85597
85621
  // landing or resizing — and said again when it reads differently (`ifChanged`):
85598
85622
  // the same scroll, the screen said right.
85599
85623
  const announcedRef = useRef(null);
85624
+ // A scroll landing past everything drawn (a key sending the list to its end,
85625
+ // a hand on the scrollbar) has no item to read a position from: it is said by
85626
+ // that re-read, even when it is the first thing the list has to say.
85627
+ const reportOwedRef = useRef(false);
85600
85628
  const reportPosition = ({
85601
85629
  ifChanged
85602
85630
  } = {}) => {
85603
85631
  if (!ref.current) {
85604
85632
  return;
85605
85633
  }
85606
- if (ifChanged && !announcedRef.current) {
85634
+ if (ifChanged && !announcedRef.current && !reportOwedRef.current) {
85607
85635
  return;
85608
85636
  }
85609
85637
  const remember = rememberScrollRef.current;
85610
85638
  const onScrolledChange = onScrolledChangeRef.current;
85611
85639
  const position = captureListAnchor(remember || Boolean(onScrolledChange));
85612
85640
  if (!position) {
85641
+ if (!ifChanged) {
85642
+ reportOwedRef.current = true;
85643
+ }
85613
85644
  return;
85614
85645
  }
85646
+ reportOwedRef.current = false;
85615
85647
  positionRef.current = position;
85616
85648
  if (!remember && !onScrolledChange) {
85617
85649
  return;
85618
85650
  }
85619
85651
  const scrolledNow = toScrolledPosition(position);
85620
- if (ifChanged && isSamePosition(scrolledNow, announcedRef.current)) {
85652
+ if (ifChanged && announcedRef.current && isSamePosition(scrolledNow, announcedRef.current)) {
85621
85653
  return;
85622
85654
  }
85623
85655
  announcedRef.current = scrolledNow;
@@ -86163,10 +86195,9 @@ const useListScrollSync = ({
86163
86195
  // ends the hold like a wheel does — the ones that come with no input event
86164
86196
  // at all: the status bar tap on iOS, find in page, a script.
86165
86197
  // Only once the list has stood where it holds itself, though. Until then
86166
- // the page is still settling around it, and some of that is written after
86167
- // the list placed itself: a navigation holding the rendering for its
86168
- // transition takes the arriving page to its top once the page rendered
86169
- // (whenRenderingResumes), and the hold puts it back at its next commit.
86198
+ // the page is still settling around it, and what that moves (a document
86199
+ // still too short for the offset clamps it) the hold puts back at its next
86200
+ // commit.
86170
86201
  const letGoUnlessHeldScroll = () => {
86171
86202
  const place = startPlaceRef.current;
86172
86203
  const way = place.way;
@@ -106919,9 +106950,9 @@ SidePanel.Foot = SidePanelFoot;
106919
106950
  * mount, or the first to render into an empty slot; later renders of a
106920
106951
  * `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
106921
106952
  * unmounting empties the slot only while it is still that one, and so does one
106922
- * in a page a route transition keeps while leaving (see keepLeavingPages in
106923
- * nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
106924
- * take it back from the page arriving.
106953
+ * in or below a page a route transition keeps while leaving (see
106954
+ * keepLeavingPages in nav/route.jsx): still mounted, hidden, it must neither
106955
+ * stay in the slot nor take it back from the page arriving.
106925
106956
  *
106926
106957
  * **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
106927
106958
  * the fills as they mount and unmount, in the order the tree walks them. A
@@ -106986,8 +107017,10 @@ const createSlot = (SlotRenderer = Box) => {
106986
107017
  }, []);
106987
107018
  // In a page kept while leaving: out of the slot as if unmounted, since
106988
107019
  // the page arriving is what the slot must show — or nothing, when it
106989
- // fills none.
106990
- if (routePage && routePage.leavingSignal.value) {
107020
+ // fills none. The signal brings the render that steps out; hasLeft() refuses
107021
+ // a fill built in a kept page (a wrapper changing shape rebuilds what it
107022
+ // holds), which renders before that signal is set.
107023
+ if (routePage && (routePage.leavingSignal.value || routePage.hasLeft())) {
106991
107024
  if (filler === fillerRef) {
106992
107025
  filler = null;
106993
107026
  slotPropsSignal.value = null;