@jsenv/navi 0.29.515 → 0.29.517

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.
@@ -489,32 +489,33 @@ const MountAfterPaint = ({
489
489
  /**
490
490
  * Builds, now, every box still waiting whose top is above `bottom` (in the
491
491
  * document's coordinates): what the document will show once scrolled so that
492
- * `bottom` is the bottom of the screen. Boxes built push those below them
493
- * further down, so the ones measured above `bottom` are all the ones that can
494
- * be shown there, and a few more at worst.
492
+ * `bottom` is the bottom of the screen.
493
+ *
494
+ * Each box is read once those before it have their height. A box has no height
495
+ * until it is built, so read together they would all stand at the end of what
496
+ * is built, above `bottom` as soon as the offset is worth a screen: a page of
497
+ * sections would be built whole before its first frame. A box found below
498
+ * `bottom` waits: a build only pushes it further down, and where it stands
499
+ * already makes the document tall enough for the offset.
500
+ *
501
+ * The walk follows the set live, in mount order: a box mounted by a build is
502
+ * read too. One standing higher than boxes built before it (mounted later) can
503
+ * leave a few built that the offset does not show, never one missing that it
504
+ * does.
495
505
  *
496
506
  * Renders synchronously, so it must be called outside a Preact commit: a render
497
507
  * flushed from a layout effect runs inside the commit still walking the tree.
498
508
  */
499
509
  const buildDeferredBoxesAbove = bottom => {
500
- if (deferredBoxes.size === 0) {
501
- return;
502
- }
503
- const toBuild = [];
504
510
  for (const deferredBox of deferredBoxes) {
505
511
  const top = readTopInWindow(deferredBox.getElement());
506
- if (top !== null && top + window.scrollY < bottom) {
507
- toBuild.push(deferredBox);
512
+ if (top === null || top + window.scrollY >= bottom) {
513
+ continue;
508
514
  }
509
- }
510
- if (toBuild.length === 0) {
511
- return;
512
- }
513
- flushSyncRendering(() => {
514
- for (const deferredBox of toBuild) {
515
+ flushSyncRendering(() => {
515
516
  deferredBox.build();
516
- }
517
- });
517
+ });
518
+ }
518
519
  };
519
520
 
520
521
  // null for a box nothing draws (a closed popup, a hidden tab): no frame shows
@@ -10839,6 +10840,12 @@ const startAtTop = (url, { from } = {}) => {
10839
10840
  // position to come back to — the return would keep the offset of the page
10840
10841
  // being left.
10841
10842
  positionByUrl.set(href, { x: 0, y: 0 });
10843
+ // A list holding the document on the page arriving has placed it already: a
10844
+ // navigation holding the rendering gets here after that render, and nothing
10845
+ // owes the list another commit to put it back (see holdDocumentScroll).
10846
+ if (isDocumentHeld()) {
10847
+ return;
10848
+ }
10842
10849
  window.scrollTo({ top: 0, left: 0, behavior: "instant" });
10843
10850
  };
10844
10851
  // Also what decides whether a push carries the state of the page it keeps
@@ -14023,10 +14030,17 @@ const isRenderingPageChange = () => renderingPageChange;
14023
14030
  * outside any render — by a subscription deciding whether to re-render, by
14024
14031
  * the scroll restoration when a position is due — and it reads the routes
14025
14032
  * untracked, so asking subscribes nobody to them.
14026
- * - `leavingSignal` — true once the page is hidden and kept while its movement
14027
- * plays: still mounted, so what it fills or moves outside its own nodes (a
14028
- * slot in a bar, the document a list scrolls) steps out rather than showing
14029
- * with, or moving, the page arriving. Every box of it measures 0×0 by then.
14033
+ * - `hasLeft()` — whether the page, or a page it sits in, is kept while leaving.
14034
+ * Written by the container's render, before anything below it renders, and
14035
+ * final. `isShown()` can answer false for a page that stays on screen (the
14036
+ * address coming back, a container first rendered while the routes are
14037
+ * frozen), so what cannot be undone asks this one.
14038
+ * - `leavingSignal` — true once the page, or a page it sits in, is hidden and
14039
+ * kept while the movement plays: still mounted, so what it fills or moves
14040
+ * outside its own nodes (a slot in a bar, the document a list scrolls) steps
14041
+ * out rather than showing with, or moving, the page arriving. Every box of it
14042
+ * measures 0×0 by then. Set after the render keeping the page, so that render
14043
+ * and what it builds can only ask `hasLeft()`.
14030
14044
  */
14031
14045
  const RoutePageContext = createContext(null);
14032
14046
 
@@ -37886,7 +37900,7 @@ const useContentKeepingLeavingPages = (branch, readShownBranch, parentPage) => {
37886
37900
  continue;
37887
37901
  }
37888
37902
  entry.hidden = true;
37889
- entry.page.leavingSignal.value = true;
37903
+ entry.page.ownLeavingSignal.value = true;
37890
37904
  // The page renders on its own while kept (a component reading a signal
37891
37905
  // the address moved), never through this container: a top node it
37892
37906
  // replaces lands between the markers unmarked, a text it writes shows
@@ -37974,11 +37988,21 @@ const hideNodesBetween = (start, end, observer) => {
37974
37988
  // page it sits in is shown itself. Never again once kept while leaving: coming
37975
37989
  // back to its route mounts the page anew.
37976
37990
  const createRoutePage = (readShownBranch, parentPage) => {
37991
+ const ownLeavingSignal = signal(false);
37977
37992
  const page = {
37978
37993
  branchId: null,
37979
37994
  element: undefined,
37980
37995
  left: false,
37981
- leavingSignal: signal(false),
37996
+ ownLeavingSignal,
37997
+ // A page nested in one that left is hidden with it, even while its own
37998
+ // container goes on picking it (a route matching on both sides).
37999
+ leavingSignal: parentPage ? computed(() => ownLeavingSignal.value || parentPage.leavingSignal.value) : ownLeavingSignal,
38000
+ hasLeft: () => {
38001
+ if (page.left) {
38002
+ return true;
38003
+ }
38004
+ return parentPage ? parentPage.hasLeft() : false;
38005
+ },
37982
38006
  isShown: () => {
37983
38007
  if (page.left) {
37984
38008
  return false;
@@ -39810,6 +39834,12 @@ const css$17 = /* css */`@property --navi-route-transition-leave {
39810
39834
  inherits: false
39811
39835
  }
39812
39836
 
39837
+ @property --navi-furniture-fade {
39838
+ syntax: "<number>";
39839
+ inherits: true;
39840
+ initial-value: 1;
39841
+ }
39842
+
39813
39843
  :root::view-transition, :root::view-transition-group(*), :root::view-transition-image-pair(*), :root::view-transition-old(*), :root::view-transition-new(*) {
39814
39844
  --navi-route-transition-leave: inherit;
39815
39845
  --navi-route-transition-enter: inherit;
@@ -39891,6 +39921,24 @@ const css$17 = /* css */`@property --navi-route-transition-leave {
39891
39921
  &::view-transition-group(*), &::view-transition-old(*), &::view-transition-new(*) {
39892
39922
  animation-duration: var(--navi-route-transition-duration, .3s);
39893
39923
  }
39924
+
39925
+ &::view-transition-image-pair(.navi_furniture_shared) {
39926
+ animation: navi-furniture-fade
39927
+ var(--navi-route-transition-duration, .3s) ease both;
39928
+ }
39929
+
39930
+ &::view-transition-old(.navi_furniture_shared), &::view-transition-new(.navi_furniture_shared) {
39931
+ mix-blend-mode: plus-lighter;
39932
+ animation-name: none;
39933
+ }
39934
+
39935
+ &::view-transition-old(.navi_furniture_shared) {
39936
+ opacity: calc(1 - var(--navi-furniture-fade));
39937
+ }
39938
+
39939
+ &::view-transition-new(.navi_furniture_shared) {
39940
+ opacity: var(--navi-furniture-fade);
39941
+ }
39894
39942
  }
39895
39943
 
39896
39944
  &::view-transition-old(root), &::view-transition-new(root), &::view-transition-old(navi-route-transition), &::view-transition-new(navi-route-transition) {
@@ -40177,6 +40225,16 @@ const css$17 = /* css */`@property --navi-route-transition-leave {
40177
40225
  opacity: 0;
40178
40226
  }
40179
40227
  }
40228
+
40229
+ @keyframes navi-furniture-fade {
40230
+ from {
40231
+ --navi-furniture-fade: 0;
40232
+ }
40233
+
40234
+ to {
40235
+ --navi-furniture-fade: 1;
40236
+ }
40237
+ }
40180
40238
  `;
40181
40239
 
40182
40240
  /**
@@ -87454,27 +87512,35 @@ const useListScrollSync = ({
87454
87512
  // landing or resizing — and said again when it reads differently (`ifChanged`):
87455
87513
  // the same scroll, the screen said right.
87456
87514
  const announcedRef = useRef(null);
87515
+ // A scroll landing past everything drawn (a key sending the list to its end,
87516
+ // a hand on the scrollbar) has no item to read a position from: it is said by
87517
+ // that re-read, even when it is the first thing the list has to say.
87518
+ const reportOwedRef = useRef(false);
87457
87519
  const reportPosition = ({
87458
87520
  ifChanged
87459
87521
  } = {}) => {
87460
87522
  if (!ref.current) {
87461
87523
  return;
87462
87524
  }
87463
- if (ifChanged && !announcedRef.current) {
87525
+ if (ifChanged && !announcedRef.current && !reportOwedRef.current) {
87464
87526
  return;
87465
87527
  }
87466
87528
  const remember = rememberScrollRef.current;
87467
87529
  const onScrolledChange = onScrolledChangeRef.current;
87468
87530
  const position = captureListAnchor(remember || Boolean(onScrolledChange));
87469
87531
  if (!position) {
87532
+ if (!ifChanged) {
87533
+ reportOwedRef.current = true;
87534
+ }
87470
87535
  return;
87471
87536
  }
87537
+ reportOwedRef.current = false;
87472
87538
  positionRef.current = position;
87473
87539
  if (!remember && !onScrolledChange) {
87474
87540
  return;
87475
87541
  }
87476
87542
  const scrolledNow = toScrolledPosition(position);
87477
- if (ifChanged && isSamePosition(scrolledNow, announcedRef.current)) {
87543
+ if (ifChanged && announcedRef.current && isSamePosition(scrolledNow, announcedRef.current)) {
87478
87544
  return;
87479
87545
  }
87480
87546
  announcedRef.current = scrolledNow;
@@ -88030,10 +88096,9 @@ const useListScrollSync = ({
88030
88096
  // ends the hold like a wheel does — the ones that come with no input event
88031
88097
  // at all: the status bar tap on iOS, find in page, a script.
88032
88098
  // Only once the list has stood where it holds itself, though. Until then
88033
- // the page is still settling around it, and some of that is written after
88034
- // the list placed itself: a navigation holding the rendering for its
88035
- // transition takes the arriving page to its top once the page rendered
88036
- // (whenRenderingResumes), and the hold puts it back at its next commit.
88099
+ // the page is still settling around it, and what that moves (a document
88100
+ // still too short for the offset clamps it) the hold puts back at its next
88101
+ // commit.
88037
88102
  const letGoUnlessHeldScroll = () => {
88038
88103
  const place = startPlaceRef.current;
88039
88104
  const way = place.way;
@@ -109061,9 +109126,9 @@ SidePanel.Foot = SidePanelFoot;
109061
109126
  * mount, or the first to render into an empty slot; later renders of a
109062
109127
  * `SlotFill` that is not the filler leave the slot alone. A `SlotFill`
109063
109128
  * unmounting empties the slot only while it is still that one, and so does one
109064
- * in a page a route transition keeps while leaving (see keepLeavingPages in
109065
- * nav/route.jsx): still mounted, hidden, it must neither stay in the slot nor
109066
- * take it back from the page arriving.
109129
+ * in or below a page a route transition keeps while leaving (see
109130
+ * keepLeavingPages in nav/route.jsx): still mounted, hidden, it must neither
109131
+ * stay in the slot nor take it back from the page arriving.
109067
109132
  *
109068
109133
  * **One `SlotFill`, rendered where the choice is made.** `isFilled` follows
109069
109134
  * the fills as they mount and unmount, in the order the tree walks them. A
@@ -109128,8 +109193,10 @@ const createSlot = (SlotRenderer = Box) => {
109128
109193
  }, []);
109129
109194
  // In a page kept while leaving: out of the slot as if unmounted, since
109130
109195
  // the page arriving is what the slot must show — or nothing, when it
109131
- // fills none.
109132
- if (routePage && routePage.leavingSignal.value) {
109196
+ // fills none. The signal brings the render that steps out; hasLeft() refuses
109197
+ // a fill built in a kept page (a wrapper changing shape rebuilds what it
109198
+ // holds), which renders before that signal is set.
109199
+ if (routePage && (routePage.leavingSignal.value || routePage.hasLeft())) {
109133
109200
  if (filler === fillerRef) {
109134
109201
  filler = null;
109135
109202
  slotPropsSignal.value = null;