@jsenv/navi 0.30.6 → 0.30.8

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.
@@ -40388,17 +40388,19 @@ const STOPPED_SILENCE = 150;
40388
40388
  const isOnRow = (page) =>
40389
40389
  page.offsetWidth > 0 && !page.hasAttribute("data-off-row");
40390
40390
 
40391
- const restsOn = (scroller, page) =>
40391
+ // `sideways`: see readRowStretch.
40392
+ const restsOn = (scroller, page, { sideways } = {}) =>
40392
40393
  Math.abs(page.offsetLeft - scroller.scrollLeft) <= AT_REST_DISTANCE &&
40393
- Math.abs(page.offsetTop - scroller.scrollTop) <= AT_REST_DISTANCE;
40394
+ (sideways ||
40395
+ Math.abs(page.offsetTop - scroller.scrollTop) <= AT_REST_DISTANCE);
40394
40396
 
40395
40397
  /**
40396
40398
  * @returns {Element|undefined} the page the row rests on, nothing while it stands
40397
40399
  * between two: a hand can pause midway, and an engine may call that the end of
40398
40400
  * a scroll.
40399
40401
  */
40400
- const pageAtRest = (scroller, pages) =>
40401
- pages.find((page) => isOnRow(page) && restsOn(scroller, page));
40402
+ const pageAtRest = (scroller, pages, options) =>
40403
+ pages.find((page) => isOnRow(page) && restsOn(scroller, page, options));
40402
40404
 
40403
40405
  /**
40404
40406
  * Where the row stands between two neighbouring pages, on the side it has moved
@@ -40406,14 +40408,18 @@ const pageAtRest = (scroller, pages) =>
40406
40408
  * since is walked over. A map moves along one axis at a time, the one it has
40407
40409
  * moved furthest on.
40408
40410
  *
40411
+ * @param {object} [options]
40412
+ * @param {boolean} [options.sideways] - a row with one line, whatever `offsetTop` its
40413
+ * pages read: a page pinned with `top` is still on it.
40409
40414
  * @returns {{base: Element, toward: Element|undefined, fraction: number, forward: boolean}}
40410
40415
  * the last page the row has passed (`page` itself until then), the one after,
40411
40416
  * and how far between the two from 0 to 1. Nothing `toward` past the last.
40412
40417
  */
40413
- const readRowStretch = (scroller, pages, page) => {
40418
+ const readRowStretch = (scroller, pages, page, { sideways } = {}) => {
40414
40419
  const alongX =
40420
+ sideways ||
40415
40421
  Math.abs(scroller.scrollLeft - page.offsetLeft) >=
40416
- Math.abs(scroller.scrollTop - page.offsetTop);
40422
+ Math.abs(scroller.scrollTop - page.offsetTop);
40417
40423
  const position = alongX ? "offsetLeft" : "offsetTop";
40418
40424
  const cross = alongX ? "offsetTop" : "offsetLeft";
40419
40425
  const scrolled = alongX ? scroller.scrollLeft : scroller.scrollTop;
@@ -40421,7 +40427,8 @@ const readRowStretch = (scroller, pages, page) => {
40421
40427
  const step = forward ? 1 : -1;
40422
40428
  const line = pages
40423
40429
  .filter(
40424
- (candidate) => isOnRow(candidate) && candidate[cross] === page[cross],
40430
+ (candidate) =>
40431
+ isOnRow(candidate) && (sideways || candidate[cross] === page[cross]),
40425
40432
  )
40426
40433
  .sort((pageA, pageB) => pageA[position] - pageB[position]);
40427
40434
  let index = line.indexOf(page);
@@ -40628,6 +40635,11 @@ const css$16 = /* css */`.navi_route_travel {
40628
40635
  // there yet: past what a hand that only shook covers, so that a shake costs
40629
40636
  // neither a navigation nor the requests of a page.
40630
40637
  const SET_OFF_PULL = 16;
40638
+ // A row of routes is one line, whatever `offsetTop` its pinned panes read (see the pin
40639
+ // above): they are on it all the same.
40640
+ const ROW_READING = {
40641
+ sideways: true
40642
+ };
40631
40643
 
40632
40644
  /**
40633
40645
  * @type {import("preact").FunctionComponent<{
@@ -40801,10 +40813,7 @@ const RouteTravel = ({
40801
40813
  const paneCurrent = panesOf(row)[currentIndex];
40802
40814
  if (paneCurrent) {
40803
40815
  movement.travelling = true;
40804
- row.scrollTo({
40805
- left: paneCurrent.offsetLeft,
40806
- behavior: "smooth"
40807
- });
40816
+ travelRowTo(currentIndex, movement);
40808
40817
  }
40809
40818
  });
40810
40819
  };
@@ -40821,7 +40830,7 @@ const RouteTravel = ({
40821
40830
  base,
40822
40831
  toward,
40823
40832
  fraction
40824
- } = readRowStretch(row, panes, panes[0]);
40833
+ } = readRowFrame(row, panes);
40825
40834
  publishRouteRowMove({
40826
40835
  basePath: pathOf(pages[panes.indexOf(base)]),
40827
40836
  towardPath: toward ? pathOf(pages[panes.indexOf(toward)]) : null,
@@ -40829,6 +40838,81 @@ const RouteTravel = ({
40829
40838
  });
40830
40839
  };
40831
40840
 
40841
+ // A travel no hand set off is one box long however far the page is, as in a
40842
+ // SlideContainer: the browser's smooth scroll would fly past every page in between.
40843
+ // So the page asked for takes the column next to what is in the frame, on the side
40844
+ // it already stands, which moves nothing on screen, and endMovement puts the columns
40845
+ // back. Columns rather than offsets: every page stays a place to snap to, and none
40846
+ // has to be hidden.
40847
+ const travelRowTo = (index, movement) => {
40848
+ const row = rowRef.current;
40849
+ const panes = panesOf(row);
40850
+ const pane = panes[index];
40851
+ if (!pane) {
40852
+ return;
40853
+ }
40854
+ const {
40855
+ base,
40856
+ toward,
40857
+ fraction
40858
+ } = readRowFrame(row, panes);
40859
+ const inFrame = toward && fraction > 0 ? [base, toward] : [base];
40860
+ if (!inFrame.includes(pane)) {
40861
+ // In a window, what the window put in a column moves with its pane.
40862
+ const standingIn = paneIndex => {
40863
+ const standing = [panes[paneIndex]];
40864
+ if (windowed) {
40865
+ const picture = movement.pictures.get(paneIndex);
40866
+ if (picture) {
40867
+ standing.push(picture);
40868
+ }
40869
+ if (paneIndex === latestRef.current.liveIndex) {
40870
+ standing.push(liveRef.current);
40871
+ }
40872
+ }
40873
+ return standing;
40874
+ };
40875
+ const columns = [...panes].sort((a, b) => a.offsetLeft - b.offsetLeft);
40876
+ const first = columns.indexOf(inFrame[0]);
40877
+ const last = columns.indexOf(inFrame[inFrame.length - 1]);
40878
+ const from = columns.indexOf(pane);
40879
+ columns.splice(from, 1);
40880
+ columns.splice(from < first ? first - 1 : last + 1, 0, pane);
40881
+ movement.placed ||= new Map();
40882
+ columns.forEach((eachPane, column) => {
40883
+ for (const element of standingIn(panes.indexOf(eachPane))) {
40884
+ if (!movement.placed.has(element)) {
40885
+ movement.placed.set(element, element.style.gridColumn);
40886
+ }
40887
+ element.style.gridColumn = String(column + 1);
40888
+ }
40889
+ });
40890
+ }
40891
+ row.scrollTo({
40892
+ left: pane.offsetLeft,
40893
+ behavior: "smooth"
40894
+ });
40895
+ };
40896
+
40897
+ // Every column back where the map says, and the row on the page of the address, in
40898
+ // one go: that page stands at the same place on screen either way.
40899
+ const putRowBack = movement => {
40900
+ const row = rowRef.current;
40901
+ if (movement.placed) {
40902
+ for (const [element, column] of movement.placed) {
40903
+ element.style.gridColumn = column;
40904
+ }
40905
+ movement.placed = null;
40906
+ }
40907
+ const pane = panesOf(row)[latestRef.current.currentIndex];
40908
+ if (pane && !restsOn(row, pane, ROW_READING)) {
40909
+ row.scrollTo({
40910
+ left: pane.offsetLeft,
40911
+ behavior: "instant"
40912
+ });
40913
+ }
40914
+ };
40915
+
40832
40916
  // A tab pressed over a row a hand has left moving, which the browser plays on and
40833
40917
  // overwrites a travel with once that travel is over: the snap is ended first. Given
40834
40918
  // back, the row stands where it stood. A frame later, because until then WebKit still
@@ -40849,12 +40933,8 @@ const RouteTravel = ({
40849
40933
  row.setAttribute("data-route-row-taken", "");
40850
40934
  row.style.removeProperty("--navi-route-row-stand");
40851
40935
  afterFrames(1, () => {
40852
- const pane = panesOf(row)[latestRef.current.currentIndex];
40853
- if (movementRef.current === movement && pane) {
40854
- row.scrollTo({
40855
- left: pane.offsetLeft,
40856
- behavior: "smooth"
40857
- });
40936
+ if (movementRef.current === movement) {
40937
+ travelRowTo(latestRef.current.currentIndex, movement);
40858
40938
  }
40859
40939
  });
40860
40940
  });
@@ -40869,6 +40949,7 @@ const RouteTravel = ({
40869
40949
  return;
40870
40950
  }
40871
40951
  movementRef.current = null;
40952
+ putRowBack(movement);
40872
40953
  row.removeAttribute("data-moving");
40873
40954
  row.removeAttribute("data-route-row-taken");
40874
40955
  publishRouteRowMove(null);
@@ -40908,7 +40989,7 @@ const RouteTravel = ({
40908
40989
  const pane = panesOf(row)[currentIndex];
40909
40990
  const answered = answeredPageRef.current;
40910
40991
  if (samePage(answered, page)) {
40911
- if (pane && !movementRef.current && !restsOn(row, pane)) {
40992
+ if (pane && !movementRef.current && !restsOn(row, pane, ROW_READING)) {
40912
40993
  row.scrollTo({
40913
40994
  left: pane.offsetLeft,
40914
40995
  behavior: "instant"
@@ -40924,13 +41005,13 @@ const RouteTravel = ({
40924
41005
  if (asked && samePage(asked, page)) {
40925
41006
  askedPageRef.current = null;
40926
41007
  // The hand landed before the address did.
40927
- if (movementRef.current?.landed && restsOn(row, pane)) {
41008
+ if (movementRef.current?.landed && restsOn(row, pane, ROW_READING)) {
40928
41009
  Promise.resolve().then(endMovement);
40929
41010
  }
40930
41011
  return;
40931
41012
  }
40932
- if (!answered || restsOn(row, pane)) {
40933
- if (!restsOn(row, pane)) {
41013
+ if (!answered || restsOn(row, pane, ROW_READING)) {
41014
+ if (!restsOn(row, pane, ROW_READING)) {
40934
41015
  row.scrollTo({
40935
41016
  left: pane.offsetLeft,
40936
41017
  behavior: "instant"
@@ -40942,19 +41023,17 @@ const RouteTravel = ({
40942
41023
  const movement = beginMovement({
40943
41024
  travelling: true
40944
41025
  });
40945
- // In this very commit: the tab pressed already wears its bar, and the first
40946
- // scroll event is a frame away.
40947
- sayRowMoves();
40948
41026
  if (movementBefore?.byHand && !movementBefore.rowTaken) {
41027
+ sayRowMoves();
40949
41028
  takeRowFromHand(movement);
40950
41029
  return;
40951
41030
  }
40952
41031
  if (row.getAttribute("data-route-row-taken") !== "clip") {
40953
- row.scrollTo({
40954
- left: pane.offsetLeft,
40955
- behavior: "smooth"
40956
- });
41032
+ travelRowTo(currentIndex, movement);
40957
41033
  }
41034
+ // In this very commit: the tab pressed already wears its bar, and the first
41035
+ // scroll event is a frame away.
41036
+ sayRowMoves();
40958
41037
  });
40959
41038
 
40960
41039
  // Somebody else is about to move the address to another page of a window: its
@@ -41001,7 +41080,7 @@ const RouteTravel = ({
41001
41080
  }
41002
41081
  const moving = movementRef.current;
41003
41082
  const pane = panes[currentIndex];
41004
- if (!moving && pane && restsOn(row, pane)) {
41083
+ if (!moving && pane && restsOn(row, pane, ROW_READING)) {
41005
41084
  // Written by this box, at rest: nobody is moving anything.
41006
41085
  return;
41007
41086
  }
@@ -41020,7 +41099,7 @@ const RouteTravel = ({
41020
41099
  base,
41021
41100
  toward,
41022
41101
  fraction
41023
- } = readRowStretch(row, panes, panes[0]);
41102
+ } = readRowFrame(row, panes);
41024
41103
  followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
41025
41104
  }
41026
41105
  const pulled = row.scrollLeft - pane.offsetLeft;
@@ -41060,7 +41139,7 @@ const RouteTravel = ({
41060
41139
  const {
41061
41140
  base,
41062
41141
  toward
41063
- } = readRowStretch(row, panes, panes[0]);
41142
+ } = readRowFrame(row, panes);
41064
41143
  const index = panes.indexOf(forward && toward ? toward : base);
41065
41144
  const page = pages[index];
41066
41145
  if (!page) {
@@ -41079,8 +41158,18 @@ const RouteTravel = ({
41079
41158
  return;
41080
41159
  }
41081
41160
  const panes = panesOf(row);
41082
- const pane = pageAtRest(row, panes);
41161
+ const pane = pageAtRest(row, panes, ROW_READING);
41083
41162
  if (!pane) {
41163
+ // A travel stopped short of its page: the layout moved under it (a scrollbar
41164
+ // coming in narrows the row), and where it was sent is not where the page
41165
+ // stands. Sent again: the row then rests, or does not move and is not heard.
41166
+ const target = panes[latestRef.current.currentIndex];
41167
+ if (movement.travelling && !isClipped() && target) {
41168
+ row.scrollTo({
41169
+ left: target.offsetLeft,
41170
+ behavior: "smooth"
41171
+ });
41172
+ }
41084
41173
  return;
41085
41174
  }
41086
41175
  movement.landed = true;
@@ -41104,8 +41193,15 @@ const RouteTravel = ({
41104
41193
  onLand,
41105
41194
  onLeftToSnap
41106
41195
  });
41107
- // A hand back on a row taken from one: it is the hand's again, snapping included.
41108
- const onHandBack = () => {
41196
+ // A finger on a travel still playing: the row is the finger's from here, and it
41197
+ // stands where the travel was going, as in a SlideContainer. A wheel only gives the
41198
+ // row its snapping back: a page scrolled vertically over it is no hand on it.
41199
+ const onTouchOnTravel = () => {
41200
+ if (movementRef.current?.travelling && !isClipped()) {
41201
+ endMovement();
41202
+ }
41203
+ };
41204
+ const onWheelOnTravel = () => {
41109
41205
  if (row.getAttribute("data-route-row-taken") === "") {
41110
41206
  row.removeAttribute("data-route-row-taken");
41111
41207
  }
@@ -41130,10 +41226,10 @@ const RouteTravel = ({
41130
41226
  movement.travelling = true;
41131
41227
  takeRowFromHand(movement);
41132
41228
  };
41133
- row.addEventListener("touchstart", onHandBack, {
41229
+ row.addEventListener("touchstart", onTouchOnTravel, {
41134
41230
  passive: true
41135
41231
  });
41136
- row.addEventListener("wheel", onHandBack, {
41232
+ row.addEventListener("wheel", onWheelOnTravel, {
41137
41233
  passive: true
41138
41234
  });
41139
41235
  document.addEventListener("click", onPress, {
@@ -41141,8 +41237,8 @@ const RouteTravel = ({
41141
41237
  });
41142
41238
  return () => {
41143
41239
  stopWatching();
41144
- row.removeEventListener("touchstart", onHandBack);
41145
- row.removeEventListener("wheel", onHandBack);
41240
+ row.removeEventListener("touchstart", onTouchOnTravel);
41241
+ row.removeEventListener("wheel", onWheelOnTravel);
41146
41242
  document.removeEventListener("click", onPress, {
41147
41243
  capture: true
41148
41244
  });
@@ -41161,7 +41257,7 @@ const RouteTravel = ({
41161
41257
  return;
41162
41258
  }
41163
41259
  const pane = panesOf(rowRef.current)[currentIndex];
41164
- if (pane && restsOn(rowRef.current, pane)) {
41260
+ if (pane && restsOn(rowRef.current, pane, ROW_READING)) {
41165
41261
  Promise.resolve().then(endMovement);
41166
41262
  }
41167
41263
  }, [currentIndex]);
@@ -41269,10 +41365,6 @@ const copyScrollPositions = (from, to) => {
41269
41365
  }
41270
41366
  }
41271
41367
  };
41272
-
41273
- // A page of the row: a route, and the params that say which of its tabs when
41274
- // several of them share it. Written as a bare route by a caller whose tabs are
41275
- // routes of their own — which is the same page with nothing to tell apart.
41276
41368
  const afterFrames = (count, callback) => {
41277
41369
  if (count === 0) {
41278
41370
  callback();
@@ -41282,6 +41374,13 @@ const afterFrames = (count, callback) => {
41282
41374
  afterFrames(count - 1, callback);
41283
41375
  });
41284
41376
  };
41377
+
41378
+ // The two pages the row stands between.
41379
+ const readRowFrame = (row, panes) => readRowStretch(row, panes, panes[0], ROW_READING);
41380
+
41381
+ // A page of the row: a route, and the params that say which of its tabs when
41382
+ // several of them share it. Written as a bare route by a caller whose tabs are
41383
+ // routes of their own — which is the same page with nothing to tell apart.
41285
41384
  const normalizePage = page => page.isRoute ? {
41286
41385
  route: page,
41287
41386
  params: undefined
@@ -85413,10 +85512,13 @@ const useListScrollSync = ({
85413
85512
  };
85414
85513
  // Items that came in at another size than what stood in for them (a
85415
85514
  // skeleton becoming its item) leave the window short of the screen, or past
85416
- // it, and nothing scrolled to say so. Asked on the next frame, not inside
85417
- // the resize delivery: the window it frames is a render, which resizes what
85418
- // is observed once more, and the browser gives up on the loop
85419
- // ("ResizeObserver loop completed with undelivered notifications").
85515
+ // it, and nothing scrolled to say so. A held list too: its hold places it but
85516
+ // leaves the window to the screen (see holdWindow), and a placement that
85517
+ // moves nothing — a document too short to scroll — fires no scroll that
85518
+ // would size it. Asked on the next frame, not inside the resize delivery:
85519
+ // the window it frames is a render, which resizes what is observed once
85520
+ // more, and the browser gives up on the loop ("ResizeObserver loop
85521
+ // completed with undelivered notifications").
85420
85522
  const onItemsResizedRef = useRef(null);
85421
85523
  onItemsResizedRef.current = () => {
85422
85524
  settleWindow();
@@ -85580,9 +85682,7 @@ const useListScrollSync = ({
85580
85682
  const putTopItemBack = entries => {
85581
85683
  const listResized = entries.some(entry => entry.target === listEl);
85582
85684
  if (listResized) {
85583
- if (onGeometryChangeRef.current()) {
85584
- return;
85585
- }
85685
+ const placed = onGeometryChangeRef.current();
85586
85686
  cancelAnimationFrame(itemsResizedFrame);
85587
85687
  itemsResizedFrame = requestAnimationFrame(() => {
85588
85688
  if (pageLeftRef.current) {
@@ -85590,6 +85690,9 @@ const useListScrollSync = ({
85590
85690
  }
85591
85691
  onItemsResizedRef.current();
85592
85692
  });
85693
+ if (placed) {
85694
+ return;
85695
+ }
85593
85696
  }
85594
85697
  if (!entries.some(entry => entry.target === scrollerEl)) {
85595
85698
  return;