@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.
@@ -14441,7 +14441,9 @@ const isRenderingPageChange = () => renderingPageChange;
14441
14441
  * outside its own nodes (a slot in a bar, the document a list scrolls) steps
14442
14442
  * out rather than showing with, or moving, the page arriving. Every box of it
14443
14443
  * measures 0×0 by then. Set after the render keeping the page, so that render
14444
- * and what it builds can only ask `hasLeft()`.
14444
+ * and what it builds can only ask `hasLeft()`. Cleared by the render showing
14445
+ * the page again, before anything below it renders: what mounts then never
14446
+ * reads its page as leaving.
14445
14447
  */
14446
14448
  const RoutePageContext = createContext(null);
14447
14449
 
@@ -38547,8 +38549,16 @@ const RouteRowPane = ({
38547
38549
  value: page
38548
38550
  }, branch.node));
38549
38551
  }
38552
+ // Back on screen before what it holds renders: built here, or mounted again under
38553
+ // it, that content runs its layout effects before this component's, and would
38554
+ // otherwise read its page as leaving.
38555
+ if (shown) {
38556
+ page.ownLeavingSignal.value = false;
38557
+ }
38550
38558
  useLayoutEffect(() => {
38551
- page.ownLeavingSignal.value = !shown;
38559
+ if (!shown) {
38560
+ page.ownLeavingSignal.value = true;
38561
+ }
38552
38562
  }, [shown]);
38553
38563
  return jsx("div", {
38554
38564
  "data-route-row-pane": "",
@@ -42658,7 +42668,7 @@ const css$16 = /* css */`.navi_route_travel {
42658
42668
  display: flex;
42659
42669
  }
42660
42670
 
42661
- & > [data-route-row-pane][inert] {
42671
+ & > [data-route-row-pane][inert]:not([data-route-row-held]), &:has( > [data-route-row-held]) > [data-route-row-pane]:not([data-route-row-held]) {
42662
42672
  top: var(--navi-route-row-pin, 0);
42663
42673
  contain: paint;
42664
42674
  max-height: 100dvh;
@@ -42708,6 +42718,12 @@ const ROW_READING = {
42708
42718
  // How far a scroll position written can read back from what was written: rounded to
42709
42719
  // the device's pixels.
42710
42720
  const WRITE_ROUNDING = 1;
42721
+ // The most one frame of a travel plays: a frame of a 30fps screen, which is what a
42722
+ // browser saving power gives (an iPhone in Low Power Mode, Chrome's Energy Saver).
42723
+ // A frame later than that is a main thread busy elsewhere (the page arriving builds,
42724
+ // a list fills its budget), and the travel takes longer rather than jump by it. Not a
42725
+ // frame of a 60Hz screen: that would play every travel in slow motion at 30fps.
42726
+ const TRAVEL_FRAME_MAX_MS = 1000 / 30;
42711
42727
 
42712
42728
  /**
42713
42729
  * @type {import("preact").FunctionComponent<{
@@ -42983,18 +42999,22 @@ const RouteTravel = ({
42983
42999
  // hesitates, then peaks at 1.65 times this one's speed): two rows of tabs in one
42984
43000
  // app would not move alike. The scroll position is written at every frame, so the
42985
43001
  // row stays a scroller all the way: what follows it (a Nav's bar) reads it as it
42986
- // reads a hand, and a finger landing finds it where it is. The clock starts at the
42987
- // first frame, after what the press itself costs, as an animation's does.
43002
+ // reads a hand, and a finger landing finds it where it is. That is played on the
43003
+ // main thread: a transform on the panes would play on the compositor, but it would
43004
+ // capture what the page on screen holds in position: fixed. Hence a clock of its
43005
+ // own, which a busy main thread slows down rather than makes jump.
42988
43006
  const playTravel = (pane, movement) => {
42989
43007
  const row = rowRef.current;
42990
43008
  // Already moving, so there is no ease-in to play: it would stall the row for an
42991
43009
  // instant right where the eye is following it.
42992
43010
  const replaced = movement.playing;
42993
43011
  const inFlight = Boolean(replaced) || Boolean(movement.rowTaken);
43012
+ // Before the row is read: let go of for an instant, it would be snapped to the
43013
+ // nearest page by that read, and the travel would start from there.
43014
+ row.setAttribute("data-route-row-travelling", "");
42994
43015
  if (replaced) {
42995
- // Its frames only: the row stays travelling across the hand-over. Let go of,
42996
- // it snaps to the nearest page at the read below, and the travel starts there.
42997
- replaced.cancelFrame();
43016
+ replaced.cancel();
43017
+ movement.playing = null;
42998
43018
  }
42999
43019
  const from = row.scrollLeft;
43000
43020
  const {
@@ -43006,56 +43026,88 @@ const RouteTravel = ({
43006
43026
  const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
43007
43027
  if (!travelMs) {
43008
43028
  row.scrollLeft = pane.offsetLeft;
43029
+ letRowGo(movement);
43009
43030
  // A write that changes nothing is followed by no scroll event to say the row
43010
43031
  // stopped. After the commit that asked, which goes on to say the row moves.
43011
43032
  Promise.resolve().then(landRow);
43012
43033
  return;
43013
43034
  }
43014
43035
  const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
43015
- let start = null;
43016
43036
  let frame = null;
43037
+ // How much of the travel has played, frame by frame. A travel setting off plays
43038
+ // nothing at its first frame, which may have begun while the press was still being
43039
+ // handled: that is no travel. One taking over goes on from the last frame of the
43040
+ // one it replaces, or the row would stand still for a frame where the eye follows it.
43041
+ let played = 0;
43017
43042
  const playing = {
43018
43043
  // Where this travel put the row, for a scroll event to tell it from a hand.
43019
43044
  written: from,
43020
- cancelFrame: () => {
43045
+ lastFrame: replaced ? replaced.lastFrame : null,
43046
+ cancel: () => {
43021
43047
  cancelAnimationFrame(frame);
43022
- },
43023
- stop: () => {
43024
- playing.cancelFrame();
43025
- if (movement.playing === playing) {
43026
- movement.playing = null;
43027
- row.removeAttribute("data-route-row-travelling");
43028
- }
43029
43048
  }
43030
43049
  };
43031
43050
  const step = now => {
43032
- if (start === null) {
43033
- // When this frame runs, not when it began: under load it began while the
43034
- // press was still being handled, and that time is no travel.
43035
- start = performance.now();
43051
+ if (playing.lastFrame !== null) {
43052
+ const gap = now - playing.lastFrame;
43053
+ if (gap > TRAVEL_FRAME_MAX_MS) {
43054
+ played += TRAVEL_FRAME_MAX_MS;
43055
+ } else if (gap > 0) {
43056
+ played += gap;
43057
+ }
43036
43058
  }
43037
- // The next frame may have begun while this first one was still being handled.
43038
- const elapsed = now > start ? (now - start) / travelMs : 0;
43059
+ playing.lastFrame = now;
43060
+ const elapsed = played / travelMs;
43039
43061
  // Read at every frame: the layout may move under the travel (a scrollbar
43040
43062
  // coming in narrows the row), and it goes where the page stands.
43041
43063
  const to = pane.offsetLeft;
43042
43064
  const arrived = elapsed >= 1;
43043
43065
  playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
43044
43066
  row.scrollLeft = playing.written;
43067
+ // In the frame it is written: the scroll event saying so comes a frame later,
43068
+ // and what follows the row would be drawn a frame behind it.
43069
+ sayRowMoves();
43045
43070
  if (!arrived) {
43046
43071
  frame = requestAnimationFrame(step);
43047
43072
  return;
43048
43073
  }
43049
- playing.stop();
43074
+ letRowGo(movement);
43050
43075
  // Said here rather than waited for: the last frames of a curve can move the row
43051
43076
  // by less than a pixel, and nothing then says it has stopped.
43052
43077
  landRow();
43053
43078
  };
43054
43079
  movement.playing = playing;
43055
- row.setAttribute("data-route-row-travelling", "");
43056
43080
  frame = requestAnimationFrame(step);
43057
43081
  };
43058
43082
 
43083
+ // The row given back to the browser: no travel writes it any more, and it snaps
43084
+ // again. A travel replacing another keeps it (playTravel).
43085
+ const letRowGo = movement => {
43086
+ if (movement.playing) {
43087
+ movement.playing.cancel();
43088
+ movement.playing = null;
43089
+ }
43090
+ rowRef.current.removeAttribute("data-route-row-travelling");
43091
+ };
43092
+
43093
+ // The address runs ahead of a hand to build the page it pulls towards (onScroll):
43094
+ // the pane under the hand stays laid out as the page on screen until the hand lets
43095
+ // go, whatever `inert` says (see the CSS). What reads the page held is told apart
43096
+ // (route_travel_hold.js), and both are let go of together.
43097
+ const heldPaneRef = useRef(null);
43098
+ const holdPaneOnScreen = pane => {
43099
+ heldPaneRef.current = pane;
43100
+ pane.setAttribute("data-route-row-held", "");
43101
+ };
43102
+ const releaseHold = () => {
43103
+ releaseHeldPage();
43104
+ const heldPane = heldPaneRef.current;
43105
+ if (heldPane) {
43106
+ heldPaneRef.current = null;
43107
+ heldPane.removeAttribute("data-route-row-held");
43108
+ }
43109
+ };
43110
+
43059
43111
  // Every column back where the map says, and the row on the page of the address, in
43060
43112
  // one go: that page stands at the same place on screen either way.
43061
43113
  const putRowBack = movement => {
@@ -43084,7 +43136,7 @@ const RouteTravel = ({
43084
43136
  const position = row.scrollLeft;
43085
43137
  movement.rowTaken = true;
43086
43138
  // A travel no hand set off holds nothing: it goes where it goes.
43087
- releaseHeldPage();
43139
+ releaseHold();
43088
43140
  row.style.setProperty("--navi-route-row-stand", `${-position}px`);
43089
43141
  row.setAttribute("data-route-row-taken", "");
43090
43142
  afterFrames(2, () => {
@@ -43112,14 +43164,11 @@ const RouteTravel = ({
43112
43164
  return;
43113
43165
  }
43114
43166
  movementRef.current = null;
43115
- if (movement.playing) {
43116
- movement.playing.stop();
43117
- }
43167
+ letRowGo(movement);
43118
43168
  putRowBack(movement);
43119
- row.removeAttribute("data-route-row-travelling");
43120
43169
  row.removeAttribute("data-route-row-taken");
43121
43170
  publishRouteRowMove(null);
43122
- releaseHeldPage();
43171
+ releaseHold();
43123
43172
  if (liveRef.current) {
43124
43173
  liveRef.current.style.top = "";
43125
43174
  }
@@ -43293,7 +43342,7 @@ const RouteTravel = ({
43293
43342
  // Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
43294
43343
  // and the travel lets it go, snapping included, as the browser's own scroll does.
43295
43344
  if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
43296
- moving.playing.stop();
43345
+ letRowGo(moving);
43297
43346
  }
43298
43347
  const pane = panes[currentIndex];
43299
43348
  if (!moving && pane && restsOn(row, pane, ROW_READING)) {
@@ -43339,8 +43388,12 @@ const RouteTravel = ({
43339
43388
  return;
43340
43389
  }
43341
43390
  leavePictureOfLive(movement);
43342
- } else if (paneTowards.hasAttribute("data-built")) {
43343
- return;
43391
+ } else {
43392
+ if (paneTowards.hasAttribute("data-built")) {
43393
+ return;
43394
+ }
43395
+ // Before the address moves: the render that makes it inert must find it held.
43396
+ holdPaneOnScreen(pane);
43344
43397
  }
43345
43398
  holdPage(pages[currentIndex], page);
43346
43399
  askFor(page);
@@ -43372,7 +43425,7 @@ const RouteTravel = ({
43372
43425
  }
43373
43426
  // After the address: a pull coming back to its page must not light the tab the
43374
43427
  // address ran ahead to, even for a render.
43375
- releaseHeldPage();
43428
+ releaseHold();
43376
43429
  };
43377
43430
  const stopWatching = watchRowScroll(row, {
43378
43431
  onScroll,
@@ -43422,12 +43475,13 @@ const RouteTravel = ({
43422
43475
  });
43423
43476
  const movement = movementRef.current;
43424
43477
  if (movement) {
43425
- movement.playing?.stop();
43478
+ movement.playing?.cancel();
43426
43479
  movement.resumeScrollRecording?.();
43427
43480
  }
43481
+ row.removeAttribute("data-route-row-travelling");
43428
43482
  movementRef.current = null;
43429
43483
  publishRouteRowMove(null);
43430
- releaseHeldPage();
43484
+ releaseHold();
43431
43485
  };
43432
43486
  }, [windowed]);
43433
43487