@jsenv/navi 0.30.30 → 0.30.32
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 +244 -116
- package/dist/dev/jsenv_navi.js.map +7 -5
- package/dist/jsenv_navi.js +244 -116
- package/dist/jsenv_navi.js.map +7 -5
- package/docs/drag_to_travel.md +4 -4
- package/package.json +1 -1
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,85 @@ 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 replaced = movement.playing;
|
|
41693
|
+
const inFlight = Boolean(replaced) || Boolean(movement.rowTaken);
|
|
41694
|
+
if (replaced) {
|
|
41695
|
+
// Its frames only: the row stays travelling across the hand-over. Let go of,
|
|
41696
|
+
// it snaps to the nearest page at the read below, and the travel starts there.
|
|
41697
|
+
replaced.cancelFrame();
|
|
41698
|
+
}
|
|
41699
|
+
const from = row.scrollLeft;
|
|
41700
|
+
const {
|
|
41701
|
+
durationMs
|
|
41702
|
+
} = latestRef.current;
|
|
41703
|
+
// One box in `duration` is the pace: what is left of a travel picked up on the
|
|
41704
|
+
// way takes what is left of the time. Never slower: two boxes are not made to drag.
|
|
41705
|
+
const boxes = Math.abs(pane.offsetLeft - from) / row.clientWidth;
|
|
41706
|
+
const travelMs = boxes < 1 ? durationMs * boxes : durationMs;
|
|
41707
|
+
if (!travelMs) {
|
|
41708
|
+
row.scrollLeft = pane.offsetLeft;
|
|
41709
|
+
// A write that changes nothing is followed by no scroll event to say the row
|
|
41710
|
+
// stopped. After the commit that asked, which goes on to say the row moves.
|
|
41711
|
+
Promise.resolve().then(landRow);
|
|
41712
|
+
return;
|
|
41713
|
+
}
|
|
41714
|
+
const curve = inFlight ? CSS_EASE_OUT : CSS_EASE;
|
|
41715
|
+
let start = null;
|
|
41716
|
+
let frame = null;
|
|
41717
|
+
const playing = {
|
|
41718
|
+
// Where this travel put the row, for a scroll event to tell it from a hand.
|
|
41719
|
+
written: from,
|
|
41720
|
+
cancelFrame: () => {
|
|
41721
|
+
cancelAnimationFrame(frame);
|
|
41722
|
+
},
|
|
41723
|
+
stop: () => {
|
|
41724
|
+
playing.cancelFrame();
|
|
41725
|
+
if (movement.playing === playing) {
|
|
41726
|
+
movement.playing = null;
|
|
41727
|
+
row.removeAttribute("data-route-row-travelling");
|
|
41728
|
+
}
|
|
41729
|
+
}
|
|
41730
|
+
};
|
|
41731
|
+
const step = now => {
|
|
41732
|
+
if (start === null) {
|
|
41733
|
+
// When this frame runs, not when it began: under load it began while the
|
|
41734
|
+
// press was still being handled, and that time is no travel.
|
|
41735
|
+
start = performance.now();
|
|
41736
|
+
}
|
|
41737
|
+
// The next frame may have begun while this first one was still being handled.
|
|
41738
|
+
const elapsed = now > start ? (now - start) / travelMs : 0;
|
|
41739
|
+
// Read at every frame: the layout may move under the travel (a scrollbar
|
|
41740
|
+
// coming in narrows the row), and it goes where the page stands.
|
|
41741
|
+
const to = pane.offsetLeft;
|
|
41742
|
+
const arrived = elapsed >= 1;
|
|
41743
|
+
playing.written = arrived ? to : from + (to - from) * easedProgress(elapsed, curve);
|
|
41744
|
+
row.scrollLeft = playing.written;
|
|
41745
|
+
if (!arrived) {
|
|
41746
|
+
frame = requestAnimationFrame(step);
|
|
41747
|
+
return;
|
|
41748
|
+
}
|
|
41749
|
+
playing.stop();
|
|
41750
|
+
// Said here rather than waited for: the last frames of a curve can move the row
|
|
41751
|
+
// by less than a pixel, and nothing then says it has stopped.
|
|
41752
|
+
landRow();
|
|
41753
|
+
};
|
|
41754
|
+
movement.playing = playing;
|
|
41755
|
+
row.setAttribute("data-route-row-travelling", "");
|
|
41756
|
+
frame = requestAnimationFrame(step);
|
|
41636
41757
|
};
|
|
41637
41758
|
|
|
41638
41759
|
// Every column back where the map says, and the row on the page of the address, in
|
|
@@ -41656,9 +41777,8 @@ const RouteTravel = ({
|
|
|
41656
41777
|
|
|
41657
41778
|
// A tab pressed over a row a hand has left moving, which the browser plays on and
|
|
41658
41779
|
// 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.
|
|
41780
|
+
// back, the row stands where it stood. The travel a frame later, because until then
|
|
41781
|
+
// WebKit still has the row where a box that is no scroller stood, at 0.
|
|
41662
41782
|
const takeRowFromHand = movement => {
|
|
41663
41783
|
const row = rowRef.current;
|
|
41664
41784
|
const position = row.scrollLeft;
|
|
@@ -41666,12 +41786,14 @@ const RouteTravel = ({
|
|
|
41666
41786
|
// A travel no hand set off holds nothing: it goes where it goes.
|
|
41667
41787
|
releaseHeldPage();
|
|
41668
41788
|
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
41669
|
-
row.setAttribute("data-route-row-taken", "
|
|
41789
|
+
row.setAttribute("data-route-row-taken", "");
|
|
41670
41790
|
afterFrames(2, () => {
|
|
41671
41791
|
if (movementRef.current !== movement) {
|
|
41672
41792
|
return;
|
|
41673
41793
|
}
|
|
41674
|
-
|
|
41794
|
+
// A scroller again, standing where no page does: not snapped meanwhile.
|
|
41795
|
+
row.setAttribute("data-route-row-travelling", "");
|
|
41796
|
+
row.removeAttribute("data-route-row-taken");
|
|
41675
41797
|
row.style.removeProperty("--navi-route-row-stand");
|
|
41676
41798
|
afterFrames(1, () => {
|
|
41677
41799
|
if (movementRef.current === movement) {
|
|
@@ -41690,8 +41812,11 @@ const RouteTravel = ({
|
|
|
41690
41812
|
return;
|
|
41691
41813
|
}
|
|
41692
41814
|
movementRef.current = null;
|
|
41815
|
+
if (movement.playing) {
|
|
41816
|
+
movement.playing.stop();
|
|
41817
|
+
}
|
|
41693
41818
|
putRowBack(movement);
|
|
41694
|
-
row.removeAttribute("data-
|
|
41819
|
+
row.removeAttribute("data-route-row-travelling");
|
|
41695
41820
|
row.removeAttribute("data-route-row-taken");
|
|
41696
41821
|
publishRouteRowMove(null);
|
|
41697
41822
|
releaseHeldPage();
|
|
@@ -41720,6 +41845,45 @@ const RouteTravel = ({
|
|
|
41720
41845
|
render();
|
|
41721
41846
|
};
|
|
41722
41847
|
|
|
41848
|
+
// The row may have stopped: on the page of the address, the movement is over; on
|
|
41849
|
+
// another, a hand put it there and the address follows.
|
|
41850
|
+
const landRow = () => {
|
|
41851
|
+
const row = rowRef.current;
|
|
41852
|
+
const movement = movementRef.current;
|
|
41853
|
+
// A travel playing says itself when it is over.
|
|
41854
|
+
if (!movement || movement.playing || isRowTaken(row)) {
|
|
41855
|
+
return;
|
|
41856
|
+
}
|
|
41857
|
+
const panes = panesOf(row);
|
|
41858
|
+
const pane = pageAtRest(row, panes, ROW_READING);
|
|
41859
|
+
if (!pane) {
|
|
41860
|
+
// A travel a hand took over (a trackpad, a wheel) and left between two pages:
|
|
41861
|
+
// sent again, the row then rests.
|
|
41862
|
+
const {
|
|
41863
|
+
currentIndex
|
|
41864
|
+
} = latestRef.current;
|
|
41865
|
+
if (movement.travelling && panes[currentIndex]) {
|
|
41866
|
+
travelRowTo(currentIndex, movement);
|
|
41867
|
+
}
|
|
41868
|
+
return;
|
|
41869
|
+
}
|
|
41870
|
+
movement.landed = true;
|
|
41871
|
+
const {
|
|
41872
|
+
pages
|
|
41873
|
+
} = latestRef.current;
|
|
41874
|
+
const page = pages[panes.indexOf(pane)];
|
|
41875
|
+
if (!page || pageIsCurrent(page)) {
|
|
41876
|
+
if (!askedPageRef.current || samePage(askedPageRef.current, page)) {
|
|
41877
|
+
endMovement();
|
|
41878
|
+
}
|
|
41879
|
+
return;
|
|
41880
|
+
}
|
|
41881
|
+
if (samePage(askedPageRef.current, page)) {
|
|
41882
|
+
return;
|
|
41883
|
+
}
|
|
41884
|
+
askForPageOfRow(panes.indexOf(pane), movement);
|
|
41885
|
+
};
|
|
41886
|
+
|
|
41723
41887
|
// The address and the row, in step. The opening and a row given again around the
|
|
41724
41888
|
// page on screen have nothing to show for it; somebody else moving the address
|
|
41725
41889
|
// (a tab pressed, the back button) is a travel.
|
|
@@ -41740,6 +41904,13 @@ const RouteTravel = ({
|
|
|
41740
41904
|
}
|
|
41741
41905
|
answeredPageRef.current = page;
|
|
41742
41906
|
if (!page || !pane) {
|
|
41907
|
+
// The address left the row while a travel nobody's hand is on was playing (a
|
|
41908
|
+
// link to a page off the row): there is nothing left to travel to, and the
|
|
41909
|
+
// landing would send the address back to the page the row rests on.
|
|
41910
|
+
const movement = movementRef.current;
|
|
41911
|
+
if (!page && movement?.travelling && !movement.byHand) {
|
|
41912
|
+
Promise.resolve().then(endMovement);
|
|
41913
|
+
}
|
|
41743
41914
|
return;
|
|
41744
41915
|
}
|
|
41745
41916
|
const asked = askedPageRef.current;
|
|
@@ -41769,7 +41940,7 @@ const RouteTravel = ({
|
|
|
41769
41940
|
takeRowFromHand(movement);
|
|
41770
41941
|
return;
|
|
41771
41942
|
}
|
|
41772
|
-
if (row.
|
|
41943
|
+
if (!row.hasAttribute("data-route-row-taken")) {
|
|
41773
41944
|
travelRowTo(currentIndex, movement);
|
|
41774
41945
|
}
|
|
41775
41946
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
@@ -41794,8 +41965,7 @@ const RouteTravel = ({
|
|
|
41794
41965
|
if (asked || currentIndex === -1) {
|
|
41795
41966
|
return;
|
|
41796
41967
|
}
|
|
41797
|
-
|
|
41798
|
-
if (pathOf(pages[currentIndex]) === path || !pages.some(page => pathOf(page) === path)) {
|
|
41968
|
+
if (pageIsAt(pages[currentIndex], url) || !pages.some(page => pageIsAt(page, url))) {
|
|
41799
41969
|
return;
|
|
41800
41970
|
}
|
|
41801
41971
|
const movement = beginMovement({
|
|
@@ -41806,7 +41976,6 @@ const RouteTravel = ({
|
|
|
41806
41976
|
}, [windowed]);
|
|
41807
41977
|
useLayoutEffect(() => {
|
|
41808
41978
|
const row = rowRef.current;
|
|
41809
|
-
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
41810
41979
|
const onScroll = ({
|
|
41811
41980
|
finger,
|
|
41812
41981
|
lifted
|
|
@@ -41817,10 +41986,15 @@ const RouteTravel = ({
|
|
|
41817
41986
|
travelByDrag
|
|
41818
41987
|
} = latestRef.current;
|
|
41819
41988
|
const panes = panesOf(row);
|
|
41820
|
-
if (panes.length < 2 ||
|
|
41989
|
+
if (panes.length < 2 || isRowTaken(row)) {
|
|
41821
41990
|
return;
|
|
41822
41991
|
}
|
|
41823
41992
|
const moving = movementRef.current;
|
|
41993
|
+
// Not where the travel put it: a hand has taken the row (a trackpad, a wheel),
|
|
41994
|
+
// and the travel lets it go, snapping included, as the browser's own scroll does.
|
|
41995
|
+
if (moving?.playing && Math.abs(row.scrollLeft - moving.playing.written) > WRITE_ROUNDING) {
|
|
41996
|
+
moving.playing.stop();
|
|
41997
|
+
}
|
|
41824
41998
|
const pane = panes[currentIndex];
|
|
41825
41999
|
if (!moving && pane && restsOn(row, pane, ROW_READING)) {
|
|
41826
42000
|
// Written by this box, at rest: nobody is moving anything.
|
|
@@ -41900,60 +42074,20 @@ const RouteTravel = ({
|
|
|
41900
42074
|
// address ran ahead to, even for a render.
|
|
41901
42075
|
releaseHeldPage();
|
|
41902
42076
|
};
|
|
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
42077
|
const stopWatching = watchRowScroll(row, {
|
|
41940
42078
|
onScroll,
|
|
41941
|
-
onLand,
|
|
42079
|
+
onLand: landRow,
|
|
41942
42080
|
onLeftToSnap
|
|
41943
42081
|
});
|
|
41944
42082
|
// 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
|
-
//
|
|
42083
|
+
// stands where the travel was going, as in a SlideContainer. Not a wheel: a page
|
|
42084
|
+
// scrolled vertically over the row is no hand on it, and one scrolling it sideways
|
|
42085
|
+
// is told apart by the travel itself (onScroll).
|
|
41947
42086
|
const onTouchOnTravel = () => {
|
|
41948
|
-
if (movementRef.current?.travelling && !
|
|
42087
|
+
if (movementRef.current?.travelling && !isRowTaken(row)) {
|
|
41949
42088
|
endMovement();
|
|
41950
42089
|
}
|
|
41951
42090
|
};
|
|
41952
|
-
const onWheelOnTravel = () => {
|
|
41953
|
-
if (row.getAttribute("data-route-row-taken") === "") {
|
|
41954
|
-
row.removeAttribute("data-route-row-taken");
|
|
41955
|
-
}
|
|
41956
|
-
};
|
|
41957
42091
|
// A press for the page the address is already on changes nothing the router can
|
|
41958
42092
|
// tell, and a stroke no lift ends leaves the address where it was: it is heard here,
|
|
41959
42093
|
// the way a tab says which page it is through its address.
|
|
@@ -41968,7 +42102,7 @@ const RouteTravel = ({
|
|
|
41968
42102
|
currentIndex
|
|
41969
42103
|
} = latestRef.current;
|
|
41970
42104
|
const page = pages[currentIndex];
|
|
41971
|
-
if (!link || !page ||
|
|
42105
|
+
if (!link || !page || !pageIsAt(page, link.href)) {
|
|
41972
42106
|
return;
|
|
41973
42107
|
}
|
|
41974
42108
|
movement.travelling = true;
|
|
@@ -41977,20 +42111,20 @@ const RouteTravel = ({
|
|
|
41977
42111
|
row.addEventListener("touchstart", onTouchOnTravel, {
|
|
41978
42112
|
passive: true
|
|
41979
42113
|
});
|
|
41980
|
-
row.addEventListener("wheel", onWheelOnTravel, {
|
|
41981
|
-
passive: true
|
|
41982
|
-
});
|
|
41983
42114
|
document.addEventListener("click", onPress, {
|
|
41984
42115
|
capture: true
|
|
41985
42116
|
});
|
|
41986
42117
|
return () => {
|
|
41987
42118
|
stopWatching();
|
|
41988
42119
|
row.removeEventListener("touchstart", onTouchOnTravel);
|
|
41989
|
-
row.removeEventListener("wheel", onWheelOnTravel);
|
|
41990
42120
|
document.removeEventListener("click", onPress, {
|
|
41991
42121
|
capture: true
|
|
41992
42122
|
});
|
|
41993
|
-
movementRef.current
|
|
42123
|
+
const movement = movementRef.current;
|
|
42124
|
+
if (movement) {
|
|
42125
|
+
movement.playing?.stop();
|
|
42126
|
+
movement.resumeScrollRecording?.();
|
|
42127
|
+
}
|
|
41994
42128
|
movementRef.current = null;
|
|
41995
42129
|
publishRouteRowMove(null);
|
|
41996
42130
|
releaseHeldPage();
|
|
@@ -42050,12 +42184,10 @@ const RouteTravel = ({
|
|
|
42050
42184
|
}, [prefetch, neighbours, restCount]);
|
|
42051
42185
|
|
|
42052
42186
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
42053
|
-
// any scroll event
|
|
42054
|
-
|
|
42187
|
+
// any scroll event, so it has to stand where the reader is looking by then: a
|
|
42188
|
+
// finger landing is the last word before that. A trackpad says nothing first, and
|
|
42189
|
+
// the pin comes with its first scroll event (beginMovement).
|
|
42055
42190
|
useLayoutEffect(() => {
|
|
42056
|
-
if (!paintsNeighbours) {
|
|
42057
|
-
return undefined;
|
|
42058
|
-
}
|
|
42059
42191
|
const row = rowRef.current;
|
|
42060
42192
|
const onTouchStart = () => {
|
|
42061
42193
|
if (!movementRef.current && rowFollowsDocument(row)) {
|
|
@@ -42068,7 +42200,8 @@ const RouteTravel = ({
|
|
|
42068
42200
|
return () => {
|
|
42069
42201
|
row.removeEventListener("touchstart", onTouchStart);
|
|
42070
42202
|
};
|
|
42071
|
-
}, [
|
|
42203
|
+
}, []);
|
|
42204
|
+
const paintsNeighbours = Boolean(renderNeighbour);
|
|
42072
42205
|
const rowContext = useMemo(() => ({
|
|
42073
42206
|
renderNeighbour
|
|
42074
42207
|
}), [renderNeighbour]);
|
|
@@ -42152,6 +42285,10 @@ const copyScrollPositions = (from, to) => {
|
|
|
42152
42285
|
}
|
|
42153
42286
|
}
|
|
42154
42287
|
};
|
|
42288
|
+
|
|
42289
|
+
// A tab pressed over a row a hand has left moving holds it still (takeRowFromHand):
|
|
42290
|
+
// what the row says meanwhile is not a hand moving it.
|
|
42291
|
+
const isRowTaken = row => row.hasAttribute("data-route-row-taken");
|
|
42155
42292
|
const afterFrames = (count, callback) => {
|
|
42156
42293
|
if (count === 0) {
|
|
42157
42294
|
callback();
|
|
@@ -69174,15 +69311,6 @@ const lineAreas = (slideElements, layout) => {
|
|
|
69174
69311
|
return parseAreas(layout === "column" ? names : [names.join(" ")]);
|
|
69175
69312
|
};
|
|
69176
69313
|
|
|
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
69314
|
// What asked for a travel, read off the event that carried it: the hand said
|
|
69187
69315
|
// "somewhere over there" (a gesture one browses with), or it said a name (a tab
|
|
69188
69316
|
// pressed, a key, a command — a place aimed at). Nothing at all when the travel
|