@jsenv/navi 0.30.32 → 0.30.34

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.
@@ -13832,7 +13832,9 @@ const isRenderingPageChange = () => renderingPageChange;
13832
13832
  * outside its own nodes (a slot in a bar, the document a list scrolls) steps
13833
13833
  * out rather than showing with, or moving, the page arriving. Every box of it
13834
13834
  * measures 0×0 by then. Set after the render keeping the page, so that render
13835
- * and what it builds can only ask `hasLeft()`.
13835
+ * and what it builds can only ask `hasLeft()`. Cleared by the render showing
13836
+ * the page again, before anything below it renders: what mounts then never
13837
+ * reads its page as leaving.
13836
13838
  */
13837
13839
  const RoutePageContext = createContext(null);
13838
13840
 
@@ -37287,8 +37289,16 @@ const RouteRowPane = ({
37287
37289
  value: page
37288
37290
  }, branch.node));
37289
37291
  }
37292
+ // Back on screen before what it holds renders: built here, or mounted again under
37293
+ // it, that content runs its layout effects before this component's, and would
37294
+ // otherwise read its page as leaving.
37295
+ if (shown) {
37296
+ page.ownLeavingSignal.value = false;
37297
+ }
37290
37298
  useLayoutEffect(() => {
37291
- page.ownLeavingSignal.value = !shown;
37299
+ if (!shown) {
37300
+ page.ownLeavingSignal.value = true;
37301
+ }
37292
37302
  }, [shown]);
37293
37303
  return jsx("div", {
37294
37304
  "data-route-row-pane": "",
@@ -41358,7 +41368,7 @@ const css$16 = /* css */`.navi_route_travel {
41358
41368
  display: flex;
41359
41369
  }
41360
41370
 
41361
- & > [data-route-row-pane][inert] {
41371
+ & > [data-route-row-pane][inert]:not([data-route-row-held]), &:has( > [data-route-row-held]) > [data-route-row-pane]:not([data-route-row-held]) {
41362
41372
  top: var(--navi-route-row-pin, 0);
41363
41373
  contain: paint;
41364
41374
  max-height: 100dvh;
@@ -41408,6 +41418,12 @@ const ROW_READING = {
41408
41418
  // How far a scroll position written can read back from what was written: rounded to
41409
41419
  // the device's pixels.
41410
41420
  const WRITE_ROUNDING = 1;
41421
+ // The most one frame of a travel plays: a frame of a 30fps screen, which is what a
41422
+ // browser saving power gives (an iPhone in Low Power Mode, Chrome's Energy Saver).
41423
+ // A frame later than that is a main thread busy elsewhere (the page arriving builds,
41424
+ // a list fills its budget), and the travel takes longer rather than jump by it. Not a
41425
+ // frame of a 60Hz screen: that would play every travel in slow motion at 30fps.
41426
+ const TRAVEL_FRAME_MAX_MS = 1000 / 30;
41411
41427
 
41412
41428
  /**
41413
41429
  * @type {import("preact").FunctionComponent<{
@@ -41683,18 +41699,22 @@ const RouteTravel = ({
41683
41699
  // hesitates, then peaks at 1.65 times this one's speed): two rows of tabs in one
41684
41700
  // app would not move alike. The scroll position is written at every frame, so the
41685
41701
  // row stays a scroller all the way: what follows it (a Nav's bar) reads it as it
41686
- // reads a hand, and a finger landing finds it where it is. The clock starts at the
41687
- // first frame, after what the press itself costs, as an animation's does.
41702
+ // reads a hand, and a finger landing finds it where it is. That is played on the
41703
+ // main thread: a transform on the panes would play on the compositor, but it would
41704
+ // capture what the page on screen holds in position: fixed. Hence a clock of its
41705
+ // own, which a busy main thread slows down rather than makes jump.
41688
41706
  const playTravel = (pane, movement) => {
41689
41707
  const row = rowRef.current;
41690
41708
  // Already moving, so there is no ease-in to play: it would stall the row for an
41691
41709
  // instant right where the eye is following it.
41692
41710
  const replaced = movement.playing;
41693
41711
  const inFlight = Boolean(replaced) || Boolean(movement.rowTaken);
41712
+ // Before the row is read: let go of for an instant, it would be snapped to the
41713
+ // nearest page by that read, and the travel would start from there.
41714
+ row.setAttribute("data-route-row-travelling", "");
41694
41715
  if (replaced) {
41695
- // Its frames only: the row stays travelling across the hand-over. Let go of,
41696
- // it snaps to the nearest page at the read below, and the travel starts there.
41697
- replaced.cancelFrame();
41716
+ replaced.cancel();
41717
+ movement.playing = null;
41698
41718
  }
41699
41719
  const from = row.scrollLeft;
41700
41720
  const {
@@ -41706,56 +41726,88 @@ const RouteTravel = ({
41706
41726
  const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
41707
41727
  if (!travelMs) {
41708
41728
  row.scrollLeft = pane.offsetLeft;
41729
+ letRowGo(movement);
41709
41730
  // A write that changes nothing is followed by no scroll event to say the row
41710
41731
  // stopped. After the commit that asked, which goes on to say the row moves.
41711
41732
  Promise.resolve().then(landRow);
41712
41733
  return;
41713
41734
  }
41714
41735
  const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
41715
- let start = null;
41716
41736
  let frame = null;
41737
+ // How much of the travel has played, frame by frame. A travel setting off plays
41738
+ // nothing at its first frame, which may have begun while the press was still being
41739
+ // handled: that is no travel. One taking over goes on from the last frame of the
41740
+ // one it replaces, or the row would stand still for a frame where the eye follows it.
41741
+ let played = 0;
41717
41742
  const playing = {
41718
41743
  // Where this travel put the row, for a scroll event to tell it from a hand.
41719
41744
  written: from,
41720
- cancelFrame: () => {
41745
+ lastFrame: replaced ? replaced.lastFrame : null,
41746
+ cancel: () => {
41721
41747
  cancelAnimationFrame(frame);
41722
- },
41723
- stop: () => {
41724
- playing.cancelFrame();
41725
- if (movement.playing === playing) {
41726
- movement.playing = null;
41727
- row.removeAttribute("data-route-row-travelling");
41728
- }
41729
41748
  }
41730
41749
  };
41731
41750
  const step = now => {
41732
- if (start === null) {
41733
- // When this frame runs, not when it began: under load it began while the
41734
- // press was still being handled, and that time is no travel.
41735
- start = performance.now();
41751
+ if (playing.lastFrame !== null) {
41752
+ const gap = now - playing.lastFrame;
41753
+ if (gap > TRAVEL_FRAME_MAX_MS) {
41754
+ played += TRAVEL_FRAME_MAX_MS;
41755
+ } else if (gap > 0) {
41756
+ played += gap;
41757
+ }
41736
41758
  }
41737
- // The next frame may have begun while this first one was still being handled.
41738
- const elapsed = now > start ? (now - start) / travelMs : 0;
41759
+ playing.lastFrame = now;
41760
+ const elapsed = played / travelMs;
41739
41761
  // Read at every frame: the layout may move under the travel (a scrollbar
41740
41762
  // coming in narrows the row), and it goes where the page stands.
41741
41763
  const to = pane.offsetLeft;
41742
41764
  const arrived = elapsed >= 1;
41743
41765
  playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
41744
41766
  row.scrollLeft = playing.written;
41767
+ // In the frame it is written: the scroll event saying so comes a frame later,
41768
+ // and what follows the row would be drawn a frame behind it.
41769
+ sayRowMoves();
41745
41770
  if (!arrived) {
41746
41771
  frame = requestAnimationFrame(step);
41747
41772
  return;
41748
41773
  }
41749
- playing.stop();
41774
+ letRowGo(movement);
41750
41775
  // Said here rather than waited for: the last frames of a curve can move the row
41751
41776
  // by less than a pixel, and nothing then says it has stopped.
41752
41777
  landRow();
41753
41778
  };
41754
41779
  movement.playing = playing;
41755
- row.setAttribute("data-route-row-travelling", "");
41756
41780
  frame = requestAnimationFrame(step);
41757
41781
  };
41758
41782
 
41783
+ // The row given back to the browser: no travel writes it any more, and it snaps
41784
+ // again. A travel replacing another keeps it (playTravel).
41785
+ const letRowGo = movement => {
41786
+ if (movement.playing) {
41787
+ movement.playing.cancel();
41788
+ movement.playing = null;
41789
+ }
41790
+ rowRef.current.removeAttribute("data-route-row-travelling");
41791
+ };
41792
+
41793
+ // The address runs ahead of a hand to build the page it pulls towards (onScroll):
41794
+ // the pane under the hand stays laid out as the page on screen until the hand lets
41795
+ // go, whatever `inert` says (see the CSS). What reads the page held is told apart
41796
+ // (route_travel_hold.js), and both are let go of together.
41797
+ const heldPaneRef = useRef(null);
41798
+ const holdPaneOnScreen = pane => {
41799
+ heldPaneRef.current = pane;
41800
+ pane.setAttribute("data-route-row-held", "");
41801
+ };
41802
+ const releaseHold = () => {
41803
+ releaseHeldPage();
41804
+ const heldPane = heldPaneRef.current;
41805
+ if (heldPane) {
41806
+ heldPaneRef.current = null;
41807
+ heldPane.removeAttribute("data-route-row-held");
41808
+ }
41809
+ };
41810
+
41759
41811
  // Every column back where the map says, and the row on the page of the address, in
41760
41812
  // one go: that page stands at the same place on screen either way.
41761
41813
  const putRowBack = movement => {
@@ -41784,7 +41836,7 @@ const RouteTravel = ({
41784
41836
  const position = row.scrollLeft;
41785
41837
  movement.rowTaken = true;
41786
41838
  // A travel no hand set off holds nothing: it goes where it goes.
41787
- releaseHeldPage();
41839
+ releaseHold();
41788
41840
  row.style.setProperty("--navi-route-row-stand", `${-position}px`);
41789
41841
  row.setAttribute("data-route-row-taken", "");
41790
41842
  afterFrames(2, () => {
@@ -41812,14 +41864,11 @@ const RouteTravel = ({
41812
41864
  return;
41813
41865
  }
41814
41866
  movementRef.current = null;
41815
- if (movement.playing) {
41816
- movement.playing.stop();
41817
- }
41867
+ letRowGo(movement);
41818
41868
  putRowBack(movement);
41819
- row.removeAttribute("data-route-row-travelling");
41820
41869
  row.removeAttribute("data-route-row-taken");
41821
41870
  publishRouteRowMove(null);
41822
- releaseHeldPage();
41871
+ releaseHold();
41823
41872
  if (liveRef.current) {
41824
41873
  liveRef.current.style.top = "";
41825
41874
  }
@@ -41993,7 +42042,7 @@ const RouteTravel = ({
41993
42042
  // Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
41994
42043
  // and the travel lets it go, snapping included, as the browser's own scroll does.
41995
42044
  if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
41996
- moving.playing.stop();
42045
+ letRowGo(moving);
41997
42046
  }
41998
42047
  const pane = panes[currentIndex];
41999
42048
  if (!moving && pane && restsOn(row, pane, ROW_READING)) {
@@ -42039,8 +42088,12 @@ const RouteTravel = ({
42039
42088
  return;
42040
42089
  }
42041
42090
  leavePictureOfLive(movement);
42042
- } else if (paneTowards.hasAttribute("data-built")) {
42043
- return;
42091
+ } else {
42092
+ if (paneTowards.hasAttribute("data-built")) {
42093
+ return;
42094
+ }
42095
+ // Before the address moves: the render that makes it inert must find it held.
42096
+ holdPaneOnScreen(pane);
42044
42097
  }
42045
42098
  holdPage(pages[currentIndex], page);
42046
42099
  askFor(page);
@@ -42072,7 +42125,7 @@ const RouteTravel = ({
42072
42125
  }
42073
42126
  // After the address: a pull coming back to its page must not light the tab the
42074
42127
  // address ran ahead to, even for a render.
42075
- releaseHeldPage();
42128
+ releaseHold();
42076
42129
  };
42077
42130
  const stopWatching = watchRowScroll(row, {
42078
42131
  onScroll,
@@ -42122,12 +42175,13 @@ const RouteTravel = ({
42122
42175
  });
42123
42176
  const movement = movementRef.current;
42124
42177
  if (movement) {
42125
- movement.playing?.stop();
42178
+ movement.playing?.cancel();
42126
42179
  movement.resumeScrollRecording?.();
42127
42180
  }
42181
+ row.removeAttribute("data-route-row-travelling");
42128
42182
  movementRef.current = null;
42129
42183
  publishRouteRowMove(null);
42130
- releaseHeldPage();
42184
+ releaseHold();
42131
42185
  };
42132
42186
  }, [windowed]);
42133
42187