@jsenv/navi 0.30.33 → 0.30.35

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<{
@@ -42830,9 +42836,23 @@ const RouteTravel = ({
42830
42836
  params
42831
42837
  }) => new URL(route.buildUrl(params), window.location.href).pathname;
42832
42838
 
42839
+ // The pane laid out as the page on screen for the length of a movement, let go of
42840
+ // when the row rests. Not what readers are told the page held is
42841
+ // (route_travel_hold.js): that follows the hand, and goes at its lift.
42842
+ const heldPaneRef = useRef(null);
42843
+ const releasePaneAtRest = () => {
42844
+ const heldPane = heldPaneRef.current;
42845
+ if (heldPane) {
42846
+ heldPaneRef.current = null;
42847
+ heldPane.removeAttribute("data-route-row-held");
42848
+ }
42849
+ };
42850
+
42833
42851
  // One movement, whoever set it off, from the first thing that moves to the row
42834
- // at rest. Null when nothing moves.
42835
- const beginMovement = fields => {
42852
+ // at rest. Null when nothing moves. `paneAtRest` is the pane the row rests on as it
42853
+ // begins: laid out as the page on screen until the row rests again, wherever the
42854
+ // address goes meanwhile (see the CSS).
42855
+ const beginMovement = (fields, paneAtRest) => {
42836
42856
  const row = rowRef.current;
42837
42857
  if (movementRef.current) {
42838
42858
  Object.assign(movementRef.current, fields);
@@ -42847,6 +42867,11 @@ const RouteTravel = ({
42847
42867
  ...fields
42848
42868
  };
42849
42869
  movementRef.current = movement;
42870
+ // In a window the page is a picture, contained with what it holds.
42871
+ if (!windowed && paneAtRest) {
42872
+ heldPaneRef.current = paneAtRest;
42873
+ paneAtRest.setAttribute("data-route-row-held", "");
42874
+ }
42850
42875
  if (movement.scrollsDocument) {
42851
42876
  // Whatever the browser does with the offset meanwhile (clamping it to a page
42852
42877
  // shorter than the trip) is not the reader scrolling, and the address it
@@ -42993,18 +43018,22 @@ const RouteTravel = ({
42993
43018
  // hesitates, then peaks at 1.65 times this one's speed): two rows of tabs in one
42994
43019
  // app would not move alike. The scroll position is written at every frame, so the
42995
43020
  // 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.
43021
+ // reads a hand, and a finger landing finds it where it is. That is played on the
43022
+ // main thread: a transform on the panes would play on the compositor, but it would
43023
+ // capture what the page on screen holds in position: fixed. Hence a clock of its
43024
+ // own, which a busy main thread slows down rather than makes jump.
42998
43025
  const playTravel = (pane, movement) => {
42999
43026
  const row = rowRef.current;
43000
43027
  // Already moving, so there is no ease-in to play: it would stall the row for an
43001
43028
  // instant right where the eye is following it.
43002
43029
  const replaced = movement.playing;
43003
43030
  const inFlight = Boolean(replaced) || Boolean(movement.rowTaken);
43031
+ // Before the row is read: let go of for an instant, it would be snapped to the
43032
+ // nearest page by that read, and the travel would start from there.
43033
+ row.setAttribute("data-route-row-travelling", "");
43004
43034
  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();
43035
+ replaced.cancel();
43036
+ movement.playing = null;
43008
43037
  }
43009
43038
  const from = row.scrollLeft;
43010
43039
  const {
@@ -43016,56 +43045,70 @@ const RouteTravel = ({
43016
43045
  const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
43017
43046
  if (!travelMs) {
43018
43047
  row.scrollLeft = pane.offsetLeft;
43048
+ letRowGo(movement);
43019
43049
  // A write that changes nothing is followed by no scroll event to say the row
43020
43050
  // stopped. After the commit that asked, which goes on to say the row moves.
43021
43051
  Promise.resolve().then(landRow);
43022
43052
  return;
43023
43053
  }
43024
43054
  const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
43025
- let start = null;
43026
43055
  let frame = null;
43056
+ // How much of the travel has played, frame by frame. A travel setting off plays
43057
+ // nothing at its first frame, which may have begun while the press was still being
43058
+ // handled: that is no travel. One taking over goes on from the last frame of the
43059
+ // one it replaces, or the row would stand still for a frame where the eye follows it.
43060
+ let played = 0;
43027
43061
  const playing = {
43028
43062
  // Where this travel put the row, for a scroll event to tell it from a hand.
43029
43063
  written: from,
43030
- cancelFrame: () => {
43064
+ lastFrame: replaced ? replaced.lastFrame : null,
43065
+ cancel: () => {
43031
43066
  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
43067
  }
43040
43068
  };
43041
43069
  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();
43070
+ if (playing.lastFrame !== null) {
43071
+ const gap = now - playing.lastFrame;
43072
+ if (gap > TRAVEL_FRAME_MAX_MS) {
43073
+ played += TRAVEL_FRAME_MAX_MS;
43074
+ } else if (gap > 0) {
43075
+ played += gap;
43076
+ }
43046
43077
  }
43047
- // The next frame may have begun while this first one was still being handled.
43048
- const elapsed = now > start ? (now - start) / travelMs : 0;
43078
+ playing.lastFrame = now;
43079
+ const elapsed = played / travelMs;
43049
43080
  // Read at every frame: the layout may move under the travel (a scrollbar
43050
43081
  // coming in narrows the row), and it goes where the page stands.
43051
43082
  const to = pane.offsetLeft;
43052
43083
  const arrived = elapsed >= 1;
43053
43084
  playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
43054
43085
  row.scrollLeft = playing.written;
43086
+ // In the frame it is written: the scroll event saying so comes a frame later,
43087
+ // and what follows the row would be drawn a frame behind it.
43088
+ sayRowMoves();
43055
43089
  if (!arrived) {
43056
43090
  frame = requestAnimationFrame(step);
43057
43091
  return;
43058
43092
  }
43059
- playing.stop();
43093
+ letRowGo(movement);
43060
43094
  // Said here rather than waited for: the last frames of a curve can move the row
43061
43095
  // by less than a pixel, and nothing then says it has stopped.
43062
43096
  landRow();
43063
43097
  };
43064
43098
  movement.playing = playing;
43065
- row.setAttribute("data-route-row-travelling", "");
43066
43099
  frame = requestAnimationFrame(step);
43067
43100
  };
43068
43101
 
43102
+ // The row given back to the browser: no travel writes it any more, and it snaps
43103
+ // again. A travel replacing another keeps it (playTravel).
43104
+ const letRowGo = movement => {
43105
+ if (movement.playing) {
43106
+ movement.playing.cancel();
43107
+ movement.playing = null;
43108
+ }
43109
+ rowRef.current.removeAttribute("data-route-row-travelling");
43110
+ };
43111
+
43069
43112
  // Every column back where the map says, and the row on the page of the address, in
43070
43113
  // one go: that page stands at the same place on screen either way.
43071
43114
  const putRowBack = movement => {
@@ -43122,14 +43165,12 @@ const RouteTravel = ({
43122
43165
  return;
43123
43166
  }
43124
43167
  movementRef.current = null;
43125
- if (movement.playing) {
43126
- movement.playing.stop();
43127
- }
43168
+ letRowGo(movement);
43128
43169
  putRowBack(movement);
43129
- row.removeAttribute("data-route-row-travelling");
43130
43170
  row.removeAttribute("data-route-row-taken");
43131
43171
  publishRouteRowMove(null);
43132
43172
  releaseHeldPage();
43173
+ releasePaneAtRest();
43133
43174
  if (liveRef.current) {
43134
43175
  liveRef.current.style.top = "";
43135
43176
  }
@@ -43242,9 +43283,11 @@ const RouteTravel = ({
43242
43283
  return;
43243
43284
  }
43244
43285
  const movementBefore = movementRef.current;
43286
+ // The row still rests on the page answered before. Held in the commit that made
43287
+ // its pane inert, so no frame paints it contained.
43245
43288
  const movement = beginMovement({
43246
43289
  travelling: true
43247
- });
43290
+ }, panesOf(row)[pages.findIndex(each => samePage(each, answered))]);
43248
43291
  if (movementBefore?.byHand && !movementBefore.rowTaken) {
43249
43292
  sayRowMoves();
43250
43293
  takeRowFromHand(movement);
@@ -43303,7 +43346,7 @@ const RouteTravel = ({
43303
43346
  // Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
43304
43347
  // and the travel lets it go, snapping included, as the browser's own scroll does.
43305
43348
  if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
43306
- moving.playing.stop();
43349
+ letRowGo(moving);
43307
43350
  }
43308
43351
  const pane = panes[currentIndex];
43309
43352
  if (!moving && pane && restsOn(row, pane, ROW_READING)) {
@@ -43312,7 +43355,7 @@ const RouteTravel = ({
43312
43355
  }
43313
43356
  const movement = moving || beginMovement({
43314
43357
  byHand: true
43315
- });
43358
+ }, pane);
43316
43359
  sayRowMoves();
43317
43360
  if (movement.travelling || !travelByDrag || !pane) {
43318
43361
  return;
@@ -43381,7 +43424,8 @@ const RouteTravel = ({
43381
43424
  askForPageOfRow(index, movement);
43382
43425
  }
43383
43426
  // After the address: a pull coming back to its page must not light the tab the
43384
- // address ran ahead to, even for a render.
43427
+ // address ran ahead to, even for a render. Only what readers are told: the
43428
+ // panes keep their layout until the row rests.
43385
43429
  releaseHeldPage();
43386
43430
  };
43387
43431
  const stopWatching = watchRowScroll(row, {
@@ -43432,12 +43476,14 @@ const RouteTravel = ({
43432
43476
  });
43433
43477
  const movement = movementRef.current;
43434
43478
  if (movement) {
43435
- movement.playing?.stop();
43479
+ movement.playing?.cancel();
43436
43480
  movement.resumeScrollRecording?.();
43437
43481
  }
43482
+ row.removeAttribute("data-route-row-travelling");
43438
43483
  movementRef.current = null;
43439
43484
  publishRouteRowMove(null);
43440
43485
  releaseHeldPage();
43486
+ releasePaneAtRest();
43441
43487
  };
43442
43488
  }, [windowed]);
43443
43489