@jsenv/navi 0.30.29 → 0.30.31
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.
- package/dist/dev/jsenv_navi.js +235 -116
- package/dist/dev/jsenv_navi.js.map +7 -5
- package/dist/jsenv_navi.js +235 -116
- package/dist/jsenv_navi.js.map +7 -5
- package/docs/drag_to_travel.md +4 -4
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -36896,6 +36896,28 @@ const pageIsCurrent = ({ route, params }) => {
|
|
|
36896
36896
|
return params ? route.matchesParams(params) : true;
|
|
36897
36897
|
};
|
|
36898
36898
|
|
|
36899
|
+
// The same question about an address the router does not have yet (a navigation
|
|
36900
|
+
// about to be routed, a link pressed): its path, and every param the page names at
|
|
36901
|
+
// the value the page gives it. The same path under another search param is another
|
|
36902
|
+
// page — a person opened from another list — and a param the page does not name is
|
|
36903
|
+
// state on top of it, as it is for matchesParams.
|
|
36904
|
+
const pageIsAt = ({ route, params }, url) => {
|
|
36905
|
+
const destination = new URL(url, window.location.href);
|
|
36906
|
+
const own = new URL(route.buildUrl(params), window.location.href);
|
|
36907
|
+
if (own.pathname !== destination.pathname) {
|
|
36908
|
+
return false;
|
|
36909
|
+
}
|
|
36910
|
+
if (!params) {
|
|
36911
|
+
return true;
|
|
36912
|
+
}
|
|
36913
|
+
for (const name of Object.keys(params)) {
|
|
36914
|
+
if (own.searchParams.get(name) !== destination.searchParams.get(name)) {
|
|
36915
|
+
return false;
|
|
36916
|
+
}
|
|
36917
|
+
}
|
|
36918
|
+
return true;
|
|
36919
|
+
};
|
|
36920
|
+
|
|
36899
36921
|
/**
|
|
36900
36922
|
* The page a <Route> container shows when none of its branches matched, given
|
|
36901
36923
|
* a name.
|
|
@@ -39091,6 +39113,45 @@ const readBand = () => {
|
|
|
39091
39113
|
};
|
|
39092
39114
|
};
|
|
39093
39115
|
|
|
39116
|
+
/**
|
|
39117
|
+
* CSS durations and easing curves, for a movement played from JS that has to look
|
|
39118
|
+
* like one played by CSS: a row scrolled frame by frame next to a track animated by
|
|
39119
|
+
* the browser, a view transition walked back from where its curve has it.
|
|
39120
|
+
*/
|
|
39121
|
+
|
|
39122
|
+
// A CSS duration read as a number of milliseconds.
|
|
39123
|
+
const durationToMs = (duration) => {
|
|
39124
|
+
const number = parseFloat(duration);
|
|
39125
|
+
if (Number.isNaN(number)) {
|
|
39126
|
+
return 0;
|
|
39127
|
+
}
|
|
39128
|
+
return String(duration).trimEnd().endsWith("ms") ? number : number * 1000;
|
|
39129
|
+
};
|
|
39130
|
+
|
|
39131
|
+
const CSS_EASE = [0.25, 0.1, 0.25, 1];
|
|
39132
|
+
const CSS_EASE_OUT = [0, 0, 0.58, 1];
|
|
39133
|
+
|
|
39134
|
+
// A cubic-bezier curve, evaluated: time in, distance out. Solved numerically
|
|
39135
|
+
// because the curve is parametric — two cubics sharing a parameter, with no closed
|
|
39136
|
+
// form for one against the other. Twenty halvings put the answer well under a pixel
|
|
39137
|
+
// of a screen-wide movement.
|
|
39138
|
+
const easedProgress = (x, [x1, y1, x2, y2]) => {
|
|
39139
|
+
let low = 0;
|
|
39140
|
+
let high = 1;
|
|
39141
|
+
for (let i = 0; i < 20; i++) {
|
|
39142
|
+
const mid = (low + high) / 2;
|
|
39143
|
+
if (bezierAxis(mid, x1, x2) < x) {
|
|
39144
|
+
low = mid;
|
|
39145
|
+
} else {
|
|
39146
|
+
high = mid;
|
|
39147
|
+
}
|
|
39148
|
+
}
|
|
39149
|
+
return bezierAxis((low + high) / 2, y1, y2);
|
|
39150
|
+
};
|
|
39151
|
+
|
|
39152
|
+
const bezierAxis = (s, a, b) =>
|
|
39153
|
+
3 * (1 - s) * (1 - s) * s * a + 3 * (1 - s) * s * s * b + s * s * s;
|
|
39154
|
+
|
|
39094
39155
|
/**
|
|
39095
39156
|
* A view transition already playing, walked back to where it started.
|
|
39096
39157
|
*
|
|
@@ -39120,6 +39181,7 @@ const readBand = () => {
|
|
|
39120
39181
|
* there while the clock counts down.
|
|
39121
39182
|
*/
|
|
39122
39183
|
|
|
39184
|
+
|
|
39123
39185
|
const viewTransitionAnimations = () => {
|
|
39124
39186
|
const animations = [];
|
|
39125
39187
|
for (const animation of document.getAnimations()) {
|
|
@@ -39244,27 +39306,6 @@ const whenPicturesArrived = (animations) => {
|
|
|
39244
39306
|
);
|
|
39245
39307
|
};
|
|
39246
39308
|
|
|
39247
|
-
// CSS `ease`, evaluated: time in, distance out. Solved numerically because the
|
|
39248
|
-
// curve is parametric — two cubics sharing a parameter, with no closed form for
|
|
39249
|
-
// one against the other. Twenty halvings put the answer well under a pixel of a
|
|
39250
|
-
// screen-wide movement.
|
|
39251
|
-
const CSS_EASE = [0.25, 0.1, 0.25, 1];
|
|
39252
|
-
const bezierAxis = (s, a, b) =>
|
|
39253
|
-
3 * (1 - s) * (1 - s) * s * a + 3 * (1 - s) * s * s * b + s * s * s;
|
|
39254
|
-
const easedProgress = (x, [x1, y1, x2, y2]) => {
|
|
39255
|
-
let low = 0;
|
|
39256
|
-
let high = 1;
|
|
39257
|
-
for (let i = 0; i < 20; i++) {
|
|
39258
|
-
const mid = (low + high) / 2;
|
|
39259
|
-
if (bezierAxis(mid, x1, x2) < x) {
|
|
39260
|
-
low = mid;
|
|
39261
|
-
} else {
|
|
39262
|
-
high = mid;
|
|
39263
|
-
}
|
|
39264
|
-
}
|
|
39265
|
-
return bezierAxis((low + high) / 2, y1, y2);
|
|
39266
|
-
};
|
|
39267
|
-
|
|
39268
39309
|
installImportMetaCssBuild(import.meta);
|
|
39269
39310
|
const startViewTransition = ensureDocumentStartViewTransition();
|
|
39270
39311
|
const TRANSITION_ATTRIBUTE = "data-navi-route-transition";
|
|
@@ -41294,11 +41335,11 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41294
41335
|
overflow: visible;
|
|
41295
41336
|
}
|
|
41296
41337
|
|
|
41297
|
-
&[data-route-row-
|
|
41338
|
+
&[data-route-row-travelling] {
|
|
41298
41339
|
scroll-snap-type: none;
|
|
41299
41340
|
}
|
|
41300
41341
|
|
|
41301
|
-
&[data-route-row-taken
|
|
41342
|
+
&[data-route-row-taken] {
|
|
41302
41343
|
overflow: clip;
|
|
41303
41344
|
|
|
41304
41345
|
& > [data-route-row-pane], & > [data-route-row-live], & > [data-route-row-pictures] > * {
|
|
@@ -41317,12 +41358,9 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41317
41358
|
display: flex;
|
|
41318
41359
|
}
|
|
41319
41360
|
|
|
41320
|
-
&:not([data-moving]) > [data-route-row-pane][inert] {
|
|
41321
|
-
visibility: hidden;
|
|
41322
|
-
}
|
|
41323
|
-
|
|
41324
41361
|
& > [data-route-row-pane][inert] {
|
|
41325
41362
|
top: var(--navi-route-row-pin, 0);
|
|
41363
|
+
contain: paint;
|
|
41326
41364
|
max-height: 100dvh;
|
|
41327
41365
|
position: relative;
|
|
41328
41366
|
overflow: clip;
|
|
@@ -41330,7 +41368,6 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41330
41368
|
|
|
41331
41369
|
& > [data-route-row-pane] > [data-route-row-neighbour] {
|
|
41332
41370
|
contain: layout paint;
|
|
41333
|
-
visibility: visible;
|
|
41334
41371
|
flex-direction: column;
|
|
41335
41372
|
display: flex;
|
|
41336
41373
|
position: absolute;
|
|
@@ -41368,6 +41405,9 @@ const SET_OFF_PULL = 16;
|
|
|
41368
41405
|
const ROW_READING = {
|
|
41369
41406
|
sideways: true
|
|
41370
41407
|
};
|
|
41408
|
+
// How far a scroll position written can read back from what was written: rounded to
|
|
41409
|
+
// the device's pixels.
|
|
41410
|
+
const WRITE_ROUNDING = 1;
|
|
41371
41411
|
|
|
41372
41412
|
/**
|
|
41373
41413
|
* @type {import("preact").FunctionComponent<{
|
|
@@ -41376,6 +41416,7 @@ const ROW_READING = {
|
|
|
41376
41416
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
41377
41417
|
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
41378
41418
|
* prefetch?: boolean,
|
|
41419
|
+
* duration?: string,
|
|
41379
41420
|
* }>}
|
|
41380
41421
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
41381
41422
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -41405,6 +41446,9 @@ const ROW_READING = {
|
|
|
41405
41446
|
* `route.preload(params)`. A swipe then builds a page whose answers are already
|
|
41406
41447
|
* there, with no wait to show. Off by default: every page one stops on costs the
|
|
41407
41448
|
* reads of two more.
|
|
41449
|
+
* @param {string} [props.duration="300ms"] - how long a travel asked for takes: a
|
|
41450
|
+
* tab pressed, the back button. One box, on the curve a SlideContainer plays, so
|
|
41451
|
+
* two rows of tabs in one app move alike. A hand goes at its own pace.
|
|
41408
41452
|
*
|
|
41409
41453
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
41410
41454
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -41422,6 +41466,7 @@ const RouteTravel = ({
|
|
|
41422
41466
|
}),
|
|
41423
41467
|
renderNeighbour,
|
|
41424
41468
|
prefetch = false,
|
|
41469
|
+
duration = "300ms",
|
|
41425
41470
|
className,
|
|
41426
41471
|
children,
|
|
41427
41472
|
...rest
|
|
@@ -41462,7 +41507,8 @@ const RouteTravel = ({
|
|
|
41462
41507
|
currentIndex,
|
|
41463
41508
|
liveIndex,
|
|
41464
41509
|
onTravel,
|
|
41465
|
-
travelByDrag
|
|
41510
|
+
travelByDrag,
|
|
41511
|
+
durationMs: durationToMs(duration)
|
|
41466
41512
|
};
|
|
41467
41513
|
|
|
41468
41514
|
// The page this box asked for: routing lands later, and read back as somebody
|
|
@@ -41482,9 +41528,9 @@ const RouteTravel = ({
|
|
|
41482
41528
|
Object.assign(movementRef.current, fields);
|
|
41483
41529
|
return movementRef.current;
|
|
41484
41530
|
}
|
|
41485
|
-
row.setAttribute("data-moving", "");
|
|
41486
41531
|
const movement = {
|
|
41487
41532
|
travelling: false,
|
|
41533
|
+
playing: null,
|
|
41488
41534
|
pictures: new Map(),
|
|
41489
41535
|
resumeScrollRecording: null,
|
|
41490
41536
|
scrollsDocument: rowFollowsDocument(row),
|
|
@@ -41580,11 +41626,11 @@ const RouteTravel = ({
|
|
|
41580
41626
|
};
|
|
41581
41627
|
|
|
41582
41628
|
// A travel no hand set off is one box long however far the page is, as in a
|
|
41583
|
-
// SlideContainer: the
|
|
41584
|
-
// So the page asked for takes the column next to what is in the frame, on
|
|
41585
|
-
// it already stands, which moves nothing on screen, and endMovement puts
|
|
41586
|
-
// back. Columns rather than offsets: every page stays a place to snap
|
|
41587
|
-
// has to be hidden.
|
|
41629
|
+
// SlideContainer: a scroll to where the page stands would fly past every page in
|
|
41630
|
+
// between. So the page asked for takes the column next to what is in the frame, on
|
|
41631
|
+
// the side it already stands, which moves nothing on screen, and endMovement puts
|
|
41632
|
+
// the columns back. Columns rather than offsets: every page stays a place to snap
|
|
41633
|
+
// to, and none has to be hidden.
|
|
41588
41634
|
const travelRowTo = (index, movement) => {
|
|
41589
41635
|
const row = rowRef.current;
|
|
41590
41636
|
const panes = panesOf(row);
|
|
@@ -41629,10 +41675,76 @@ const RouteTravel = ({
|
|
|
41629
41675
|
}
|
|
41630
41676
|
});
|
|
41631
41677
|
}
|
|
41632
|
-
|
|
41633
|
-
|
|
41634
|
-
|
|
41635
|
-
|
|
41678
|
+
playTravel(pane, movement);
|
|
41679
|
+
};
|
|
41680
|
+
|
|
41681
|
+
// A travel asked for, in `duration`, on SlideContainer's curve. Not the browser's
|
|
41682
|
+
// smooth scroll, which has no duration to ask for and a curve of its own (Chrome's
|
|
41683
|
+
// hesitates, then peaks at 1.65 times this one's speed): two rows of tabs in one
|
|
41684
|
+
// app would not move alike. The scroll position is written at every frame, so the
|
|
41685
|
+
// row stays a scroller all the way: what follows it (a Nav's bar) reads it as it
|
|
41686
|
+
// reads a hand, and a finger landing finds it where it is. The clock starts at the
|
|
41687
|
+
// first frame, after what the press itself costs, as an animation's does.
|
|
41688
|
+
const playTravel = (pane, movement) => {
|
|
41689
|
+
const row = rowRef.current;
|
|
41690
|
+
// Already moving, so there is no ease-in to play: it would stall the row for an
|
|
41691
|
+
// instant right where the eye is following it.
|
|
41692
|
+
const inFlight = Boolean(movement.playing) || Boolean(movement.rowTaken);
|
|
41693
|
+
if (movement.playing) {
|
|
41694
|
+
movement.playing.stop();
|
|
41695
|
+
}
|
|
41696
|
+
const from = row.scrollLeft;
|
|
41697
|
+
const {
|
|
41698
|
+
durationMs
|
|
41699
|
+
} = latestRef.current;
|
|
41700
|
+
// One box in `duration` is the pace: what is left of a travel picked up on the
|
|
41701
|
+
// way takes what is left of the time. Never slower: two boxes are not made to drag.
|
|
41702
|
+
const boxes = Math.abs(pane.offsetLeft - from) / row.clientWidth;
|
|
41703
|
+
const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
|
|
41704
|
+
if (!travelMs) {
|
|
41705
|
+
row.scrollLeft = pane.offsetLeft;
|
|
41706
|
+
// A write that changes nothing is followed by no scroll event to say the row
|
|
41707
|
+
// stopped. After the commit that asked, which goes on to say the row moves.
|
|
41708
|
+
Promise.resolve().then(landRow);
|
|
41709
|
+
return;
|
|
41710
|
+
}
|
|
41711
|
+
const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
|
|
41712
|
+
let start = null;
|
|
41713
|
+
let frame = null;
|
|
41714
|
+
const playing = {
|
|
41715
|
+
// Where this travel put the row, for a scroll event to tell it from a hand.
|
|
41716
|
+
written: from,
|
|
41717
|
+
stop: () => {
|
|
41718
|
+
cancelAnimationFrame(frame);
|
|
41719
|
+
if (movement.playing === playing) {
|
|
41720
|
+
movement.playing = null;
|
|
41721
|
+
row.removeAttribute("data-route-row-travelling");
|
|
41722
|
+
}
|
|
41723
|
+
}
|
|
41724
|
+
};
|
|
41725
|
+
const step = now => {
|
|
41726
|
+
if (start === null) {
|
|
41727
|
+
start = now;
|
|
41728
|
+
}
|
|
41729
|
+
const elapsed = (now - start) / travelMs;
|
|
41730
|
+
// Read at every frame: the layout may move under the travel (a scrollbar
|
|
41731
|
+
// coming in narrows the row), and it goes where the page stands.
|
|
41732
|
+
const to = pane.offsetLeft;
|
|
41733
|
+
const arrived = elapsed >= 1;
|
|
41734
|
+
playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
|
|
41735
|
+
row.scrollLeft = playing.written;
|
|
41736
|
+
if (!arrived) {
|
|
41737
|
+
frame = requestAnimationFrame(step);
|
|
41738
|
+
return;
|
|
41739
|
+
}
|
|
41740
|
+
playing.stop();
|
|
41741
|
+
// Said here rather than waited for: the last frames of a curve can move the row
|
|
41742
|
+
// by less than a pixel, and nothing then says it has stopped.
|
|
41743
|
+
landRow();
|
|
41744
|
+
};
|
|
41745
|
+
movement.playing = playing;
|
|
41746
|
+
row.setAttribute("data-route-row-travelling", "");
|
|
41747
|
+
frame = requestAnimationFrame(step);
|
|
41636
41748
|
};
|
|
41637
41749
|
|
|
41638
41750
|
// Every column back where the map says, and the row on the page of the address, in
|
|
@@ -41656,9 +41768,8 @@ const RouteTravel = ({
|
|
|
41656
41768
|
|
|
41657
41769
|
// A tab pressed over a row a hand has left moving, which the browser plays on and
|
|
41658
41770
|
// overwrites a travel with once that travel is over: the snap is ended first. Given
|
|
41659
|
-
// back, the row stands where it stood.
|
|
41660
|
-
//
|
|
41661
|
-
// sees nothing to do.
|
|
41771
|
+
// back, the row stands where it stood. The travel a frame later, because until then
|
|
41772
|
+
// WebKit still has the row where a box that is no scroller stood, at 0.
|
|
41662
41773
|
const takeRowFromHand = movement => {
|
|
41663
41774
|
const row = rowRef.current;
|
|
41664
41775
|
const position = row.scrollLeft;
|
|
@@ -41666,12 +41777,14 @@ const RouteTravel = ({
|
|
|
41666
41777
|
// A travel no hand set off holds nothing: it goes where it goes.
|
|
41667
41778
|
releaseHeldPage();
|
|
41668
41779
|
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
41669
|
-
row.setAttribute("data-route-row-taken", "
|
|
41780
|
+
row.setAttribute("data-route-row-taken", "");
|
|
41670
41781
|
afterFrames(2, () => {
|
|
41671
41782
|
if (movementRef.current !== movement) {
|
|
41672
41783
|
return;
|
|
41673
41784
|
}
|
|
41674
|
-
|
|
41785
|
+
// A scroller again, standing where no page does: not snapped meanwhile.
|
|
41786
|
+
row.setAttribute("data-route-row-travelling", "");
|
|
41787
|
+
row.removeAttribute("data-route-row-taken");
|
|
41675
41788
|
row.style.removeProperty("--navi-route-row-stand");
|
|
41676
41789
|
afterFrames(1, () => {
|
|
41677
41790
|
if (movementRef.current === movement) {
|
|
@@ -41690,8 +41803,11 @@ const RouteTravel = ({
|
|
|
41690
41803
|
return;
|
|
41691
41804
|
}
|
|
41692
41805
|
movementRef.current = null;
|
|
41806
|
+
if (movement.playing) {
|
|
41807
|
+
movement.playing.stop();
|
|
41808
|
+
}
|
|
41693
41809
|
putRowBack(movement);
|
|
41694
|
-
row.removeAttribute("data-
|
|
41810
|
+
row.removeAttribute("data-route-row-travelling");
|
|
41695
41811
|
row.removeAttribute("data-route-row-taken");
|
|
41696
41812
|
publishRouteRowMove(null);
|
|
41697
41813
|
releaseHeldPage();
|
|
@@ -41720,6 +41836,45 @@ const RouteTravel = ({
|
|
|
41720
41836
|
render();
|
|
41721
41837
|
};
|
|
41722
41838
|
|
|
41839
|
+
// The row may have stopped: on the page of the address, the movement is over; on
|
|
41840
|
+
// another, a hand put it there and the address follows.
|
|
41841
|
+
const landRow = () => {
|
|
41842
|
+
const row = rowRef.current;
|
|
41843
|
+
const movement = movementRef.current;
|
|
41844
|
+
// A travel playing says itself when it is over.
|
|
41845
|
+
if (!movement || movement.playing || isRowTaken(row)) {
|
|
41846
|
+
return;
|
|
41847
|
+
}
|
|
41848
|
+
const panes = panesOf(row);
|
|
41849
|
+
const pane = pageAtRest(row, panes, ROW_READING);
|
|
41850
|
+
if (!pane) {
|
|
41851
|
+
// A travel a hand took over (a trackpad, a wheel) and left between two pages:
|
|
41852
|
+
// sent again, the row then rests.
|
|
41853
|
+
const {
|
|
41854
|
+
currentIndex
|
|
41855
|
+
} = latestRef.current;
|
|
41856
|
+
if (movement.travelling && panes[currentIndex]) {
|
|
41857
|
+
travelRowTo(currentIndex, movement);
|
|
41858
|
+
}
|
|
41859
|
+
return;
|
|
41860
|
+
}
|
|
41861
|
+
movement.landed = true;
|
|
41862
|
+
const {
|
|
41863
|
+
pages
|
|
41864
|
+
} = latestRef.current;
|
|
41865
|
+
const page = pages[panes.indexOf(pane)];
|
|
41866
|
+
if (!page || pageIsCurrent(page)) {
|
|
41867
|
+
if (!askedPageRef.current || samePage(askedPageRef.current, page)) {
|
|
41868
|
+
endMovement();
|
|
41869
|
+
}
|
|
41870
|
+
return;
|
|
41871
|
+
}
|
|
41872
|
+
if (samePage(askedPageRef.current, page)) {
|
|
41873
|
+
return;
|
|
41874
|
+
}
|
|
41875
|
+
askForPageOfRow(panes.indexOf(pane), movement);
|
|
41876
|
+
};
|
|
41877
|
+
|
|
41723
41878
|
// The address and the row, in step. The opening and a row given again around the
|
|
41724
41879
|
// page on screen have nothing to show for it; somebody else moving the address
|
|
41725
41880
|
// (a tab pressed, the back button) is a travel.
|
|
@@ -41740,6 +41895,13 @@ const RouteTravel = ({
|
|
|
41740
41895
|
}
|
|
41741
41896
|
answeredPageRef.current = page;
|
|
41742
41897
|
if (!page || !pane) {
|
|
41898
|
+
// The address left the row while a travel nobody's hand is on was playing (a
|
|
41899
|
+
// link to a page off the row): there is nothing left to travel to, and the
|
|
41900
|
+
// landing would send the address back to the page the row rests on.
|
|
41901
|
+
const movement = movementRef.current;
|
|
41902
|
+
if (!page && movement?.travelling && !movement.byHand) {
|
|
41903
|
+
Promise.resolve().then(endMovement);
|
|
41904
|
+
}
|
|
41743
41905
|
return;
|
|
41744
41906
|
}
|
|
41745
41907
|
const asked = askedPageRef.current;
|
|
@@ -41769,7 +41931,7 @@ const RouteTravel = ({
|
|
|
41769
41931
|
takeRowFromHand(movement);
|
|
41770
41932
|
return;
|
|
41771
41933
|
}
|
|
41772
|
-
if (row.
|
|
41934
|
+
if (!row.hasAttribute("data-route-row-taken")) {
|
|
41773
41935
|
travelRowTo(currentIndex, movement);
|
|
41774
41936
|
}
|
|
41775
41937
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
@@ -41794,8 +41956,7 @@ const RouteTravel = ({
|
|
|
41794
41956
|
if (asked || currentIndex === -1) {
|
|
41795
41957
|
return;
|
|
41796
41958
|
}
|
|
41797
|
-
|
|
41798
|
-
if (pathOf(pages[currentIndex]) === path || !pages.some(page => pathOf(page) === path)) {
|
|
41959
|
+
if (pageIsAt(pages[currentIndex], url) || !pages.some(page => pageIsAt(page, url))) {
|
|
41799
41960
|
return;
|
|
41800
41961
|
}
|
|
41801
41962
|
const movement = beginMovement({
|
|
@@ -41806,7 +41967,6 @@ const RouteTravel = ({
|
|
|
41806
41967
|
}, [windowed]);
|
|
41807
41968
|
useLayoutEffect(() => {
|
|
41808
41969
|
const row = rowRef.current;
|
|
41809
|
-
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
41810
41970
|
const onScroll = ({
|
|
41811
41971
|
finger,
|
|
41812
41972
|
lifted
|
|
@@ -41817,10 +41977,15 @@ const RouteTravel = ({
|
|
|
41817
41977
|
travelByDrag
|
|
41818
41978
|
} = latestRef.current;
|
|
41819
41979
|
const panes = panesOf(row);
|
|
41820
|
-
if (panes.length < 2 ||
|
|
41980
|
+
if (panes.length < 2 || isRowTaken(row)) {
|
|
41821
41981
|
return;
|
|
41822
41982
|
}
|
|
41823
41983
|
const moving = movementRef.current;
|
|
41984
|
+
// Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
|
|
41985
|
+
// and the travel lets it go, snapping included, as the browser's own scroll does.
|
|
41986
|
+
if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
|
|
41987
|
+
moving.playing.stop();
|
|
41988
|
+
}
|
|
41824
41989
|
const pane = panes[currentIndex];
|
|
41825
41990
|
if (!moving && pane && restsOn(row, pane, ROW_READING)) {
|
|
41826
41991
|
// Written by this box, at rest: nobody is moving anything.
|
|
@@ -41900,60 +42065,20 @@ const RouteTravel = ({
|
|
|
41900
42065
|
// address ran ahead to, even for a render.
|
|
41901
42066
|
releaseHeldPage();
|
|
41902
42067
|
};
|
|
41903
|
-
const onLand = () => {
|
|
41904
|
-
const movement = movementRef.current;
|
|
41905
|
-
if (!movement || isClipped()) {
|
|
41906
|
-
return;
|
|
41907
|
-
}
|
|
41908
|
-
const panes = panesOf(row);
|
|
41909
|
-
const pane = pageAtRest(row, panes, ROW_READING);
|
|
41910
|
-
if (!pane) {
|
|
41911
|
-
// A travel stopped short of its page: the layout moved under it (a scrollbar
|
|
41912
|
-
// coming in narrows the row), and where it was sent is not where the page
|
|
41913
|
-
// stands. Sent again: the row then rests, or does not move and is not heard.
|
|
41914
|
-
const target = panes[latestRef.current.currentIndex];
|
|
41915
|
-
if (movement.travelling && !isClipped() && target) {
|
|
41916
|
-
row.scrollTo({
|
|
41917
|
-
left: target.offsetLeft,
|
|
41918
|
-
behavior: "smooth"
|
|
41919
|
-
});
|
|
41920
|
-
}
|
|
41921
|
-
return;
|
|
41922
|
-
}
|
|
41923
|
-
movement.landed = true;
|
|
41924
|
-
const {
|
|
41925
|
-
pages
|
|
41926
|
-
} = latestRef.current;
|
|
41927
|
-
const page = pages[panes.indexOf(pane)];
|
|
41928
|
-
if (!page || pageIsCurrent(page)) {
|
|
41929
|
-
if (!askedPageRef.current || samePage(askedPageRef.current, page)) {
|
|
41930
|
-
endMovement();
|
|
41931
|
-
}
|
|
41932
|
-
return;
|
|
41933
|
-
}
|
|
41934
|
-
if (samePage(askedPageRef.current, page)) {
|
|
41935
|
-
return;
|
|
41936
|
-
}
|
|
41937
|
-
askForPageOfRow(panes.indexOf(pane), movement);
|
|
41938
|
-
};
|
|
41939
42068
|
const stopWatching = watchRowScroll(row, {
|
|
41940
42069
|
onScroll,
|
|
41941
|
-
onLand,
|
|
42070
|
+
onLand: landRow,
|
|
41942
42071
|
onLeftToSnap
|
|
41943
42072
|
});
|
|
41944
42073
|
// A finger on a travel still playing: the row is the finger's from here, and it
|
|
41945
|
-
// stands where the travel was going, as in a SlideContainer.
|
|
41946
|
-
//
|
|
42074
|
+
// stands where the travel was going, as in a SlideContainer. Not a wheel: a page
|
|
42075
|
+
// scrolled vertically over the row is no hand on it, and one scrolling it sideways
|
|
42076
|
+
// is told apart by the travel itself (onScroll).
|
|
41947
42077
|
const onTouchOnTravel = () => {
|
|
41948
|
-
if (movementRef.current?.travelling && !
|
|
42078
|
+
if (movementRef.current?.travelling && !isRowTaken(row)) {
|
|
41949
42079
|
endMovement();
|
|
41950
42080
|
}
|
|
41951
42081
|
};
|
|
41952
|
-
const onWheelOnTravel = () => {
|
|
41953
|
-
if (row.getAttribute("data-route-row-taken") === "") {
|
|
41954
|
-
row.removeAttribute("data-route-row-taken");
|
|
41955
|
-
}
|
|
41956
|
-
};
|
|
41957
42082
|
// A press for the page the address is already on changes nothing the router can
|
|
41958
42083
|
// tell, and a stroke no lift ends leaves the address where it was: it is heard here,
|
|
41959
42084
|
// the way a tab says which page it is through its address.
|
|
@@ -41968,7 +42093,7 @@ const RouteTravel = ({
|
|
|
41968
42093
|
currentIndex
|
|
41969
42094
|
} = latestRef.current;
|
|
41970
42095
|
const page = pages[currentIndex];
|
|
41971
|
-
if (!link || !page ||
|
|
42096
|
+
if (!link || !page || !pageIsAt(page, link.href)) {
|
|
41972
42097
|
return;
|
|
41973
42098
|
}
|
|
41974
42099
|
movement.travelling = true;
|
|
@@ -41977,20 +42102,20 @@ const RouteTravel = ({
|
|
|
41977
42102
|
row.addEventListener("touchstart", onTouchOnTravel, {
|
|
41978
42103
|
passive: true
|
|
41979
42104
|
});
|
|
41980
|
-
row.addEventListener("wheel", onWheelOnTravel, {
|
|
41981
|
-
passive: true
|
|
41982
|
-
});
|
|
41983
42105
|
document.addEventListener("click", onPress, {
|
|
41984
42106
|
capture: true
|
|
41985
42107
|
});
|
|
41986
42108
|
return () => {
|
|
41987
42109
|
stopWatching();
|
|
41988
42110
|
row.removeEventListener("touchstart", onTouchOnTravel);
|
|
41989
|
-
row.removeEventListener("wheel", onWheelOnTravel);
|
|
41990
42111
|
document.removeEventListener("click", onPress, {
|
|
41991
42112
|
capture: true
|
|
41992
42113
|
});
|
|
41993
|
-
movementRef.current
|
|
42114
|
+
const movement = movementRef.current;
|
|
42115
|
+
if (movement) {
|
|
42116
|
+
movement.playing?.stop();
|
|
42117
|
+
movement.resumeScrollRecording?.();
|
|
42118
|
+
}
|
|
41994
42119
|
movementRef.current = null;
|
|
41995
42120
|
publishRouteRowMove(null);
|
|
41996
42121
|
releaseHeldPage();
|
|
@@ -42050,12 +42175,10 @@ const RouteTravel = ({
|
|
|
42050
42175
|
}, [prefetch, neighbours, restCount]);
|
|
42051
42176
|
|
|
42052
42177
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
42053
|
-
// any scroll event
|
|
42054
|
-
|
|
42178
|
+
// any scroll event, so it has to stand where the reader is looking by then: a
|
|
42179
|
+
// finger landing is the last word before that. A trackpad says nothing first, and
|
|
42180
|
+
// the pin comes with its first scroll event (beginMovement).
|
|
42055
42181
|
useLayoutEffect(() => {
|
|
42056
|
-
if (!paintsNeighbours) {
|
|
42057
|
-
return undefined;
|
|
42058
|
-
}
|
|
42059
42182
|
const row = rowRef.current;
|
|
42060
42183
|
const onTouchStart = () => {
|
|
42061
42184
|
if (!movementRef.current && rowFollowsDocument(row)) {
|
|
@@ -42068,7 +42191,8 @@ const RouteTravel = ({
|
|
|
42068
42191
|
return () => {
|
|
42069
42192
|
row.removeEventListener("touchstart", onTouchStart);
|
|
42070
42193
|
};
|
|
42071
|
-
}, [
|
|
42194
|
+
}, []);
|
|
42195
|
+
const paintsNeighbours = Boolean(renderNeighbour);
|
|
42072
42196
|
const rowContext = useMemo(() => ({
|
|
42073
42197
|
renderNeighbour
|
|
42074
42198
|
}), [renderNeighbour]);
|
|
@@ -42152,6 +42276,10 @@ const copyScrollPositions = (from, to) => {
|
|
|
42152
42276
|
}
|
|
42153
42277
|
}
|
|
42154
42278
|
};
|
|
42279
|
+
|
|
42280
|
+
// A tab pressed over a row a hand has left moving holds it still (takeRowFromHand):
|
|
42281
|
+
// what the row says meanwhile is not a hand moving it.
|
|
42282
|
+
const isRowTaken = row => row.hasAttribute("data-route-row-taken");
|
|
42155
42283
|
const afterFrames = (count, callback) => {
|
|
42156
42284
|
if (count === 0) {
|
|
42157
42285
|
callback();
|
|
@@ -69174,15 +69302,6 @@ const lineAreas = (slideElements, layout) => {
|
|
|
69174
69302
|
return parseAreas(layout === "column" ? names : [names.join(" ")]);
|
|
69175
69303
|
};
|
|
69176
69304
|
|
|
69177
|
-
// A CSS duration read as a number, so a window knows when its travel is over.
|
|
69178
|
-
const durationToMs = duration => {
|
|
69179
|
-
const number = parseFloat(duration);
|
|
69180
|
-
if (Number.isNaN(number)) {
|
|
69181
|
-
return 0;
|
|
69182
|
-
}
|
|
69183
|
-
return String(duration).trimEnd().endsWith("ms") ? number : number * 1000;
|
|
69184
|
-
};
|
|
69185
|
-
|
|
69186
69305
|
// What asked for a travel, read off the event that carried it: the hand said
|
|
69187
69306
|
// "somewhere over there" (a gesture one browses with), or it said a name (a tab
|
|
69188
69307
|
// pressed, a key, a command — a place aimed at). Nothing at all when the travel
|