@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.
@@ -42668,7 +42668,7 @@ const css$16 = /* css */`.navi_route_travel {
42668
42668
  display: flex;
42669
42669
  }
42670
42670
 
42671
- & > [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]) {
42672
42672
  top: var(--navi-route-row-pin, 0);
42673
42673
  contain: paint;
42674
42674
  max-height: 100dvh;
@@ -42718,6 +42718,12 @@ const ROW_READING = {
42718
42718
  // How far a scroll position written can read back from what was written: rounded to
42719
42719
  // the device's pixels.
42720
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;
42721
42727
 
42722
42728
  /**
42723
42729
  * @type {import("preact").FunctionComponent<{
@@ -42993,18 +42999,22 @@ const RouteTravel = ({
42993
42999
  // hesitates, then peaks at 1.65 times this one's speed): two rows of tabs in one
42994
43000
  // app would not move alike. The scroll position is written at every frame, so the
42995
43001
  // row stays a scroller all the way: what follows it (a Nav's bar) reads it as it
42996
- // reads a hand, and a finger landing finds it where it is. The clock starts at the
42997
- // 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.
42998
43006
  const playTravel = (pane, movement) => {
42999
43007
  const row = rowRef.current;
43000
43008
  // Already moving, so there is no ease-in to play: it would stall the row for an
43001
43009
  // instant right where the eye is following it.
43002
43010
  const replaced = movement.playing;
43003
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", "");
43004
43015
  if (replaced) {
43005
- // Its frames only: the row stays travelling across the hand-over. Let go of,
43006
- // it snaps to the nearest page at the read below, and the travel starts there.
43007
- replaced.cancelFrame();
43016
+ replaced.cancel();
43017
+ movement.playing = null;
43008
43018
  }
43009
43019
  const from = row.scrollLeft;
43010
43020
  const {
@@ -43016,56 +43026,88 @@ const RouteTravel = ({
43016
43026
  const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
43017
43027
  if (!travelMs) {
43018
43028
  row.scrollLeft = pane.offsetLeft;
43029
+ letRowGo(movement);
43019
43030
  // A write that changes nothing is followed by no scroll event to say the row
43020
43031
  // stopped. After the commit that asked, which goes on to say the row moves.
43021
43032
  Promise.resolve().then(landRow);
43022
43033
  return;
43023
43034
  }
43024
43035
  const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
43025
- let start = null;
43026
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;
43027
43042
  const playing = {
43028
43043
  // Where this travel put the row, for a scroll event to tell it from a hand.
43029
43044
  written: from,
43030
- cancelFrame: () => {
43045
+ lastFrame: replaced ? replaced.lastFrame : null,
43046
+ cancel: () => {
43031
43047
  cancelAnimationFrame(frame);
43032
- },
43033
- stop: () => {
43034
- playing.cancelFrame();
43035
- if (movement.playing === playing) {
43036
- movement.playing = null;
43037
- row.removeAttribute("data-route-row-travelling");
43038
- }
43039
43048
  }
43040
43049
  };
43041
43050
  const step = now => {
43042
- if (start === null) {
43043
- // When this frame runs, not when it began: under load it began while the
43044
- // press was still being handled, and that time is no travel.
43045
- 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
+ }
43046
43058
  }
43047
- // The next frame may have begun while this first one was still being handled.
43048
- const elapsed = now > start ? (now - start) / travelMs : 0;
43059
+ playing.lastFrame = now;
43060
+ const elapsed = played / travelMs;
43049
43061
  // Read at every frame: the layout may move under the travel (a scrollbar
43050
43062
  // coming in narrows the row), and it goes where the page stands.
43051
43063
  const to = pane.offsetLeft;
43052
43064
  const arrived = elapsed >= 1;
43053
43065
  playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
43054
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();
43055
43070
  if (!arrived) {
43056
43071
  frame = requestAnimationFrame(step);
43057
43072
  return;
43058
43073
  }
43059
- playing.stop();
43074
+ letRowGo(movement);
43060
43075
  // Said here rather than waited for: the last frames of a curve can move the row
43061
43076
  // by less than a pixel, and nothing then says it has stopped.
43062
43077
  landRow();
43063
43078
  };
43064
43079
  movement.playing = playing;
43065
- row.setAttribute("data-route-row-travelling", "");
43066
43080
  frame = requestAnimationFrame(step);
43067
43081
  };
43068
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
+
43069
43111
  // Every column back where the map says, and the row on the page of the address, in
43070
43112
  // one go: that page stands at the same place on screen either way.
43071
43113
  const putRowBack = movement => {
@@ -43094,7 +43136,7 @@ const RouteTravel = ({
43094
43136
  const position = row.scrollLeft;
43095
43137
  movement.rowTaken = true;
43096
43138
  // A travel no hand set off holds nothing: it goes where it goes.
43097
- releaseHeldPage();
43139
+ releaseHold();
43098
43140
  row.style.setProperty("--navi-route-row-stand", `${-position}px`);
43099
43141
  row.setAttribute("data-route-row-taken", "");
43100
43142
  afterFrames(2, () => {
@@ -43122,14 +43164,11 @@ const RouteTravel = ({
43122
43164
  return;
43123
43165
  }
43124
43166
  movementRef.current = null;
43125
- if (movement.playing) {
43126
- movement.playing.stop();
43127
- }
43167
+ letRowGo(movement);
43128
43168
  putRowBack(movement);
43129
- row.removeAttribute("data-route-row-travelling");
43130
43169
  row.removeAttribute("data-route-row-taken");
43131
43170
  publishRouteRowMove(null);
43132
- releaseHeldPage();
43171
+ releaseHold();
43133
43172
  if (liveRef.current) {
43134
43173
  liveRef.current.style.top = "";
43135
43174
  }
@@ -43303,7 +43342,7 @@ const RouteTravel = ({
43303
43342
  // Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
43304
43343
  // and the travel lets it go, snapping included, as the browser's own scroll does.
43305
43344
  if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
43306
- moving.playing.stop();
43345
+ letRowGo(moving);
43307
43346
  }
43308
43347
  const pane = panes[currentIndex];
43309
43348
  if (!moving && pane && restsOn(row, pane, ROW_READING)) {
@@ -43349,8 +43388,12 @@ const RouteTravel = ({
43349
43388
  return;
43350
43389
  }
43351
43390
  leavePictureOfLive(movement);
43352
- } else if (paneTowards.hasAttribute("data-built")) {
43353
- 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);
43354
43397
  }
43355
43398
  holdPage(pages[currentIndex], page);
43356
43399
  askFor(page);
@@ -43382,7 +43425,7 @@ const RouteTravel = ({
43382
43425
  }
43383
43426
  // After the address: a pull coming back to its page must not light the tab the
43384
43427
  // address ran ahead to, even for a render.
43385
- releaseHeldPage();
43428
+ releaseHold();
43386
43429
  };
43387
43430
  const stopWatching = watchRowScroll(row, {
43388
43431
  onScroll,
@@ -43432,12 +43475,13 @@ const RouteTravel = ({
43432
43475
  });
43433
43476
  const movement = movementRef.current;
43434
43477
  if (movement) {
43435
- movement.playing?.stop();
43478
+ movement.playing?.cancel();
43436
43479
  movement.resumeScrollRecording?.();
43437
43480
  }
43481
+ row.removeAttribute("data-route-row-travelling");
43438
43482
  movementRef.current = null;
43439
43483
  publishRouteRowMove(null);
43440
- releaseHeldPage();
43484
+ releaseHold();
43441
43485
  };
43442
43486
  }, [windowed]);
43443
43487