@jsenv/navi 0.30.33 → 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.
@@ -41368,7 +41368,7 @@ const css$16 = /* css */`.navi_route_travel {
41368
41368
  display: flex;
41369
41369
  }
41370
41370
 
41371
- & > [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]) {
41372
41372
  top: var(--navi-route-row-pin, 0);
41373
41373
  contain: paint;
41374
41374
  max-height: 100dvh;
@@ -41418,6 +41418,12 @@ const ROW_READING = {
41418
41418
  // How far a scroll position written can read back from what was written: rounded to
41419
41419
  // the device's pixels.
41420
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;
41421
41427
 
41422
41428
  /**
41423
41429
  * @type {import("preact").FunctionComponent<{
@@ -41693,18 +41699,22 @@ const RouteTravel = ({
41693
41699
  // hesitates, then peaks at 1.65 times this one's speed): two rows of tabs in one
41694
41700
  // app would not move alike. The scroll position is written at every frame, so the
41695
41701
  // row stays a scroller all the way: what follows it (a Nav's bar) reads it as it
41696
- // reads a hand, and a finger landing finds it where it is. The clock starts at the
41697
- // 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.
41698
41706
  const playTravel = (pane, movement) => {
41699
41707
  const row = rowRef.current;
41700
41708
  // Already moving, so there is no ease-in to play: it would stall the row for an
41701
41709
  // instant right where the eye is following it.
41702
41710
  const replaced = movement.playing;
41703
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", "");
41704
41715
  if (replaced) {
41705
- // Its frames only: the row stays travelling across the hand-over. Let go of,
41706
- // it snaps to the nearest page at the read below, and the travel starts there.
41707
- replaced.cancelFrame();
41716
+ replaced.cancel();
41717
+ movement.playing = null;
41708
41718
  }
41709
41719
  const from = row.scrollLeft;
41710
41720
  const {
@@ -41716,56 +41726,88 @@ const RouteTravel = ({
41716
41726
  const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
41717
41727
  if (!travelMs) {
41718
41728
  row.scrollLeft = pane.offsetLeft;
41729
+ letRowGo(movement);
41719
41730
  // A write that changes nothing is followed by no scroll event to say the row
41720
41731
  // stopped. After the commit that asked, which goes on to say the row moves.
41721
41732
  Promise.resolve().then(landRow);
41722
41733
  return;
41723
41734
  }
41724
41735
  const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
41725
- let start = null;
41726
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;
41727
41742
  const playing = {
41728
41743
  // Where this travel put the row, for a scroll event to tell it from a hand.
41729
41744
  written: from,
41730
- cancelFrame: () => {
41745
+ lastFrame: replaced ? replaced.lastFrame : null,
41746
+ cancel: () => {
41731
41747
  cancelAnimationFrame(frame);
41732
- },
41733
- stop: () => {
41734
- playing.cancelFrame();
41735
- if (movement.playing === playing) {
41736
- movement.playing = null;
41737
- row.removeAttribute("data-route-row-travelling");
41738
- }
41739
41748
  }
41740
41749
  };
41741
41750
  const step = now => {
41742
- if (start === null) {
41743
- // When this frame runs, not when it began: under load it began while the
41744
- // press was still being handled, and that time is no travel.
41745
- 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
+ }
41746
41758
  }
41747
- // The next frame may have begun while this first one was still being handled.
41748
- const elapsed = now > start ? (now - start) / travelMs : 0;
41759
+ playing.lastFrame = now;
41760
+ const elapsed = played / travelMs;
41749
41761
  // Read at every frame: the layout may move under the travel (a scrollbar
41750
41762
  // coming in narrows the row), and it goes where the page stands.
41751
41763
  const to = pane.offsetLeft;
41752
41764
  const arrived = elapsed >= 1;
41753
41765
  playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
41754
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();
41755
41770
  if (!arrived) {
41756
41771
  frame = requestAnimationFrame(step);
41757
41772
  return;
41758
41773
  }
41759
- playing.stop();
41774
+ letRowGo(movement);
41760
41775
  // Said here rather than waited for: the last frames of a curve can move the row
41761
41776
  // by less than a pixel, and nothing then says it has stopped.
41762
41777
  landRow();
41763
41778
  };
41764
41779
  movement.playing = playing;
41765
- row.setAttribute("data-route-row-travelling", "");
41766
41780
  frame = requestAnimationFrame(step);
41767
41781
  };
41768
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
+
41769
41811
  // Every column back where the map says, and the row on the page of the address, in
41770
41812
  // one go: that page stands at the same place on screen either way.
41771
41813
  const putRowBack = movement => {
@@ -41794,7 +41836,7 @@ const RouteTravel = ({
41794
41836
  const position = row.scrollLeft;
41795
41837
  movement.rowTaken = true;
41796
41838
  // A travel no hand set off holds nothing: it goes where it goes.
41797
- releaseHeldPage();
41839
+ releaseHold();
41798
41840
  row.style.setProperty("--navi-route-row-stand", `${-position}px`);
41799
41841
  row.setAttribute("data-route-row-taken", "");
41800
41842
  afterFrames(2, () => {
@@ -41822,14 +41864,11 @@ const RouteTravel = ({
41822
41864
  return;
41823
41865
  }
41824
41866
  movementRef.current = null;
41825
- if (movement.playing) {
41826
- movement.playing.stop();
41827
- }
41867
+ letRowGo(movement);
41828
41868
  putRowBack(movement);
41829
- row.removeAttribute("data-route-row-travelling");
41830
41869
  row.removeAttribute("data-route-row-taken");
41831
41870
  publishRouteRowMove(null);
41832
- releaseHeldPage();
41871
+ releaseHold();
41833
41872
  if (liveRef.current) {
41834
41873
  liveRef.current.style.top = "";
41835
41874
  }
@@ -42003,7 +42042,7 @@ const RouteTravel = ({
42003
42042
  // Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
42004
42043
  // and the travel lets it go, snapping included, as the browser's own scroll does.
42005
42044
  if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
42006
- moving.playing.stop();
42045
+ letRowGo(moving);
42007
42046
  }
42008
42047
  const pane = panes[currentIndex];
42009
42048
  if (!moving && pane && restsOn(row, pane, ROW_READING)) {
@@ -42049,8 +42088,12 @@ const RouteTravel = ({
42049
42088
  return;
42050
42089
  }
42051
42090
  leavePictureOfLive(movement);
42052
- } else if (paneTowards.hasAttribute("data-built")) {
42053
- 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);
42054
42097
  }
42055
42098
  holdPage(pages[currentIndex], page);
42056
42099
  askFor(page);
@@ -42082,7 +42125,7 @@ const RouteTravel = ({
42082
42125
  }
42083
42126
  // After the address: a pull coming back to its page must not light the tab the
42084
42127
  // address ran ahead to, even for a render.
42085
- releaseHeldPage();
42128
+ releaseHold();
42086
42129
  };
42087
42130
  const stopWatching = watchRowScroll(row, {
42088
42131
  onScroll,
@@ -42132,12 +42175,13 @@ const RouteTravel = ({
42132
42175
  });
42133
42176
  const movement = movementRef.current;
42134
42177
  if (movement) {
42135
- movement.playing?.stop();
42178
+ movement.playing?.cancel();
42136
42179
  movement.resumeScrollRecording?.();
42137
42180
  }
42181
+ row.removeAttribute("data-route-row-travelling");
42138
42182
  movementRef.current = null;
42139
42183
  publishRouteRowMove(null);
42140
- releaseHeldPage();
42184
+ releaseHold();
42141
42185
  };
42142
42186
  }, [windowed]);
42143
42187