@jsenv/navi 0.30.5 → 0.30.7
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 +480 -86
- package/dist/dev/jsenv_navi.js.map +9 -8
- package/dist/jsenv_navi.js +476 -85
- package/dist/jsenv_navi.js.map +9 -8
- package/docs/drag_to_travel.md +17 -4
- package/docs/mobile_touch.md +64 -5
- package/docs/navigation.md +4 -3
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -24953,7 +24953,7 @@ defineInteractionDetector({
|
|
|
24953
24953
|
const transitionAfter = peekLastViewTransition();
|
|
24954
24954
|
const drawn =
|
|
24955
24955
|
transitionAfter === transitionBefore
|
|
24956
|
-
? afterFrames(2)
|
|
24956
|
+
? afterFrames$1(2)
|
|
24957
24957
|
: transitionAfter.finished.then(NOOP, NOOP);
|
|
24958
24958
|
const write = {
|
|
24959
24959
|
settled: false,
|
|
@@ -25212,7 +25212,7 @@ defineInteractionDetector({
|
|
|
25212
25212
|
|
|
25213
25213
|
const NOOP = () => {};
|
|
25214
25214
|
|
|
25215
|
-
const afterFrames = (count) =>
|
|
25215
|
+
const afterFrames$1 = (count) =>
|
|
25216
25216
|
new Promise((resolve) => {
|
|
25217
25217
|
const next = (remaining) => {
|
|
25218
25218
|
if (remaining === 0) {
|
|
@@ -40302,8 +40302,9 @@ const publishRouteRowMove = (move) => {
|
|
|
40302
40302
|
* itself; a counter, a title or a breadcrumb around the box reads it first.
|
|
40303
40303
|
*
|
|
40304
40304
|
* It goes back to `null` as soon as the hand has let go and the row moves. Nothing
|
|
40305
|
-
* says when the fingers leave a trackpad: there it
|
|
40306
|
-
* rest. A travel no hand set off holds nothing: it goes where it
|
|
40305
|
+
* says when the fingers leave a trackpad: there it is the page nearest the row, until
|
|
40306
|
+
* the row has come to rest. A travel no hand set off holds nothing: it goes where it
|
|
40307
|
+
* goes.
|
|
40307
40308
|
*
|
|
40308
40309
|
* One for the document. `route` says which row, for a page holding several boxes.
|
|
40309
40310
|
*/
|
|
@@ -40321,6 +40322,13 @@ const holdPage = (page, headingPage) => {
|
|
|
40321
40322
|
}
|
|
40322
40323
|
};
|
|
40323
40324
|
|
|
40325
|
+
// A hand no browser says has let go: the page nearest the row is where the reader is,
|
|
40326
|
+
// and the address, wherever it stands, is not.
|
|
40327
|
+
const followPageOnScreen = (page, addressPage) => {
|
|
40328
|
+
headingPageSignal.value = addressPage;
|
|
40329
|
+
routeTravelSettledPageSignal.value = page;
|
|
40330
|
+
};
|
|
40331
|
+
|
|
40324
40332
|
const releaseHeldPage = () => {
|
|
40325
40333
|
routeTravelSettledPageSignal.value = null;
|
|
40326
40334
|
headingPageSignal.value = null;
|
|
@@ -40380,17 +40388,19 @@ const STOPPED_SILENCE = 150;
|
|
|
40380
40388
|
const isOnRow = (page) =>
|
|
40381
40389
|
page.offsetWidth > 0 && !page.hasAttribute("data-off-row");
|
|
40382
40390
|
|
|
40383
|
-
|
|
40391
|
+
// `sideways`: see readRowStretch.
|
|
40392
|
+
const restsOn = (scroller, page, { sideways } = {}) =>
|
|
40384
40393
|
Math.abs(page.offsetLeft - scroller.scrollLeft) <= AT_REST_DISTANCE &&
|
|
40385
|
-
|
|
40394
|
+
(sideways ||
|
|
40395
|
+
Math.abs(page.offsetTop - scroller.scrollTop) <= AT_REST_DISTANCE);
|
|
40386
40396
|
|
|
40387
40397
|
/**
|
|
40388
40398
|
* @returns {Element|undefined} the page the row rests on, nothing while it stands
|
|
40389
40399
|
* between two: a hand can pause midway, and an engine may call that the end of
|
|
40390
40400
|
* a scroll.
|
|
40391
40401
|
*/
|
|
40392
|
-
const pageAtRest = (scroller, pages) =>
|
|
40393
|
-
pages.find((page) => isOnRow(page) && restsOn(scroller, page));
|
|
40402
|
+
const pageAtRest = (scroller, pages, options) =>
|
|
40403
|
+
pages.find((page) => isOnRow(page) && restsOn(scroller, page, options));
|
|
40394
40404
|
|
|
40395
40405
|
/**
|
|
40396
40406
|
* Where the row stands between two neighbouring pages, on the side it has moved
|
|
@@ -40398,14 +40408,18 @@ const pageAtRest = (scroller, pages) =>
|
|
|
40398
40408
|
* since is walked over. A map moves along one axis at a time, the one it has
|
|
40399
40409
|
* moved furthest on.
|
|
40400
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.
|
|
40401
40414
|
* @returns {{base: Element, toward: Element|undefined, fraction: number, forward: boolean}}
|
|
40402
40415
|
* the last page the row has passed (`page` itself until then), the one after,
|
|
40403
40416
|
* and how far between the two from 0 to 1. Nothing `toward` past the last.
|
|
40404
40417
|
*/
|
|
40405
|
-
const readRowStretch = (scroller, pages, page) => {
|
|
40418
|
+
const readRowStretch = (scroller, pages, page, { sideways } = {}) => {
|
|
40406
40419
|
const alongX =
|
|
40420
|
+
sideways ||
|
|
40407
40421
|
Math.abs(scroller.scrollLeft - page.offsetLeft) >=
|
|
40408
|
-
|
|
40422
|
+
Math.abs(scroller.scrollTop - page.offsetTop);
|
|
40409
40423
|
const position = alongX ? "offsetLeft" : "offsetTop";
|
|
40410
40424
|
const cross = alongX ? "offsetTop" : "offsetLeft";
|
|
40411
40425
|
const scrolled = alongX ? scroller.scrollLeft : scroller.scrollTop;
|
|
@@ -40413,7 +40427,8 @@ const readRowStretch = (scroller, pages, page) => {
|
|
|
40413
40427
|
const step = forward ? 1 : -1;
|
|
40414
40428
|
const line = pages
|
|
40415
40429
|
.filter(
|
|
40416
|
-
(candidate) =>
|
|
40430
|
+
(candidate) =>
|
|
40431
|
+
isOnRow(candidate) && (sideways || candidate[cross] === page[cross]),
|
|
40417
40432
|
)
|
|
40418
40433
|
.sort((pageA, pageB) => pageA[position] - pageB[position]);
|
|
40419
40434
|
let index = line.indexOf(page);
|
|
@@ -40482,12 +40497,6 @@ const watchRowScroll = (
|
|
|
40482
40497
|
liftedAt = null;
|
|
40483
40498
|
onLeftToSnap?.(moved > 0);
|
|
40484
40499
|
};
|
|
40485
|
-
const onTouchStart = () => {
|
|
40486
|
-
touching = true;
|
|
40487
|
-
touchedSinceStop = true;
|
|
40488
|
-
liftedAt = null;
|
|
40489
|
-
clearTimeout(stoppedTimeout);
|
|
40490
|
-
};
|
|
40491
40500
|
const onTouchEnd = (touchEvent) => {
|
|
40492
40501
|
touching = touchEvent.touches.length > 0;
|
|
40493
40502
|
if (!touching) {
|
|
@@ -40495,18 +40504,31 @@ const watchRowScroll = (
|
|
|
40495
40504
|
armSilence();
|
|
40496
40505
|
}
|
|
40497
40506
|
};
|
|
40507
|
+
const onTouchStart = (touchEvent) => {
|
|
40508
|
+
touching = true;
|
|
40509
|
+
touchedSinceStop = true;
|
|
40510
|
+
liftedAt = null;
|
|
40511
|
+
clearTimeout(stoppedTimeout);
|
|
40512
|
+
// Listened for on what the finger landed on, where the end of a touch is always
|
|
40513
|
+
// told: a page changing under the finger may have taken it out of the document by
|
|
40514
|
+
// then, and nothing comes up to the row from there.
|
|
40515
|
+
const { target } = touchEvent;
|
|
40516
|
+
const onEnd = (endEvent) => {
|
|
40517
|
+
target.removeEventListener("touchend", onEnd);
|
|
40518
|
+
target.removeEventListener("touchcancel", onEnd);
|
|
40519
|
+
onTouchEnd(endEvent);
|
|
40520
|
+
};
|
|
40521
|
+
target.addEventListener("touchend", onEnd, { passive: true });
|
|
40522
|
+
target.addEventListener("touchcancel", onEnd, { passive: true });
|
|
40523
|
+
};
|
|
40498
40524
|
scroller.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
40499
40525
|
scroller.addEventListener("scrollend", onLand);
|
|
40500
40526
|
scroller.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
40501
|
-
scroller.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
40502
|
-
scroller.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
40503
40527
|
return () => {
|
|
40504
40528
|
clearTimeout(stoppedTimeout);
|
|
40505
40529
|
scroller.removeEventListener("scroll", onScrollEvent);
|
|
40506
40530
|
scroller.removeEventListener("scrollend", onLand);
|
|
40507
40531
|
scroller.removeEventListener("touchstart", onTouchStart);
|
|
40508
|
-
scroller.removeEventListener("touchend", onTouchEnd);
|
|
40509
|
-
scroller.removeEventListener("touchcancel", onTouchEnd);
|
|
40510
40532
|
};
|
|
40511
40533
|
};
|
|
40512
40534
|
|
|
@@ -40544,6 +40566,19 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
40544
40566
|
overflow: visible;
|
|
40545
40567
|
}
|
|
40546
40568
|
|
|
40569
|
+
&[data-route-row-taken] {
|
|
40570
|
+
scroll-snap-type: none;
|
|
40571
|
+
}
|
|
40572
|
+
|
|
40573
|
+
&[data-route-row-taken="clip"] {
|
|
40574
|
+
overflow: clip;
|
|
40575
|
+
|
|
40576
|
+
& > [data-route-row-pane], & > [data-route-row-live], & > [data-route-row-pictures] > * {
|
|
40577
|
+
left: var(--navi-route-row-stand);
|
|
40578
|
+
position: relative;
|
|
40579
|
+
}
|
|
40580
|
+
}
|
|
40581
|
+
|
|
40547
40582
|
& > [data-route-row-pane] {
|
|
40548
40583
|
scroll-snap-align: start;
|
|
40549
40584
|
scroll-snap-stop: always;
|
|
@@ -40600,6 +40635,11 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
40600
40635
|
// there yet: past what a hand that only shook covers, so that a shake costs
|
|
40601
40636
|
// neither a navigation nor the requests of a page.
|
|
40602
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
|
+
};
|
|
40603
40643
|
|
|
40604
40644
|
/**
|
|
40605
40645
|
* @type {import("preact").FunctionComponent<{
|
|
@@ -40773,10 +40813,7 @@ const RouteTravel = ({
|
|
|
40773
40813
|
const paneCurrent = panesOf(row)[currentIndex];
|
|
40774
40814
|
if (paneCurrent) {
|
|
40775
40815
|
movement.travelling = true;
|
|
40776
|
-
|
|
40777
|
-
left: paneCurrent.offsetLeft,
|
|
40778
|
-
behavior: "smooth"
|
|
40779
|
-
});
|
|
40816
|
+
travelRowTo(currentIndex, movement);
|
|
40780
40817
|
}
|
|
40781
40818
|
});
|
|
40782
40819
|
};
|
|
@@ -40793,7 +40830,7 @@ const RouteTravel = ({
|
|
|
40793
40830
|
base,
|
|
40794
40831
|
toward,
|
|
40795
40832
|
fraction
|
|
40796
|
-
} =
|
|
40833
|
+
} = readRowFrame(row, panes);
|
|
40797
40834
|
publishRouteRowMove({
|
|
40798
40835
|
basePath: pathOf(pages[panes.indexOf(base)]),
|
|
40799
40836
|
towardPath: toward ? pathOf(pages[panes.indexOf(toward)]) : null,
|
|
@@ -40801,6 +40838,108 @@ const RouteTravel = ({
|
|
|
40801
40838
|
});
|
|
40802
40839
|
};
|
|
40803
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
|
+
|
|
40916
|
+
// A tab pressed over a row a hand has left moving, which the browser plays on and
|
|
40917
|
+
// overwrites a travel with once that travel is over: the snap is ended first. Given
|
|
40918
|
+
// back, the row stands where it stood. A frame later, because until then WebKit still
|
|
40919
|
+
// starts from where a box that is no scroller stood: a smooth scroll to the first page
|
|
40920
|
+
// sees nothing to do.
|
|
40921
|
+
const takeRowFromHand = movement => {
|
|
40922
|
+
const row = rowRef.current;
|
|
40923
|
+
const position = row.scrollLeft;
|
|
40924
|
+
movement.rowTaken = true;
|
|
40925
|
+
// A travel no hand set off holds nothing: it goes where it goes.
|
|
40926
|
+
releaseHeldPage();
|
|
40927
|
+
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
40928
|
+
row.setAttribute("data-route-row-taken", "clip");
|
|
40929
|
+
afterFrames(2, () => {
|
|
40930
|
+
if (movementRef.current !== movement) {
|
|
40931
|
+
return;
|
|
40932
|
+
}
|
|
40933
|
+
row.setAttribute("data-route-row-taken", "");
|
|
40934
|
+
row.style.removeProperty("--navi-route-row-stand");
|
|
40935
|
+
afterFrames(1, () => {
|
|
40936
|
+
if (movementRef.current === movement) {
|
|
40937
|
+
travelRowTo(latestRef.current.currentIndex, movement);
|
|
40938
|
+
}
|
|
40939
|
+
});
|
|
40940
|
+
});
|
|
40941
|
+
};
|
|
40942
|
+
|
|
40804
40943
|
// The row has stopped, on the page of the address: everything held for the
|
|
40805
40944
|
// movement is let go of in one go, before anything paints.
|
|
40806
40945
|
const endMovement = () => {
|
|
@@ -40810,7 +40949,9 @@ const RouteTravel = ({
|
|
|
40810
40949
|
return;
|
|
40811
40950
|
}
|
|
40812
40951
|
movementRef.current = null;
|
|
40952
|
+
putRowBack(movement);
|
|
40813
40953
|
row.removeAttribute("data-moving");
|
|
40954
|
+
row.removeAttribute("data-route-row-taken");
|
|
40814
40955
|
publishRouteRowMove(null);
|
|
40815
40956
|
releaseHeldPage();
|
|
40816
40957
|
if (liveRef.current) {
|
|
@@ -40848,7 +40989,7 @@ const RouteTravel = ({
|
|
|
40848
40989
|
const pane = panesOf(row)[currentIndex];
|
|
40849
40990
|
const answered = answeredPageRef.current;
|
|
40850
40991
|
if (samePage(answered, page)) {
|
|
40851
|
-
if (pane && !movementRef.current && !restsOn(row, pane)) {
|
|
40992
|
+
if (pane && !movementRef.current && !restsOn(row, pane, ROW_READING)) {
|
|
40852
40993
|
row.scrollTo({
|
|
40853
40994
|
left: pane.offsetLeft,
|
|
40854
40995
|
behavior: "instant"
|
|
@@ -40864,13 +41005,13 @@ const RouteTravel = ({
|
|
|
40864
41005
|
if (asked && samePage(asked, page)) {
|
|
40865
41006
|
askedPageRef.current = null;
|
|
40866
41007
|
// The hand landed before the address did.
|
|
40867
|
-
if (movementRef.current?.landed && restsOn(row, pane)) {
|
|
41008
|
+
if (movementRef.current?.landed && restsOn(row, pane, ROW_READING)) {
|
|
40868
41009
|
Promise.resolve().then(endMovement);
|
|
40869
41010
|
}
|
|
40870
41011
|
return;
|
|
40871
41012
|
}
|
|
40872
|
-
if (!answered || restsOn(row, pane)) {
|
|
40873
|
-
if (!restsOn(row, pane)) {
|
|
41013
|
+
if (!answered || restsOn(row, pane, ROW_READING)) {
|
|
41014
|
+
if (!restsOn(row, pane, ROW_READING)) {
|
|
40874
41015
|
row.scrollTo({
|
|
40875
41016
|
left: pane.offsetLeft,
|
|
40876
41017
|
behavior: "instant"
|
|
@@ -40878,13 +41019,18 @@ const RouteTravel = ({
|
|
|
40878
41019
|
}
|
|
40879
41020
|
return;
|
|
40880
41021
|
}
|
|
40881
|
-
|
|
41022
|
+
const movementBefore = movementRef.current;
|
|
41023
|
+
const movement = beginMovement({
|
|
40882
41024
|
travelling: true
|
|
40883
41025
|
});
|
|
40884
|
-
|
|
40885
|
-
|
|
40886
|
-
|
|
40887
|
-
|
|
41026
|
+
if (movementBefore?.byHand && !movementBefore.rowTaken) {
|
|
41027
|
+
sayRowMoves();
|
|
41028
|
+
takeRowFromHand(movement);
|
|
41029
|
+
return;
|
|
41030
|
+
}
|
|
41031
|
+
if (row.getAttribute("data-route-row-taken") !== "clip") {
|
|
41032
|
+
travelRowTo(currentIndex, movement);
|
|
41033
|
+
}
|
|
40888
41034
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
40889
41035
|
// scroll event is a frame away.
|
|
40890
41036
|
sayRowMoves();
|
|
@@ -40919,27 +41065,43 @@ const RouteTravel = ({
|
|
|
40919
41065
|
}, [windowed]);
|
|
40920
41066
|
useLayoutEffect(() => {
|
|
40921
41067
|
const row = rowRef.current;
|
|
40922
|
-
const
|
|
41068
|
+
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
41069
|
+
const onScroll = ({
|
|
41070
|
+
finger
|
|
41071
|
+
}) => {
|
|
40923
41072
|
const {
|
|
40924
41073
|
pages,
|
|
40925
41074
|
currentIndex,
|
|
40926
41075
|
travelByDrag
|
|
40927
41076
|
} = latestRef.current;
|
|
40928
41077
|
const panes = panesOf(row);
|
|
40929
|
-
if (panes.length < 2) {
|
|
41078
|
+
if (panes.length < 2 || isClipped()) {
|
|
40930
41079
|
return;
|
|
40931
41080
|
}
|
|
40932
41081
|
const moving = movementRef.current;
|
|
40933
41082
|
const pane = panes[currentIndex];
|
|
40934
|
-
if (!moving && pane && restsOn(row, pane)) {
|
|
41083
|
+
if (!moving && pane && restsOn(row, pane, ROW_READING)) {
|
|
40935
41084
|
// Written by this box, at rest: nobody is moving anything.
|
|
40936
41085
|
return;
|
|
40937
41086
|
}
|
|
40938
|
-
const movement = moving || beginMovement({
|
|
41087
|
+
const movement = moving || beginMovement({
|
|
41088
|
+
byHand: true
|
|
41089
|
+
});
|
|
40939
41090
|
sayRowMoves();
|
|
40940
41091
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
40941
41092
|
return;
|
|
40942
41093
|
}
|
|
41094
|
+
// Nothing will say when a trackpad's fingers leave, and a long stroke would keep
|
|
41095
|
+
// the first tab lit for the whole trip: the nearest page answers. A finger has a
|
|
41096
|
+
// lift to wait for (onLeftToSnap), and until then the page held stands.
|
|
41097
|
+
if (!finger) {
|
|
41098
|
+
const {
|
|
41099
|
+
base,
|
|
41100
|
+
toward,
|
|
41101
|
+
fraction
|
|
41102
|
+
} = readRowFrame(row, panes);
|
|
41103
|
+
followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
|
|
41104
|
+
}
|
|
40943
41105
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
40944
41106
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
40945
41107
|
return;
|
|
@@ -40977,7 +41139,7 @@ const RouteTravel = ({
|
|
|
40977
41139
|
const {
|
|
40978
41140
|
base,
|
|
40979
41141
|
toward
|
|
40980
|
-
} =
|
|
41142
|
+
} = readRowFrame(row, panes);
|
|
40981
41143
|
const index = panes.indexOf(forward && toward ? toward : base);
|
|
40982
41144
|
const page = pages[index];
|
|
40983
41145
|
if (!page) {
|
|
@@ -40992,12 +41154,22 @@ const RouteTravel = ({
|
|
|
40992
41154
|
};
|
|
40993
41155
|
const onLand = () => {
|
|
40994
41156
|
const movement = movementRef.current;
|
|
40995
|
-
if (!movement) {
|
|
41157
|
+
if (!movement || isClipped()) {
|
|
40996
41158
|
return;
|
|
40997
41159
|
}
|
|
40998
41160
|
const panes = panesOf(row);
|
|
40999
|
-
const pane = pageAtRest(row, panes);
|
|
41161
|
+
const pane = pageAtRest(row, panes, ROW_READING);
|
|
41000
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
|
+
}
|
|
41001
41173
|
return;
|
|
41002
41174
|
}
|
|
41003
41175
|
movement.landed = true;
|
|
@@ -41021,8 +41193,55 @@ const RouteTravel = ({
|
|
|
41021
41193
|
onLand,
|
|
41022
41194
|
onLeftToSnap
|
|
41023
41195
|
});
|
|
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 = () => {
|
|
41205
|
+
if (row.getAttribute("data-route-row-taken") === "") {
|
|
41206
|
+
row.removeAttribute("data-route-row-taken");
|
|
41207
|
+
}
|
|
41208
|
+
};
|
|
41209
|
+
// A press for the page the address is already on changes nothing the router can
|
|
41210
|
+
// tell, and a stroke no lift ends leaves the address where it was: it is heard here,
|
|
41211
|
+
// the way a tab says which page it is through its address.
|
|
41212
|
+
const onPress = clickEvent => {
|
|
41213
|
+
const movement = movementRef.current;
|
|
41214
|
+
if (!movement?.byHand || movement.rowTaken) {
|
|
41215
|
+
return;
|
|
41216
|
+
}
|
|
41217
|
+
const link = clickEvent.target.closest?.("a[href]");
|
|
41218
|
+
const {
|
|
41219
|
+
pages,
|
|
41220
|
+
currentIndex
|
|
41221
|
+
} = latestRef.current;
|
|
41222
|
+
const page = pages[currentIndex];
|
|
41223
|
+
if (!link || !page || new URL(link.href).pathname !== pathOf(page)) {
|
|
41224
|
+
return;
|
|
41225
|
+
}
|
|
41226
|
+
movement.travelling = true;
|
|
41227
|
+
takeRowFromHand(movement);
|
|
41228
|
+
};
|
|
41229
|
+
row.addEventListener("touchstart", onTouchOnTravel, {
|
|
41230
|
+
passive: true
|
|
41231
|
+
});
|
|
41232
|
+
row.addEventListener("wheel", onWheelOnTravel, {
|
|
41233
|
+
passive: true
|
|
41234
|
+
});
|
|
41235
|
+
document.addEventListener("click", onPress, {
|
|
41236
|
+
capture: true
|
|
41237
|
+
});
|
|
41024
41238
|
return () => {
|
|
41025
41239
|
stopWatching();
|
|
41240
|
+
row.removeEventListener("touchstart", onTouchOnTravel);
|
|
41241
|
+
row.removeEventListener("wheel", onWheelOnTravel);
|
|
41242
|
+
document.removeEventListener("click", onPress, {
|
|
41243
|
+
capture: true
|
|
41244
|
+
});
|
|
41026
41245
|
movementRef.current?.resumeScrollRecording?.();
|
|
41027
41246
|
movementRef.current = null;
|
|
41028
41247
|
publishRouteRowMove(null);
|
|
@@ -41038,7 +41257,7 @@ const RouteTravel = ({
|
|
|
41038
41257
|
return;
|
|
41039
41258
|
}
|
|
41040
41259
|
const pane = panesOf(rowRef.current)[currentIndex];
|
|
41041
|
-
if (pane && restsOn(rowRef.current, pane)) {
|
|
41260
|
+
if (pane && restsOn(rowRef.current, pane, ROW_READING)) {
|
|
41042
41261
|
Promise.resolve().then(endMovement);
|
|
41043
41262
|
}
|
|
41044
41263
|
}, [currentIndex]);
|
|
@@ -41146,6 +41365,18 @@ const copyScrollPositions = (from, to) => {
|
|
|
41146
41365
|
}
|
|
41147
41366
|
}
|
|
41148
41367
|
};
|
|
41368
|
+
const afterFrames = (count, callback) => {
|
|
41369
|
+
if (count === 0) {
|
|
41370
|
+
callback();
|
|
41371
|
+
return;
|
|
41372
|
+
}
|
|
41373
|
+
requestAnimationFrame(() => {
|
|
41374
|
+
afterFrames(count - 1, callback);
|
|
41375
|
+
});
|
|
41376
|
+
};
|
|
41377
|
+
|
|
41378
|
+
// The two pages the row stands between.
|
|
41379
|
+
const readRowFrame = (row, panes) => readRowStretch(row, panes, panes[0], ROW_READING);
|
|
41149
41380
|
|
|
41150
41381
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
41151
41382
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
@@ -56641,8 +56872,9 @@ const wordsOf = (element, attribute) => {
|
|
|
56641
56872
|
return value ? value.split(" ") : NO_WAYS;
|
|
56642
56873
|
};
|
|
56643
56874
|
|
|
56644
|
-
// The slide
|
|
56645
|
-
//
|
|
56875
|
+
// The slide one is on, for whatever tells the reader where they are or what a press
|
|
56876
|
+
// would do: the current one while a hand holds the row, where the row is going once
|
|
56877
|
+
// it has let go. data-slide-current only changes at rest.
|
|
56646
56878
|
const readSlideOnScreen = (element) =>
|
|
56647
56879
|
element.getAttribute(SLIDE_HEADING_ATTRIBUTE) ??
|
|
56648
56880
|
element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ??
|
|
@@ -56692,10 +56924,9 @@ const sameSlideContainerState = (a, b) =>
|
|
|
56692
56924
|
* has let go, because that is what one is looking at. It can come before the
|
|
56693
56925
|
* slide's own signal and `onCurrentChange`, which wait for the row to rest.
|
|
56694
56926
|
* `toward` is the other slide in the frame while the picture is between two,
|
|
56695
|
-
* and nothing at rest. `can` is "asking for it would do something", from
|
|
56696
|
-
* slide
|
|
56697
|
-
*
|
|
56698
|
-
* the reader.
|
|
56927
|
+
* and nothing at rest. `can` is "asking for it would do something", from that
|
|
56928
|
+
* same slide, `held` is "it is there and this slide says no" — a way out is dead
|
|
56929
|
+
* in both cases, and only the second is worth a word to the reader.
|
|
56699
56930
|
* `known` is false until the box has been read: no box was named and none is
|
|
56700
56931
|
* above, the id names nothing, or — for one commit — this mounted before the
|
|
56701
56932
|
* box had painted. Until then `can` answers YES, because it is the answer that
|
|
@@ -67667,6 +67898,10 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
67667
67898
|
overflow-y: auto;
|
|
67668
67899
|
}
|
|
67669
67900
|
|
|
67901
|
+
&[data-slide-row-taken] {
|
|
67902
|
+
overflow: clip;
|
|
67903
|
+
}
|
|
67904
|
+
|
|
67670
67905
|
&:focus {
|
|
67671
67906
|
outline: none;
|
|
67672
67907
|
}
|
|
@@ -67718,6 +67953,77 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
67718
67953
|
}
|
|
67719
67954
|
`;
|
|
67720
67955
|
|
|
67956
|
+
// What a row left moving shows when a travel is asked for over it, read before the
|
|
67957
|
+
// slides are written: the one or two slides in the frame, and how far the track has
|
|
67958
|
+
// to stand, in a box that is no scroller, for that frame not to move.
|
|
67959
|
+
const readRowStand = (containerEl, slideElements) => {
|
|
67960
|
+
const restElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
67961
|
+
if (!restElement) {
|
|
67962
|
+
return null;
|
|
67963
|
+
}
|
|
67964
|
+
const {
|
|
67965
|
+
base,
|
|
67966
|
+
toward,
|
|
67967
|
+
fraction
|
|
67968
|
+
} = readRowStretch(containerEl, slideElements, restElement);
|
|
67969
|
+
return {
|
|
67970
|
+
base: readArea(base),
|
|
67971
|
+
toward: toward && fraction > 0 ? readArea(toward) : null,
|
|
67972
|
+
fraction,
|
|
67973
|
+
offset: `${-containerEl.scrollLeft}px ${-containerEl.scrollTop}px`
|
|
67974
|
+
};
|
|
67975
|
+
};
|
|
67976
|
+
// The stage of a travel asked for over a row left moving. What is in the frame stays
|
|
67977
|
+
// where it is, and the slide asked for comes one box past it, unless it is already
|
|
67978
|
+
// there: then what is left to make is a part of a box.
|
|
67979
|
+
const stageOverRow = (rowStand, area, realPlaceOf, stepAsked) => {
|
|
67980
|
+
const {
|
|
67981
|
+
base,
|
|
67982
|
+
toward,
|
|
67983
|
+
fraction
|
|
67984
|
+
} = rowStand;
|
|
67985
|
+
const basePlace = realPlaceOf(base);
|
|
67986
|
+
const placeByArea = new Map([[base, basePlace]]);
|
|
67987
|
+
const towardPlace = toward ? realPlaceOf(toward) : null;
|
|
67988
|
+
if (toward) {
|
|
67989
|
+
placeByArea.set(toward, towardPlace);
|
|
67990
|
+
}
|
|
67991
|
+
if (area === base || area === toward) {
|
|
67992
|
+
const otherArea = area === base ? toward : base;
|
|
67993
|
+
if (!otherArea) {
|
|
67994
|
+
return {
|
|
67995
|
+
placeByArea,
|
|
67996
|
+
leaningArea: null,
|
|
67997
|
+
progress: 0
|
|
67998
|
+
};
|
|
67999
|
+
}
|
|
68000
|
+
const from = realPlaceOf(otherArea);
|
|
68001
|
+
const to = realPlaceOf(area);
|
|
68002
|
+
const sign = Math.sign(to.x - from.x) || Math.sign(to.y - from.y);
|
|
68003
|
+
return {
|
|
68004
|
+
placeByArea,
|
|
68005
|
+
leaningArea: otherArea,
|
|
68006
|
+
progress: sign * (area === base ? fraction : 1 - fraction)
|
|
68007
|
+
};
|
|
68008
|
+
}
|
|
68009
|
+
const to = realPlaceOf(area);
|
|
68010
|
+
const step = stepAsked || {
|
|
68011
|
+
x: Math.sign(to.x - basePlace.x),
|
|
68012
|
+
y: Math.sign(to.y - basePlace.y)
|
|
68013
|
+
};
|
|
68014
|
+
const pastToward = towardPlace && (towardPlace.x - basePlace.x) * step.x + (towardPlace.y - basePlace.y) * step.y > 0;
|
|
68015
|
+
const side = pastToward ? towardPlace : basePlace;
|
|
68016
|
+
placeByArea.set(area, {
|
|
68017
|
+
x: side.x + step.x,
|
|
68018
|
+
y: side.y + step.y
|
|
68019
|
+
});
|
|
68020
|
+
return {
|
|
68021
|
+
placeByArea,
|
|
68022
|
+
leaningArea: toward && fraction >= 0.5 ? toward : base,
|
|
68023
|
+
progress: step.x || step.y
|
|
68024
|
+
};
|
|
68025
|
+
};
|
|
68026
|
+
|
|
67721
68027
|
// How much of a travel is left to make, as a fraction of the travel that was
|
|
67722
68028
|
// asked for: 1 for a move starting from rest, less for one picked up while the
|
|
67723
68029
|
// track was already moving. Everything is measured in px against the track's
|
|
@@ -67734,6 +68040,19 @@ const ratioOfOneTravel = (track, from, to, targetBefore) => {
|
|
|
67734
68040
|
const ratio = left / asked;
|
|
67735
68041
|
return ratio > 1 ? 1 : ratio;
|
|
67736
68042
|
};
|
|
68043
|
+
// The same for a travel taken over a row, which nobody asked for from anywhere in
|
|
68044
|
+
// particular: what is left, in boxes. Laid out sizes, like the scroll position that
|
|
68045
|
+
// `from` is read off: a row inside a popup still growing is not its transformed size.
|
|
68046
|
+
const ratioOfOneBox = (track, from, to) => {
|
|
68047
|
+
const box = {
|
|
68048
|
+
width: track.offsetWidth,
|
|
68049
|
+
height: track.offsetHeight
|
|
68050
|
+
};
|
|
68051
|
+
const start = offsetToPx(from, box);
|
|
68052
|
+
const end = offsetToPx(to, box);
|
|
68053
|
+
const ratio = Math.abs(end.x - start.x) / box.width + Math.abs(end.y - start.y) / box.height;
|
|
68054
|
+
return ratio > 1 ? 1 : ratio;
|
|
68055
|
+
};
|
|
67737
68056
|
|
|
67738
68057
|
// A translate ("-100% 0%", "-260px 0px", "none") as two numbers of pixels.
|
|
67739
68058
|
// Percentages are the size of the box, which is what a translate resolves them
|
|
@@ -68311,6 +68630,10 @@ const SlideContainer = ({
|
|
|
68311
68630
|
// The slide a hand has just landed on, for the render it asks for: it is
|
|
68312
68631
|
// already there, so there is nothing to play.
|
|
68313
68632
|
const handLandedRef = useRef(null);
|
|
68633
|
+
// The slide a row is being carried away from, pressed: it stays current, so only
|
|
68634
|
+
// a render says the row has to come back to it (see goToArea).
|
|
68635
|
+
const rowBackAskedRef = useRef(false);
|
|
68636
|
+
const [, askRowBack] = useState(0);
|
|
68314
68637
|
// Where the box rests on the row, and what the row starts from: a slide that
|
|
68315
68638
|
// holds on to the user ends the row on that side, and a scroll range starts at
|
|
68316
68639
|
// its first reachable slide (see writeRowAtRest).
|
|
@@ -68550,17 +68873,23 @@ const SlideContainer = ({
|
|
|
68550
68873
|
};
|
|
68551
68874
|
|
|
68552
68875
|
// Where the row is going, for as long as the current slide has not changed:
|
|
68553
|
-
// anything saying where one is reads this first
|
|
68554
|
-
// the caller to announce, a landing
|
|
68876
|
+
// anything saying where one is reads this first, and so does what a press would
|
|
68877
|
+
// do from there (see readSlideOnScreen). Left to the caller to announce, a landing
|
|
68878
|
+
// having more to say in the same breath.
|
|
68555
68879
|
const writeHeading = headingElement => {
|
|
68556
68880
|
const containerEl = containerRef.current;
|
|
68557
68881
|
const area = headingElement ? readArea(headingElement) : null;
|
|
68558
68882
|
// A window does not change slide, it rolls: it has nowhere to be heading.
|
|
68559
|
-
|
|
68560
|
-
|
|
68883
|
+
const heading = loop || area === null || area === containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ? null : area;
|
|
68884
|
+
if (heading === containerEl.getAttribute(SLIDE_HEADING_ATTRIBUTE)) {
|
|
68561
68885
|
return;
|
|
68562
68886
|
}
|
|
68563
|
-
|
|
68887
|
+
if (heading === null) {
|
|
68888
|
+
containerEl.removeAttribute(SLIDE_HEADING_ATTRIBUTE);
|
|
68889
|
+
} else {
|
|
68890
|
+
containerEl.setAttribute(SLIDE_HEADING_ATTRIBUTE, heading);
|
|
68891
|
+
}
|
|
68892
|
+
paintWays();
|
|
68564
68893
|
};
|
|
68565
68894
|
const markAnswered = area => {
|
|
68566
68895
|
const order = readMap().slideElements.map(readArea);
|
|
@@ -68727,6 +69056,7 @@ const SlideContainer = ({
|
|
|
68727
69056
|
offsetRef.current = "0% 0%";
|
|
68728
69057
|
track.style.setProperty("--slide-container-offset", "0% 0%");
|
|
68729
69058
|
containerRef.current.removeAttribute("data-slide-travelling");
|
|
69059
|
+
containerRef.current.removeAttribute("data-slide-row-taken");
|
|
68730
69060
|
sendRowToRest(currentElement);
|
|
68731
69061
|
renderNear(currentArea, {
|
|
68732
69062
|
alongside: true
|
|
@@ -68801,10 +69131,26 @@ const SlideContainer = ({
|
|
|
68801
69131
|
// between the picture and the slide arriving, on the axis it walks.
|
|
68802
69132
|
let travelStep = null;
|
|
68803
69133
|
const drawnArea = stage ? stage.area : drawnAreaRef.current;
|
|
68804
|
-
const
|
|
68805
|
-
|
|
68806
|
-
|
|
69134
|
+
const rowBackAsked = rowBackAskedRef.current;
|
|
69135
|
+
rowBackAskedRef.current = false;
|
|
69136
|
+
// Asked for over a row a hand has left moving: the row belongs to what was asked
|
|
69137
|
+
// for, and where the hand's snap would have ended decides nothing.
|
|
69138
|
+
const rowTaken = handMovingRef.current && !handLanded && (drawnArea !== currentArea || rowBackAsked);
|
|
69139
|
+
const rowStand = rowTaken ? readRowStand(containerRef.current, slideElements) : null;
|
|
69140
|
+
if (rowTaken) {
|
|
69141
|
+
handMovingRef.current = false;
|
|
68807
69142
|
writeHeading(null);
|
|
69143
|
+
paintTravelProgress();
|
|
69144
|
+
}
|
|
69145
|
+
const travelStarts = !still && durationMs > 0 && drawnArea !== undefined && (drawnArea !== currentArea || rowTaken) && slideElements.some(slideElement => readArea(slideElement) === drawnArea);
|
|
69146
|
+
let overRow = null;
|
|
69147
|
+
if (travelStarts && rowStand) {
|
|
69148
|
+
overRow = stageOverRow(rowStand, currentArea, realPlaceOf, travelStepRef.current);
|
|
69149
|
+
stage = stageRef.current = {
|
|
69150
|
+
placeByArea: overRow.placeByArea,
|
|
69151
|
+
area: currentArea
|
|
69152
|
+
};
|
|
69153
|
+
} else if (travelStarts) {
|
|
68808
69154
|
// Where the slide being left IS — the place the stage gave it if a travel
|
|
68809
69155
|
// was already playing (a press landing mid-travel departs from where the
|
|
68810
69156
|
// eye is, not from the map), its own place otherwise.
|
|
@@ -68889,9 +69235,16 @@ const SlideContainer = ({
|
|
|
68889
69235
|
}
|
|
68890
69236
|
const track = trackRef.current;
|
|
68891
69237
|
containerRef.current.toggleAttribute("data-slide-travelling", Boolean(stage));
|
|
68892
|
-
|
|
69238
|
+
if (rowTaken) {
|
|
69239
|
+
containerRef.current.setAttribute("data-slide-row-taken", "");
|
|
69240
|
+
}
|
|
69241
|
+
// How far the track stands from where the scroll position rests: none at rest. A
|
|
69242
|
+
// box that is no scroller has no position, and the track says it all.
|
|
68893
69243
|
const rowPlace = onRow(currentPlace);
|
|
68894
|
-
const restPlace =
|
|
69244
|
+
const restPlace = containerRef.current.hasAttribute("data-slide-row-taken") ? {
|
|
69245
|
+
x: 0,
|
|
69246
|
+
y: 0
|
|
69247
|
+
} : restPlaceRef.current;
|
|
68895
69248
|
const offset = `${(restPlace.x - rowPlace.x) * 100}% ${(restPlace.y - rowPlace.y) * 100}%`;
|
|
68896
69249
|
// The travel that was ASKED for is still one box, whatever is left of it to
|
|
68897
69250
|
// cover: what is left takes what is left of the duration.
|
|
@@ -68914,6 +69267,9 @@ const SlideContainer = ({
|
|
|
68914
69267
|
const onScreenPx = trackOffsetPx(track, containerRef.current);
|
|
68915
69268
|
offsetOnScreen = `${onScreenPx.x}px ${onScreenPx.y}px`;
|
|
68916
69269
|
}
|
|
69270
|
+
if (overRow) {
|
|
69271
|
+
offsetOnScreen = rowStand.offset;
|
|
69272
|
+
}
|
|
68917
69273
|
const offsetBefore = offsetOnScreen ?? offsetRef.current;
|
|
68918
69274
|
offsetRef.current = offset;
|
|
68919
69275
|
track.style.setProperty("--slide-container-offset", offset);
|
|
@@ -68932,10 +69288,10 @@ const SlideContainer = ({
|
|
|
68932
69288
|
// short distance for a full duration. One box in `duration` is the pace;
|
|
68933
69289
|
// this only ever shortens it (a longer travel is not made slower, which
|
|
68934
69290
|
// would make a two-box move drag).
|
|
68935
|
-
const travelRatio = ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
69291
|
+
const travelRatio = overRow ? ratioOfOneBox(track, offsetBefore, offset) : ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
68936
69292
|
// Already moving, so there is no ease-in to play: it would stall the track
|
|
68937
69293
|
// for an instant right where the eye is following it.
|
|
68938
|
-
const easing = travelInFlight ? "ease-out" : "ease";
|
|
69294
|
+
const easing = travelInFlight || overRow ? "ease-out" : "ease";
|
|
68939
69295
|
// Cancelled rather than layered: two animations on the same property
|
|
68940
69296
|
// would blend, and what one sees then is neither of the two moves.
|
|
68941
69297
|
trackAnimationRef.current?.cancel();
|
|
@@ -68943,15 +69299,21 @@ const SlideContainer = ({
|
|
|
68943
69299
|
translate: offsetBefore
|
|
68944
69300
|
}, {
|
|
68945
69301
|
translate: offset
|
|
68946
|
-
}],
|
|
69302
|
+
}],
|
|
69303
|
+
// Backwards: the first frame's clock can read a fraction of a millisecond
|
|
69304
|
+
// before the start time Chrome gives the animation, which would show the
|
|
69305
|
+
// travel already over.
|
|
69306
|
+
{
|
|
68947
69307
|
duration: durationMs * travelRatio,
|
|
68948
|
-
easing
|
|
69308
|
+
easing,
|
|
69309
|
+
fill: "backwards"
|
|
68949
69310
|
});
|
|
68950
69311
|
// The trait travels with the slides, from a whole box away.
|
|
68951
|
-
const
|
|
69312
|
+
const stepProgress = travelStep ? travelStep.x || travelStep.y : 0;
|
|
69313
|
+
const progressFrom = overRow ? overRow.progress : stepProgress;
|
|
68952
69314
|
// The slide the picture leans on for the length of it is the one being
|
|
68953
69315
|
// LEFT: it is the second one in the frame until the travel is over.
|
|
68954
|
-
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, drawnArea);
|
|
69316
|
+
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, overRow ? overRow.leaningArea : drawnArea);
|
|
68955
69317
|
// Presses still waiting behind this one: it is already late, so it is
|
|
68956
69318
|
// sent home at once rather than played out at the pace of someone who
|
|
68957
69319
|
// has stopped pressing. Someone pressing → four times is asking to be
|
|
@@ -68972,6 +69334,17 @@ const SlideContainer = ({
|
|
|
68972
69334
|
// thing it was standing for is over.
|
|
68973
69335
|
settleTravel();
|
|
68974
69336
|
}
|
|
69337
|
+
// The row taken with nothing playing to give it back (no duration, a page change
|
|
69338
|
+
// carrying the slide): no scroller for as long as the compositor takes to hear it.
|
|
69339
|
+
if (!stageRef.current && containerRef.current.hasAttribute("data-slide-row-taken")) {
|
|
69340
|
+
requestAnimationFrame(() => {
|
|
69341
|
+
requestAnimationFrame(() => {
|
|
69342
|
+
if (trackAnimationRef.current?.playState !== "running") {
|
|
69343
|
+
settleTravel();
|
|
69344
|
+
}
|
|
69345
|
+
});
|
|
69346
|
+
});
|
|
69347
|
+
}
|
|
68975
69348
|
// A window waiting for its travel to be over (see goToArea's own loop
|
|
68976
69349
|
// branch): the animation says when, and says it about the move that just
|
|
68977
69350
|
// started rather than about a duration counted out beside it.
|
|
@@ -69035,9 +69408,9 @@ const SlideContainer = ({
|
|
|
69035
69408
|
sendRowToRest(currentElement);
|
|
69036
69409
|
}
|
|
69037
69410
|
// Said last, and after data-current has been written: what a travel would do
|
|
69038
|
-
// is read off the map from the slide
|
|
69039
|
-
//
|
|
69040
|
-
paintWays(
|
|
69411
|
+
// is read off the map from the slide on screen, and off the locks that slide is
|
|
69412
|
+
// wearing in this very commit.
|
|
69413
|
+
paintWays();
|
|
69041
69414
|
// …and once everything this commit had to write is written, the box says so
|
|
69042
69415
|
// — a single announcement for a render that may have changed several of the
|
|
69043
69416
|
// facts at once.
|
|
@@ -69084,14 +69457,18 @@ const SlideContainer = ({
|
|
|
69084
69457
|
placeOf
|
|
69085
69458
|
} = readMap();
|
|
69086
69459
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
69087
|
-
if (!area
|
|
69460
|
+
if (!area) {
|
|
69088
69461
|
return false;
|
|
69089
69462
|
}
|
|
69463
|
+
// What a press leaves is the slide on screen, which is what paintWays offers from.
|
|
69464
|
+
// A landing is the row arriving there, and what it leaves is the current one.
|
|
69465
|
+
const areaLeft = landed ? readArea(currentElement) : readSlideOnScreen(containerRef.current) ?? readArea(currentElement);
|
|
69466
|
+
const elementLeft = slideElements.find(slideElement => readArea(slideElement) === areaLeft) || currentElement;
|
|
69090
69467
|
if (forward === undefined) {
|
|
69091
69468
|
// Asked for by name (--navi-go-to-slide, --navi-back) rather than by a
|
|
69092
69469
|
// direction: which way it is is still a fact, read off the map, and it is
|
|
69093
69470
|
// what says which of the two locks below applies.
|
|
69094
|
-
const from = placeOf.get(
|
|
69471
|
+
const from = placeOf.get(areaLeft) || {
|
|
69095
69472
|
x: 0,
|
|
69096
69473
|
y: 0
|
|
69097
69474
|
};
|
|
@@ -69109,9 +69486,18 @@ const SlideContainer = ({
|
|
|
69109
69486
|
// one departure the forward hold does not apply to. Said here rather than
|
|
69110
69487
|
// by dropping the attribute, which stays the render's to write — a hold
|
|
69111
69488
|
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
69112
|
-
if (forward ? !released &&
|
|
69489
|
+
if (area !== areaLeft && (forward ? !released && elementLeft.hasAttribute("data-prevent-nav-next") : elementLeft.hasAttribute("data-prevent-nav-previous"))) {
|
|
69113
69490
|
return false;
|
|
69114
69491
|
}
|
|
69492
|
+
if (area === readArea(currentElement)) {
|
|
69493
|
+
// Nowhere to go, unless a hand has left the row on its way off this slide.
|
|
69494
|
+
if (!handMovingRef.current) {
|
|
69495
|
+
return false;
|
|
69496
|
+
}
|
|
69497
|
+
rowBackAskedRef.current = true;
|
|
69498
|
+
askRowBack(count => count + 1);
|
|
69499
|
+
return true;
|
|
69500
|
+
}
|
|
69115
69501
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
69116
69502
|
// who pressed what, and whether they were on the keyboard, is knowable now
|
|
69117
69503
|
// and gone a render later. The slide arriving is made reachable on the spot
|
|
@@ -69493,7 +69879,7 @@ const SlideContainer = ({
|
|
|
69493
69879
|
const move = (dx, dy, event, {
|
|
69494
69880
|
value,
|
|
69495
69881
|
released
|
|
69496
|
-
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
69882
|
+
} = {}) => goToArea(areaTowards(dx, dy, readSlideOnScreen(containerRef.current)), {
|
|
69497
69883
|
forward: dx > 0 || dy > 0,
|
|
69498
69884
|
event,
|
|
69499
69885
|
value,
|
|
@@ -69574,10 +69960,10 @@ const SlideContainer = ({
|
|
|
69574
69960
|
const paintNativeRow = ({
|
|
69575
69961
|
finger
|
|
69576
69962
|
}) => {
|
|
69577
|
-
|
|
69963
|
+
const containerEl = containerRef.current;
|
|
69964
|
+
if (trackAnimationRef.current?.playState === "running" || containerEl.hasAttribute("data-slide-row-taken")) {
|
|
69578
69965
|
return;
|
|
69579
69966
|
}
|
|
69580
|
-
const containerEl = containerRef.current;
|
|
69581
69967
|
const slideElements = Array.from(trackRef.current.children);
|
|
69582
69968
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69583
69969
|
if (!currentElement) {
|
|
@@ -69795,13 +70181,19 @@ const SlideContainer = ({
|
|
|
69795
70181
|
// ([data-slide-ways~="right"]), or through useSlideContainer, which watches
|
|
69796
70182
|
// them. Nothing is told, everything is read: it is what lets them sit
|
|
69797
70183
|
// anywhere, and what lets a container nobody drives still be followed.
|
|
69798
|
-
const paintWays =
|
|
70184
|
+
const paintWays = () => {
|
|
69799
70185
|
const ways = [];
|
|
69800
70186
|
const held = [];
|
|
70187
|
+
const {
|
|
70188
|
+
slideElements,
|
|
70189
|
+
order
|
|
70190
|
+
} = readMap();
|
|
70191
|
+
const areaOnScreen = readSlideOnScreen(containerRef.current);
|
|
70192
|
+
const elementOnScreen = slideElements.find(slideElement => readArea(slideElement) === areaOnScreen);
|
|
69801
70193
|
// Whether the slide being left says no, this way. The one gate again, read
|
|
69802
70194
|
// exactly as goToArea reads it at the moment of the travel — so what is
|
|
69803
70195
|
// published here and what would happen cannot disagree.
|
|
69804
|
-
const holdsTowards = forward =>
|
|
70196
|
+
const holdsTowards = forward => elementOnScreen?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
69805
70197
|
const say = (name, offered, forward) => {
|
|
69806
70198
|
if (!offered) {
|
|
69807
70199
|
return;
|
|
@@ -69818,18 +70210,14 @@ const SlideContainer = ({
|
|
|
69818
70210
|
// would be saying it about every row on the page.
|
|
69819
70211
|
continue;
|
|
69820
70212
|
}
|
|
69821
|
-
say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
|
|
70213
|
+
say(direction, Boolean(areaTowards(dx, dy, areaOnScreen)), dx > 0 || dy > 0);
|
|
69822
70214
|
}
|
|
69823
70215
|
// The two ends are ways out like the others — "all the way that way", said
|
|
69824
70216
|
// by the map's own order rather than by a direction (see goToEnd, and
|
|
69825
70217
|
// SlideContainer.First / .Last). Offered while one is not already standing
|
|
69826
70218
|
// there, and held by the same lock the direction they lie in would be.
|
|
69827
|
-
|
|
69828
|
-
|
|
69829
|
-
} = readMap();
|
|
69830
|
-
const currentArea = currentElement ? readArea(currentElement) : undefined;
|
|
69831
|
-
say("first", order.length > 0 && order[0] !== currentArea, false);
|
|
69832
|
-
say("last", order.length > 0 && order[order.length - 1] !== currentArea, true);
|
|
70219
|
+
say("first", order.length > 0 && order[0] !== areaOnScreen, false);
|
|
70220
|
+
say("last", order.length > 0 && order[order.length - 1] !== areaOnScreen, true);
|
|
69833
70221
|
for (const element of travelPainters()) {
|
|
69834
70222
|
if (ways.length) {
|
|
69835
70223
|
element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
|
|
@@ -69922,7 +70310,8 @@ const SlideContainer = ({
|
|
|
69922
70310
|
"--slide-travel-progress": 0
|
|
69923
70311
|
}], {
|
|
69924
70312
|
duration: durationMs,
|
|
69925
|
-
easing
|
|
70313
|
+
easing,
|
|
70314
|
+
fill: "backwards"
|
|
69926
70315
|
}));
|
|
69927
70316
|
progressAnimationsRef.current[0].finished.then(() => {
|
|
69928
70317
|
progressAnimationsRef.current = [];
|
|
@@ -105867,7 +106256,9 @@ const StepList = ({
|
|
|
105867
106256
|
const read = () => {
|
|
105868
106257
|
const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
105869
106258
|
const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
|
|
105870
|
-
|
|
106259
|
+
// Which step is lit follows the screen. The halo counts from the current slide,
|
|
106260
|
+
// which is what the progress it draws with is measured against.
|
|
106261
|
+
setContainerCurrent(readSlideOnScreen(containerElement));
|
|
105871
106262
|
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
105872
106263
|
if (currentIdx === -1) {
|
|
105873
106264
|
// A slide no step names (a confirmation screen): the halo is not
|