@jsenv/navi 0.30.4 → 0.30.6
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 +532 -77
- package/dist/dev/jsenv_navi.js.map +10 -9
- package/dist/jsenv_navi.js +532 -77
- package/dist/jsenv_navi.js.map +10 -9
- package/docs/drag_to_travel.md +15 -1
- package/docs/mobile_touch.md +64 -5
- package/docs/navigation.md +54 -3
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -25965,7 +25965,7 @@ defineInteractionDetector({
|
|
|
25965
25965
|
const transitionAfter = peekLastViewTransition();
|
|
25966
25966
|
const drawn =
|
|
25967
25967
|
transitionAfter === transitionBefore
|
|
25968
|
-
? afterFrames(2)
|
|
25968
|
+
? afterFrames$1(2)
|
|
25969
25969
|
: transitionAfter.finished.then(NOOP, NOOP);
|
|
25970
25970
|
const write = {
|
|
25971
25971
|
settled: false,
|
|
@@ -26265,7 +26265,7 @@ const warnWhenNothingToLandOn = (element, dropContainer, canLeave) => {
|
|
|
26265
26265
|
|
|
26266
26266
|
const NOOP = () => {};
|
|
26267
26267
|
|
|
26268
|
-
const afterFrames = (count) =>
|
|
26268
|
+
const afterFrames$1 = (count) =>
|
|
26269
26269
|
new Promise((resolve) => {
|
|
26270
26270
|
const next = (remaining) => {
|
|
26271
26271
|
if (remaining === 0) {
|
|
@@ -37756,9 +37756,23 @@ const collectRoutePages = children => {
|
|
|
37756
37756
|
* Whether the next container down is a row a hand scrolls: every page has a
|
|
37757
37757
|
* place on it from the start, and the browser moves between them (see
|
|
37758
37758
|
* RouteTravel's `nativeScroll`). Told to one container, not to the pages it holds.
|
|
37759
|
+
* False, or `{ renderNeighbour }`.
|
|
37759
37760
|
*/
|
|
37760
37761
|
const RouteRowContext = createContext(false);
|
|
37761
37762
|
|
|
37763
|
+
/**
|
|
37764
|
+
* What stands in the pane of a neighbour until its page does. A component of its
|
|
37765
|
+
* own: what the application calls in there (a hook, a signal) answers to it, however
|
|
37766
|
+
* many neighbours the row has at that moment.
|
|
37767
|
+
*/
|
|
37768
|
+
const RouteRowNeighbour = ({
|
|
37769
|
+
renderNeighbour,
|
|
37770
|
+
page
|
|
37771
|
+
}) => jsx("div", {
|
|
37772
|
+
"data-route-row-neighbour": "",
|
|
37773
|
+
children: renderNeighbour(page)
|
|
37774
|
+
});
|
|
37775
|
+
|
|
37762
37776
|
// A container as a row: one pane per leaf, in the order they are written. A page
|
|
37763
37777
|
// still builds when its route first matches, since a route's actions only ask
|
|
37764
37778
|
// while it does: before that its pane is empty. Once built it stays, with what
|
|
@@ -37771,6 +37785,9 @@ const RouteRowContext = createContext(false);
|
|
|
37771
37785
|
const RouteRowContainer = ({
|
|
37772
37786
|
children
|
|
37773
37787
|
}) => {
|
|
37788
|
+
const {
|
|
37789
|
+
renderNeighbour
|
|
37790
|
+
} = useContext(RouteRowContext);
|
|
37774
37791
|
const {
|
|
37775
37792
|
activeBranch,
|
|
37776
37793
|
leaves
|
|
@@ -37819,16 +37836,19 @@ const RouteRowContainer = ({
|
|
|
37819
37836
|
shown: true
|
|
37820
37837
|
}, "fallback");
|
|
37821
37838
|
}
|
|
37822
|
-
|
|
37839
|
+
const shownIndex = activeBranch ? leaves.findIndex(leaf => leaf.id === activeBranch.id) : -1;
|
|
37840
|
+
return leaves.map((leaf, index) => jsx(RouteRowPane, {
|
|
37823
37841
|
branch: leaf,
|
|
37824
37842
|
page: pageOf(leaf),
|
|
37825
|
-
shown:
|
|
37843
|
+
shown: index === shownIndex,
|
|
37844
|
+
renderNeighbour: shownIndex !== -1 && (index === shownIndex - 1 || index === shownIndex + 1) ? renderNeighbour : undefined
|
|
37826
37845
|
}, leaf.id));
|
|
37827
37846
|
};
|
|
37828
37847
|
const RouteRowPane = ({
|
|
37829
37848
|
branch,
|
|
37830
37849
|
page,
|
|
37831
|
-
shown
|
|
37850
|
+
shown,
|
|
37851
|
+
renderNeighbour
|
|
37832
37852
|
}) => {
|
|
37833
37853
|
// The very vnode rendered last, while not on screen: Preact does not look into
|
|
37834
37854
|
// it, and a page nobody can see costs nothing when the address moves.
|
|
@@ -37847,7 +37867,10 @@ const RouteRowPane = ({
|
|
|
37847
37867
|
"data-route-row-pane": "",
|
|
37848
37868
|
"data-built": contentRef.current ? "" : undefined,
|
|
37849
37869
|
inert: !shown,
|
|
37850
|
-
children: contentRef.current
|
|
37870
|
+
children: contentRef.current || renderNeighbour && jsx(RouteRowNeighbour, {
|
|
37871
|
+
renderNeighbour: renderNeighbour,
|
|
37872
|
+
page: branchRoutePage(branch)
|
|
37873
|
+
})
|
|
37851
37874
|
});
|
|
37852
37875
|
};
|
|
37853
37876
|
|
|
@@ -41570,8 +41593,9 @@ const publishRouteRowMove = (move) => {
|
|
|
41570
41593
|
* itself; a counter, a title or a breadcrumb around the box reads it first.
|
|
41571
41594
|
*
|
|
41572
41595
|
* It goes back to `null` as soon as the hand has let go and the row moves. Nothing
|
|
41573
|
-
* says when the fingers leave a trackpad: there it
|
|
41574
|
-
* rest. A travel no hand set off holds nothing: it goes where it
|
|
41596
|
+
* says when the fingers leave a trackpad: there it is the page nearest the row, until
|
|
41597
|
+
* the row has come to rest. A travel no hand set off holds nothing: it goes where it
|
|
41598
|
+
* goes.
|
|
41575
41599
|
*
|
|
41576
41600
|
* One for the document. `route` says which row, for a page holding several boxes.
|
|
41577
41601
|
*/
|
|
@@ -41589,6 +41613,13 @@ const holdPage = (page, headingPage) => {
|
|
|
41589
41613
|
}
|
|
41590
41614
|
};
|
|
41591
41615
|
|
|
41616
|
+
// A hand no browser says has let go: the page nearest the row is where the reader is,
|
|
41617
|
+
// and the address, wherever it stands, is not.
|
|
41618
|
+
const followPageOnScreen = (page, addressPage) => {
|
|
41619
|
+
headingPageSignal.value = addressPage;
|
|
41620
|
+
routeTravelSettledPageSignal.value = page;
|
|
41621
|
+
};
|
|
41622
|
+
|
|
41592
41623
|
const releaseHeldPage = () => {
|
|
41593
41624
|
routeTravelSettledPageSignal.value = null;
|
|
41594
41625
|
headingPageSignal.value = null;
|
|
@@ -41703,7 +41734,9 @@ const readRowStretch = (scroller, pages, page) => {
|
|
|
41703
41734
|
/**
|
|
41704
41735
|
* @param {Element} scroller
|
|
41705
41736
|
* @param {object} options
|
|
41706
|
-
* @param {() => void} options.onScroll - the row moved.
|
|
41737
|
+
* @param {(move: {finger: boolean}) => void} options.onScroll - the row moved.
|
|
41738
|
+
* `finger`: a touch has come since the row last stopped, so a lift will say where
|
|
41739
|
+
* it goes (`onLeftToSnap`). Without one (a trackpad, a wheel) nothing will.
|
|
41707
41740
|
* @param {(event: Event|null) => void} options.onLand - the row may have stopped.
|
|
41708
41741
|
* Read `pageAtRest` to know whether it stopped on a page. Heard more than once
|
|
41709
41742
|
* for one landing, so what it does must find nothing to do the second time.
|
|
@@ -41719,11 +41752,13 @@ const watchRowScroll = (
|
|
|
41719
41752
|
) => {
|
|
41720
41753
|
let stoppedTimeout = null;
|
|
41721
41754
|
let touching = false;
|
|
41755
|
+
let touchedSinceStop = false;
|
|
41722
41756
|
let liftedAt = null;
|
|
41723
41757
|
const landOnSilence = () => {
|
|
41724
41758
|
liftedAt = null;
|
|
41725
41759
|
// A finger holding the row still: nothing has stopped.
|
|
41726
41760
|
if (!touching) {
|
|
41761
|
+
touchedSinceStop = false;
|
|
41727
41762
|
onLand(null);
|
|
41728
41763
|
}
|
|
41729
41764
|
};
|
|
@@ -41732,7 +41767,7 @@ const watchRowScroll = (
|
|
|
41732
41767
|
stoppedTimeout = setTimeout(landOnSilence, STOPPED_SILENCE);
|
|
41733
41768
|
};
|
|
41734
41769
|
const onScrollEvent = () => {
|
|
41735
|
-
onScroll();
|
|
41770
|
+
onScroll({ finger: touchedSinceStop });
|
|
41736
41771
|
armSilence();
|
|
41737
41772
|
if (!liftedAt) {
|
|
41738
41773
|
return;
|
|
@@ -41746,11 +41781,6 @@ const watchRowScroll = (
|
|
|
41746
41781
|
liftedAt = null;
|
|
41747
41782
|
onLeftToSnap?.(moved > 0);
|
|
41748
41783
|
};
|
|
41749
|
-
const onTouchStart = () => {
|
|
41750
|
-
touching = true;
|
|
41751
|
-
liftedAt = null;
|
|
41752
|
-
clearTimeout(stoppedTimeout);
|
|
41753
|
-
};
|
|
41754
41784
|
const onTouchEnd = (touchEvent) => {
|
|
41755
41785
|
touching = touchEvent.touches.length > 0;
|
|
41756
41786
|
if (!touching) {
|
|
@@ -41758,23 +41788,38 @@ const watchRowScroll = (
|
|
|
41758
41788
|
armSilence();
|
|
41759
41789
|
}
|
|
41760
41790
|
};
|
|
41791
|
+
const onTouchStart = (touchEvent) => {
|
|
41792
|
+
touching = true;
|
|
41793
|
+
touchedSinceStop = true;
|
|
41794
|
+
liftedAt = null;
|
|
41795
|
+
clearTimeout(stoppedTimeout);
|
|
41796
|
+
// Listened for on what the finger landed on, where the end of a touch is always
|
|
41797
|
+
// told: a page changing under the finger may have taken it out of the document by
|
|
41798
|
+
// then, and nothing comes up to the row from there.
|
|
41799
|
+
const { target } = touchEvent;
|
|
41800
|
+
const onEnd = (endEvent) => {
|
|
41801
|
+
target.removeEventListener("touchend", onEnd);
|
|
41802
|
+
target.removeEventListener("touchcancel", onEnd);
|
|
41803
|
+
onTouchEnd(endEvent);
|
|
41804
|
+
};
|
|
41805
|
+
target.addEventListener("touchend", onEnd, { passive: true });
|
|
41806
|
+
target.addEventListener("touchcancel", onEnd, { passive: true });
|
|
41807
|
+
};
|
|
41761
41808
|
scroller.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
41762
41809
|
scroller.addEventListener("scrollend", onLand);
|
|
41763
41810
|
scroller.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
41764
|
-
scroller.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
41765
|
-
scroller.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
41766
41811
|
return () => {
|
|
41767
41812
|
clearTimeout(stoppedTimeout);
|
|
41768
41813
|
scroller.removeEventListener("scroll", onScrollEvent);
|
|
41769
41814
|
scroller.removeEventListener("scrollend", onLand);
|
|
41770
41815
|
scroller.removeEventListener("touchstart", onTouchStart);
|
|
41771
|
-
scroller.removeEventListener("touchend", onTouchEnd);
|
|
41772
|
-
scroller.removeEventListener("touchcancel", onTouchEnd);
|
|
41773
41816
|
};
|
|
41774
41817
|
};
|
|
41775
41818
|
|
|
41776
41819
|
installImportMetaCssBuild(import.meta);
|
|
41777
41820
|
const css$16 = /* css */`.navi_route_travel {
|
|
41821
|
+
flex-direction: column;
|
|
41822
|
+
display: flex;
|
|
41778
41823
|
position: relative;
|
|
41779
41824
|
}
|
|
41780
41825
|
|
|
@@ -41805,6 +41850,19 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41805
41850
|
overflow: visible;
|
|
41806
41851
|
}
|
|
41807
41852
|
|
|
41853
|
+
&[data-route-row-taken] {
|
|
41854
|
+
scroll-snap-type: none;
|
|
41855
|
+
}
|
|
41856
|
+
|
|
41857
|
+
&[data-route-row-taken="clip"] {
|
|
41858
|
+
overflow: clip;
|
|
41859
|
+
|
|
41860
|
+
& > [data-route-row-pane], & > [data-route-row-live], & > [data-route-row-pictures] > * {
|
|
41861
|
+
left: var(--navi-route-row-stand);
|
|
41862
|
+
position: relative;
|
|
41863
|
+
}
|
|
41864
|
+
}
|
|
41865
|
+
|
|
41808
41866
|
& > [data-route-row-pane] {
|
|
41809
41867
|
scroll-snap-align: start;
|
|
41810
41868
|
scroll-snap-stop: always;
|
|
@@ -41826,6 +41884,15 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41826
41884
|
overflow: clip;
|
|
41827
41885
|
}
|
|
41828
41886
|
|
|
41887
|
+
& > [data-route-row-pane] > [data-route-row-neighbour] {
|
|
41888
|
+
contain: layout paint;
|
|
41889
|
+
visibility: visible;
|
|
41890
|
+
flex-direction: column;
|
|
41891
|
+
display: flex;
|
|
41892
|
+
position: absolute;
|
|
41893
|
+
inset: 0;
|
|
41894
|
+
}
|
|
41895
|
+
|
|
41829
41896
|
& > [data-route-row-live] {
|
|
41830
41897
|
flex-direction: column;
|
|
41831
41898
|
grid-row: 1;
|
|
@@ -41858,6 +41925,7 @@ const SET_OFF_PULL = 16;
|
|
|
41858
41925
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
41859
41926
|
* travelByDrag?: boolean,
|
|
41860
41927
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
41928
|
+
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
41861
41929
|
* }>}
|
|
41862
41930
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
41863
41931
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -41874,6 +41942,13 @@ const SET_OFF_PULL = 16;
|
|
|
41874
41942
|
* of the page under six entries. A tab pressed goes the way its <Link> says —
|
|
41875
41943
|
* `<Link replace>` for a row of tabs, so a press and a swipe towards the same
|
|
41876
41944
|
* neighbour write the same history.
|
|
41945
|
+
* @param {(page: {route: object, params: object|undefined}) => import("preact").ComponentChildren} [props.renderNeighbour]
|
|
41946
|
+
* - what stands in the pane of a page next to the one on screen, until the hand
|
|
41947
|
+
* has pulled far enough to bring that page: its background, its skeleton, what the
|
|
41948
|
+
* list already knows. A `<Route>` page already built shows itself. Receives
|
|
41949
|
+
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
41950
|
+
* the height of the row, as the page is. Its route does not match while it stands
|
|
41951
|
+
* there: what it reads is not asked for.
|
|
41877
41952
|
*
|
|
41878
41953
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
41879
41954
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -41889,6 +41964,7 @@ const RouteTravel = ({
|
|
|
41889
41964
|
replace: true,
|
|
41890
41965
|
routeTransition: "none"
|
|
41891
41966
|
}),
|
|
41967
|
+
renderNeighbour,
|
|
41892
41968
|
className,
|
|
41893
41969
|
children,
|
|
41894
41970
|
...rest
|
|
@@ -41949,9 +42025,7 @@ const RouteTravel = ({
|
|
|
41949
42025
|
travelling: false,
|
|
41950
42026
|
pictures: new Map(),
|
|
41951
42027
|
resumeScrollRecording: null,
|
|
41952
|
-
scrollsDocument:
|
|
41953
|
-
includeHidden: true
|
|
41954
|
-
}) === document.documentElement,
|
|
42028
|
+
scrollsDocument: rowFollowsDocument(row),
|
|
41955
42029
|
...fields
|
|
41956
42030
|
};
|
|
41957
42031
|
movementRef.current = movement;
|
|
@@ -42046,6 +42120,37 @@ const RouteTravel = ({
|
|
|
42046
42120
|
});
|
|
42047
42121
|
};
|
|
42048
42122
|
|
|
42123
|
+
// A tab pressed over a row a hand has left moving, which the browser plays on and
|
|
42124
|
+
// overwrites a travel with once that travel is over: the snap is ended first. Given
|
|
42125
|
+
// back, the row stands where it stood. A frame later, because until then WebKit still
|
|
42126
|
+
// starts from where a box that is no scroller stood: a smooth scroll to the first page
|
|
42127
|
+
// sees nothing to do.
|
|
42128
|
+
const takeRowFromHand = movement => {
|
|
42129
|
+
const row = rowRef.current;
|
|
42130
|
+
const position = row.scrollLeft;
|
|
42131
|
+
movement.rowTaken = true;
|
|
42132
|
+
// A travel no hand set off holds nothing: it goes where it goes.
|
|
42133
|
+
releaseHeldPage();
|
|
42134
|
+
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
42135
|
+
row.setAttribute("data-route-row-taken", "clip");
|
|
42136
|
+
afterFrames(2, () => {
|
|
42137
|
+
if (movementRef.current !== movement) {
|
|
42138
|
+
return;
|
|
42139
|
+
}
|
|
42140
|
+
row.setAttribute("data-route-row-taken", "");
|
|
42141
|
+
row.style.removeProperty("--navi-route-row-stand");
|
|
42142
|
+
afterFrames(1, () => {
|
|
42143
|
+
const pane = panesOf(row)[latestRef.current.currentIndex];
|
|
42144
|
+
if (movementRef.current === movement && pane) {
|
|
42145
|
+
row.scrollTo({
|
|
42146
|
+
left: pane.offsetLeft,
|
|
42147
|
+
behavior: "smooth"
|
|
42148
|
+
});
|
|
42149
|
+
}
|
|
42150
|
+
});
|
|
42151
|
+
});
|
|
42152
|
+
};
|
|
42153
|
+
|
|
42049
42154
|
// The row has stopped, on the page of the address: everything held for the
|
|
42050
42155
|
// movement is let go of in one go, before anything paints.
|
|
42051
42156
|
const endMovement = () => {
|
|
@@ -42056,6 +42161,7 @@ const RouteTravel = ({
|
|
|
42056
42161
|
}
|
|
42057
42162
|
movementRef.current = null;
|
|
42058
42163
|
row.removeAttribute("data-moving");
|
|
42164
|
+
row.removeAttribute("data-route-row-taken");
|
|
42059
42165
|
publishRouteRowMove(null);
|
|
42060
42166
|
releaseHeldPage();
|
|
42061
42167
|
if (liveRef.current) {
|
|
@@ -42123,16 +42229,23 @@ const RouteTravel = ({
|
|
|
42123
42229
|
}
|
|
42124
42230
|
return;
|
|
42125
42231
|
}
|
|
42126
|
-
|
|
42232
|
+
const movementBefore = movementRef.current;
|
|
42233
|
+
const movement = beginMovement({
|
|
42127
42234
|
travelling: true
|
|
42128
42235
|
});
|
|
42129
|
-
row.scrollTo({
|
|
42130
|
-
left: pane.offsetLeft,
|
|
42131
|
-
behavior: "smooth"
|
|
42132
|
-
});
|
|
42133
42236
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
42134
42237
|
// scroll event is a frame away.
|
|
42135
42238
|
sayRowMoves();
|
|
42239
|
+
if (movementBefore?.byHand && !movementBefore.rowTaken) {
|
|
42240
|
+
takeRowFromHand(movement);
|
|
42241
|
+
return;
|
|
42242
|
+
}
|
|
42243
|
+
if (row.getAttribute("data-route-row-taken") !== "clip") {
|
|
42244
|
+
row.scrollTo({
|
|
42245
|
+
left: pane.offsetLeft,
|
|
42246
|
+
behavior: "smooth"
|
|
42247
|
+
});
|
|
42248
|
+
}
|
|
42136
42249
|
});
|
|
42137
42250
|
|
|
42138
42251
|
// Somebody else is about to move the address to another page of a window: its
|
|
@@ -42164,14 +42277,17 @@ const RouteTravel = ({
|
|
|
42164
42277
|
}, [windowed]);
|
|
42165
42278
|
useLayoutEffect(() => {
|
|
42166
42279
|
const row = rowRef.current;
|
|
42167
|
-
const
|
|
42280
|
+
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
42281
|
+
const onScroll = ({
|
|
42282
|
+
finger
|
|
42283
|
+
}) => {
|
|
42168
42284
|
const {
|
|
42169
42285
|
pages,
|
|
42170
42286
|
currentIndex,
|
|
42171
42287
|
travelByDrag
|
|
42172
42288
|
} = latestRef.current;
|
|
42173
42289
|
const panes = panesOf(row);
|
|
42174
|
-
if (panes.length < 2) {
|
|
42290
|
+
if (panes.length < 2 || isClipped()) {
|
|
42175
42291
|
return;
|
|
42176
42292
|
}
|
|
42177
42293
|
const moving = movementRef.current;
|
|
@@ -42180,11 +42296,24 @@ const RouteTravel = ({
|
|
|
42180
42296
|
// Written by this box, at rest: nobody is moving anything.
|
|
42181
42297
|
return;
|
|
42182
42298
|
}
|
|
42183
|
-
const movement = moving || beginMovement({
|
|
42299
|
+
const movement = moving || beginMovement({
|
|
42300
|
+
byHand: true
|
|
42301
|
+
});
|
|
42184
42302
|
sayRowMoves();
|
|
42185
42303
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
42186
42304
|
return;
|
|
42187
42305
|
}
|
|
42306
|
+
// Nothing will say when a trackpad's fingers leave, and a long stroke would keep
|
|
42307
|
+
// the first tab lit for the whole trip: the nearest page answers. A finger has a
|
|
42308
|
+
// lift to wait for (onLeftToSnap), and until then the page held stands.
|
|
42309
|
+
if (!finger) {
|
|
42310
|
+
const {
|
|
42311
|
+
base,
|
|
42312
|
+
toward,
|
|
42313
|
+
fraction
|
|
42314
|
+
} = readRowStretch(row, panes, panes[0]);
|
|
42315
|
+
followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
|
|
42316
|
+
}
|
|
42188
42317
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
42189
42318
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
42190
42319
|
return;
|
|
@@ -42237,7 +42366,7 @@ const RouteTravel = ({
|
|
|
42237
42366
|
};
|
|
42238
42367
|
const onLand = () => {
|
|
42239
42368
|
const movement = movementRef.current;
|
|
42240
|
-
if (!movement) {
|
|
42369
|
+
if (!movement || isClipped()) {
|
|
42241
42370
|
return;
|
|
42242
42371
|
}
|
|
42243
42372
|
const panes = panesOf(row);
|
|
@@ -42266,8 +42395,48 @@ const RouteTravel = ({
|
|
|
42266
42395
|
onLand,
|
|
42267
42396
|
onLeftToSnap
|
|
42268
42397
|
});
|
|
42398
|
+
// A hand back on a row taken from one: it is the hand's again, snapping included.
|
|
42399
|
+
const onHandBack = () => {
|
|
42400
|
+
if (row.getAttribute("data-route-row-taken") === "") {
|
|
42401
|
+
row.removeAttribute("data-route-row-taken");
|
|
42402
|
+
}
|
|
42403
|
+
};
|
|
42404
|
+
// A press for the page the address is already on changes nothing the router can
|
|
42405
|
+
// tell, and a stroke no lift ends leaves the address where it was: it is heard here,
|
|
42406
|
+
// the way a tab says which page it is through its address.
|
|
42407
|
+
const onPress = clickEvent => {
|
|
42408
|
+
const movement = movementRef.current;
|
|
42409
|
+
if (!movement?.byHand || movement.rowTaken) {
|
|
42410
|
+
return;
|
|
42411
|
+
}
|
|
42412
|
+
const link = clickEvent.target.closest?.("a[href]");
|
|
42413
|
+
const {
|
|
42414
|
+
pages,
|
|
42415
|
+
currentIndex
|
|
42416
|
+
} = latestRef.current;
|
|
42417
|
+
const page = pages[currentIndex];
|
|
42418
|
+
if (!link || !page || new URL(link.href).pathname !== pathOf(page)) {
|
|
42419
|
+
return;
|
|
42420
|
+
}
|
|
42421
|
+
movement.travelling = true;
|
|
42422
|
+
takeRowFromHand(movement);
|
|
42423
|
+
};
|
|
42424
|
+
row.addEventListener("touchstart", onHandBack, {
|
|
42425
|
+
passive: true
|
|
42426
|
+
});
|
|
42427
|
+
row.addEventListener("wheel", onHandBack, {
|
|
42428
|
+
passive: true
|
|
42429
|
+
});
|
|
42430
|
+
document.addEventListener("click", onPress, {
|
|
42431
|
+
capture: true
|
|
42432
|
+
});
|
|
42269
42433
|
return () => {
|
|
42270
42434
|
stopWatching();
|
|
42435
|
+
row.removeEventListener("touchstart", onHandBack);
|
|
42436
|
+
row.removeEventListener("wheel", onHandBack);
|
|
42437
|
+
document.removeEventListener("click", onPress, {
|
|
42438
|
+
capture: true
|
|
42439
|
+
});
|
|
42271
42440
|
movementRef.current?.resumeScrollRecording?.();
|
|
42272
42441
|
movementRef.current = null;
|
|
42273
42442
|
publishRouteRowMove(null);
|
|
@@ -42287,6 +42456,30 @@ const RouteTravel = ({
|
|
|
42287
42456
|
Promise.resolve().then(endMovement);
|
|
42288
42457
|
}
|
|
42289
42458
|
}, [currentIndex]);
|
|
42459
|
+
|
|
42460
|
+
// A neighbour shows from the first frame the compositor moves the row, before
|
|
42461
|
+
// any scroll event: a finger landing is the last word before that.
|
|
42462
|
+
const paintsNeighbours = Boolean(renderNeighbour);
|
|
42463
|
+
useLayoutEffect(() => {
|
|
42464
|
+
if (!paintsNeighbours) {
|
|
42465
|
+
return undefined;
|
|
42466
|
+
}
|
|
42467
|
+
const row = rowRef.current;
|
|
42468
|
+
const onTouchStart = () => {
|
|
42469
|
+
if (!movementRef.current && rowFollowsDocument(row)) {
|
|
42470
|
+
row.style.setProperty("--navi-route-row-pin", `${pinOf(row)}px`);
|
|
42471
|
+
}
|
|
42472
|
+
};
|
|
42473
|
+
row.addEventListener("touchstart", onTouchStart, {
|
|
42474
|
+
passive: true
|
|
42475
|
+
});
|
|
42476
|
+
return () => {
|
|
42477
|
+
row.removeEventListener("touchstart", onTouchStart);
|
|
42478
|
+
};
|
|
42479
|
+
}, [paintsNeighbours]);
|
|
42480
|
+
const rowContext = useMemo(() => ({
|
|
42481
|
+
renderNeighbour
|
|
42482
|
+
}), [renderNeighbour]);
|
|
42290
42483
|
const rowKind = pages.length < 2 && windowed ? "still" : travelByDrag ? "" : "code";
|
|
42291
42484
|
return jsx("div", {
|
|
42292
42485
|
...rest,
|
|
@@ -42299,12 +42492,20 @@ const RouteTravel = ({
|
|
|
42299
42492
|
,
|
|
42300
42493
|
"data-travel-by-drag": travelByDrag && rowKind !== "still" ? "x" : undefined,
|
|
42301
42494
|
children: windowed ? jsxs(Fragment, {
|
|
42302
|
-
children: [pages.map((page, index) =>
|
|
42303
|
-
|
|
42304
|
-
|
|
42305
|
-
|
|
42306
|
-
|
|
42307
|
-
|
|
42495
|
+
children: [pages.map((page, index) => {
|
|
42496
|
+
const holdsNeighbour = paintsNeighbours && isNeighbourPane(index, liveIndex, movementRef.current);
|
|
42497
|
+
return jsx("div", {
|
|
42498
|
+
"data-route-row-pane": "",
|
|
42499
|
+
inert: holdsNeighbour,
|
|
42500
|
+
style: {
|
|
42501
|
+
gridColumn: index + 1
|
|
42502
|
+
},
|
|
42503
|
+
children: holdsNeighbour && jsx(RouteRowNeighbour, {
|
|
42504
|
+
renderNeighbour: renderNeighbour,
|
|
42505
|
+
page: page
|
|
42506
|
+
})
|
|
42507
|
+
}, index);
|
|
42508
|
+
}), jsx("div", {
|
|
42308
42509
|
ref: liveRef,
|
|
42309
42510
|
"data-route-row-live": "",
|
|
42310
42511
|
style: {
|
|
@@ -42316,13 +42517,25 @@ const RouteTravel = ({
|
|
|
42316
42517
|
"data-route-row-pictures": ""
|
|
42317
42518
|
})]
|
|
42318
42519
|
}) : jsx(RouteRowContext.Provider, {
|
|
42319
|
-
value:
|
|
42520
|
+
value: rowContext,
|
|
42320
42521
|
children: children
|
|
42321
42522
|
})
|
|
42322
42523
|
})
|
|
42323
42524
|
});
|
|
42324
42525
|
};
|
|
42325
42526
|
|
|
42527
|
+
// The panes on each side of the live page. Not one holding a picture of the page
|
|
42528
|
+
// left there: a page with no background of its own would show it through.
|
|
42529
|
+
const isNeighbourPane = (index, liveIndex, movement) => {
|
|
42530
|
+
if (index !== liveIndex - 1 && index !== liveIndex + 1) {
|
|
42531
|
+
return false;
|
|
42532
|
+
}
|
|
42533
|
+
return !movement || !movement.pictures.has(index);
|
|
42534
|
+
};
|
|
42535
|
+
const rowFollowsDocument = row => getScrollContainer(row.parentElement, {
|
|
42536
|
+
includeHidden: true
|
|
42537
|
+
}) === document.documentElement;
|
|
42538
|
+
|
|
42326
42539
|
// How far down the row the reader is looking, for a page brought in: a page on
|
|
42327
42540
|
// screen scrolls with the document, and one arriving has to start from there.
|
|
42328
42541
|
const pinOf = row => {
|
|
@@ -42351,6 +42564,15 @@ const copyScrollPositions = (from, to) => {
|
|
|
42351
42564
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
42352
42565
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
42353
42566
|
// routes of their own — which is the same page with nothing to tell apart.
|
|
42567
|
+
const afterFrames = (count, callback) => {
|
|
42568
|
+
if (count === 0) {
|
|
42569
|
+
callback();
|
|
42570
|
+
return;
|
|
42571
|
+
}
|
|
42572
|
+
requestAnimationFrame(() => {
|
|
42573
|
+
afterFrames(count - 1, callback);
|
|
42574
|
+
});
|
|
42575
|
+
};
|
|
42354
42576
|
const normalizePage = page => page.isRoute ? {
|
|
42355
42577
|
route: page,
|
|
42356
42578
|
params: undefined
|
|
@@ -58059,6 +58281,10 @@ const SLIDE_TOWARD_ATTRIBUTE = "data-slide-travel-toward";
|
|
|
58059
58281
|
// one: a hand on a row the browser scrolls can carry it past several slides, and
|
|
58060
58282
|
// what follows it goes on between the two it is standing between.
|
|
58061
58283
|
const SLIDE_BASE_ATTRIBUTE = "data-slide-travel-base";
|
|
58284
|
+
// The slide the row is on its way to while the current one has not changed yet,
|
|
58285
|
+
// because that only happens at rest: nothing is rendered and no address is written
|
|
58286
|
+
// under a snap, and what says where one is should not wait for it.
|
|
58287
|
+
const SLIDE_HEADING_ATTRIBUTE = "data-slide-heading";
|
|
58062
58288
|
// Every way out that would DO something right now, in the words the commands
|
|
58063
58289
|
// use: a direction ("left", "right", "up", "down") when there is a slide that
|
|
58064
58290
|
// way, plus "first" and "last" when one is not already standing at that end of
|
|
@@ -58093,9 +58319,17 @@ const wordsOf = (element, attribute) => {
|
|
|
58093
58319
|
return value ? value.split(" ") : NO_WAYS;
|
|
58094
58320
|
};
|
|
58095
58321
|
|
|
58322
|
+
// The slide one is on, for whatever tells the reader where they are or what a press
|
|
58323
|
+
// would do: the current one while a hand holds the row, where the row is going once
|
|
58324
|
+
// it has let go. data-slide-current only changes at rest.
|
|
58325
|
+
const readSlideOnScreen = (element) =>
|
|
58326
|
+
element.getAttribute(SLIDE_HEADING_ATTRIBUTE) ??
|
|
58327
|
+
element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ??
|
|
58328
|
+
undefined;
|
|
58329
|
+
|
|
58096
58330
|
const readSlideContainerState = (element) => ({
|
|
58097
58331
|
known: true,
|
|
58098
|
-
current: element
|
|
58332
|
+
current: readSlideOnScreen(element),
|
|
58099
58333
|
toward: element.getAttribute(SLIDE_TOWARD_ATTRIBUTE) ?? undefined,
|
|
58100
58334
|
// In DOM order — which is the order of the WALK for a line, and not
|
|
58101
58335
|
// necessarily for a map: there the order is the one the areas are written in
|
|
@@ -58133,11 +58367,13 @@ const sameSlideContainerState = (a, b) =>
|
|
|
58133
58367
|
* can: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
|
|
58134
58368
|
* held: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
|
|
58135
58369
|
* }} where the box stands. `current` is the slide on screen — the one being
|
|
58136
|
-
* travelled TO while a travel plays,
|
|
58370
|
+
* travelled TO while a travel plays, and the one the row is going to once a hand
|
|
58371
|
+
* has let go, because that is what one is looking at. It can come before the
|
|
58372
|
+
* slide's own signal and `onCurrentChange`, which wait for the row to rest.
|
|
58137
58373
|
* `toward` is the other slide in the frame while the picture is between two,
|
|
58138
|
-
* and nothing at rest. `can` is "asking for it would do something",
|
|
58139
|
-
* "it is there and this slide says no" — a way out is dead
|
|
58140
|
-
* only the second is worth a word to the reader.
|
|
58374
|
+
* and nothing at rest. `can` is "asking for it would do something", from that
|
|
58375
|
+
* same slide, `held` is "it is there and this slide says no" — a way out is dead
|
|
58376
|
+
* in both cases, and only the second is worth a word to the reader.
|
|
58141
58377
|
* `known` is false until the box has been read: no box was named and none is
|
|
58142
58378
|
* above, the id names nothing, or — for one commit — this mounted before the
|
|
58143
58379
|
* box had painted. Until then `can` answers YES, because it is the answer that
|
|
@@ -58991,7 +59227,7 @@ const Nav = ({
|
|
|
58991
59227
|
}
|
|
58992
59228
|
slideContainerElementRef.current = containerElement;
|
|
58993
59229
|
const readContainer = () => {
|
|
58994
|
-
setCurrentSlideArea(containerElement
|
|
59230
|
+
setCurrentSlideArea(readSlideOnScreen(containerElement));
|
|
58995
59231
|
paintIndicatorGeometryRef.current();
|
|
58996
59232
|
scheduleTabScrollSyncRef.current();
|
|
58997
59233
|
};
|
|
@@ -69163,6 +69399,10 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
69163
69399
|
overflow-y: auto;
|
|
69164
69400
|
}
|
|
69165
69401
|
|
|
69402
|
+
&[data-slide-row-taken] {
|
|
69403
|
+
overflow: clip;
|
|
69404
|
+
}
|
|
69405
|
+
|
|
69166
69406
|
&:focus {
|
|
69167
69407
|
outline: none;
|
|
69168
69408
|
}
|
|
@@ -69214,6 +69454,77 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
69214
69454
|
}
|
|
69215
69455
|
`;
|
|
69216
69456
|
|
|
69457
|
+
// What a row left moving shows when a travel is asked for over it, read before the
|
|
69458
|
+
// slides are written: the one or two slides in the frame, and how far the track has
|
|
69459
|
+
// to stand, in a box that is no scroller, for that frame not to move.
|
|
69460
|
+
const readRowStand = (containerEl, slideElements) => {
|
|
69461
|
+
const restElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69462
|
+
if (!restElement) {
|
|
69463
|
+
return null;
|
|
69464
|
+
}
|
|
69465
|
+
const {
|
|
69466
|
+
base,
|
|
69467
|
+
toward,
|
|
69468
|
+
fraction
|
|
69469
|
+
} = readRowStretch(containerEl, slideElements, restElement);
|
|
69470
|
+
return {
|
|
69471
|
+
base: readArea(base),
|
|
69472
|
+
toward: toward && fraction > 0 ? readArea(toward) : null,
|
|
69473
|
+
fraction,
|
|
69474
|
+
offset: `${-containerEl.scrollLeft}px ${-containerEl.scrollTop}px`
|
|
69475
|
+
};
|
|
69476
|
+
};
|
|
69477
|
+
// The stage of a travel asked for over a row left moving. What is in the frame stays
|
|
69478
|
+
// where it is, and the slide asked for comes one box past it, unless it is already
|
|
69479
|
+
// there: then what is left to make is a part of a box.
|
|
69480
|
+
const stageOverRow = (rowStand, area, realPlaceOf, stepAsked) => {
|
|
69481
|
+
const {
|
|
69482
|
+
base,
|
|
69483
|
+
toward,
|
|
69484
|
+
fraction
|
|
69485
|
+
} = rowStand;
|
|
69486
|
+
const basePlace = realPlaceOf(base);
|
|
69487
|
+
const placeByArea = new Map([[base, basePlace]]);
|
|
69488
|
+
const towardPlace = toward ? realPlaceOf(toward) : null;
|
|
69489
|
+
if (toward) {
|
|
69490
|
+
placeByArea.set(toward, towardPlace);
|
|
69491
|
+
}
|
|
69492
|
+
if (area === base || area === toward) {
|
|
69493
|
+
const otherArea = area === base ? toward : base;
|
|
69494
|
+
if (!otherArea) {
|
|
69495
|
+
return {
|
|
69496
|
+
placeByArea,
|
|
69497
|
+
leaningArea: null,
|
|
69498
|
+
progress: 0
|
|
69499
|
+
};
|
|
69500
|
+
}
|
|
69501
|
+
const from = realPlaceOf(otherArea);
|
|
69502
|
+
const to = realPlaceOf(area);
|
|
69503
|
+
const sign = Math.sign(to.x - from.x) || Math.sign(to.y - from.y);
|
|
69504
|
+
return {
|
|
69505
|
+
placeByArea,
|
|
69506
|
+
leaningArea: otherArea,
|
|
69507
|
+
progress: sign * (area === base ? fraction : 1 - fraction)
|
|
69508
|
+
};
|
|
69509
|
+
}
|
|
69510
|
+
const to = realPlaceOf(area);
|
|
69511
|
+
const step = stepAsked || {
|
|
69512
|
+
x: Math.sign(to.x - basePlace.x),
|
|
69513
|
+
y: Math.sign(to.y - basePlace.y)
|
|
69514
|
+
};
|
|
69515
|
+
const pastToward = towardPlace && (towardPlace.x - basePlace.x) * step.x + (towardPlace.y - basePlace.y) * step.y > 0;
|
|
69516
|
+
const side = pastToward ? towardPlace : basePlace;
|
|
69517
|
+
placeByArea.set(area, {
|
|
69518
|
+
x: side.x + step.x,
|
|
69519
|
+
y: side.y + step.y
|
|
69520
|
+
});
|
|
69521
|
+
return {
|
|
69522
|
+
placeByArea,
|
|
69523
|
+
leaningArea: toward && fraction >= 0.5 ? toward : base,
|
|
69524
|
+
progress: step.x || step.y
|
|
69525
|
+
};
|
|
69526
|
+
};
|
|
69527
|
+
|
|
69217
69528
|
// How much of a travel is left to make, as a fraction of the travel that was
|
|
69218
69529
|
// asked for: 1 for a move starting from rest, less for one picked up while the
|
|
69219
69530
|
// track was already moving. Everything is measured in px against the track's
|
|
@@ -69230,6 +69541,19 @@ const ratioOfOneTravel = (track, from, to, targetBefore) => {
|
|
|
69230
69541
|
const ratio = left / asked;
|
|
69231
69542
|
return ratio > 1 ? 1 : ratio;
|
|
69232
69543
|
};
|
|
69544
|
+
// The same for a travel taken over a row, which nobody asked for from anywhere in
|
|
69545
|
+
// particular: what is left, in boxes. Laid out sizes, like the scroll position that
|
|
69546
|
+
// `from` is read off: a row inside a popup still growing is not its transformed size.
|
|
69547
|
+
const ratioOfOneBox = (track, from, to) => {
|
|
69548
|
+
const box = {
|
|
69549
|
+
width: track.offsetWidth,
|
|
69550
|
+
height: track.offsetHeight
|
|
69551
|
+
};
|
|
69552
|
+
const start = offsetToPx(from, box);
|
|
69553
|
+
const end = offsetToPx(to, box);
|
|
69554
|
+
const ratio = Math.abs(end.x - start.x) / box.width + Math.abs(end.y - start.y) / box.height;
|
|
69555
|
+
return ratio > 1 ? 1 : ratio;
|
|
69556
|
+
};
|
|
69233
69557
|
|
|
69234
69558
|
// A translate ("-100% 0%", "-260px 0px", "none") as two numbers of pixels.
|
|
69235
69559
|
// Percentages are the size of the box, which is what a translate resolves them
|
|
@@ -69807,6 +70131,10 @@ const SlideContainer = ({
|
|
|
69807
70131
|
// The slide a hand has just landed on, for the render it asks for: it is
|
|
69808
70132
|
// already there, so there is nothing to play.
|
|
69809
70133
|
const handLandedRef = useRef(null);
|
|
70134
|
+
// The slide a row is being carried away from, pressed: it stays current, so only
|
|
70135
|
+
// a render says the row has to come back to it (see goToArea).
|
|
70136
|
+
const rowBackAskedRef = useRef(false);
|
|
70137
|
+
const [, askRowBack] = useState(0);
|
|
69810
70138
|
// Where the box rests on the row, and what the row starts from: a slide that
|
|
69811
70139
|
// holds on to the user ends the row on that side, and a scroll range starts at
|
|
69812
70140
|
// its first reachable slide (see writeRowAtRest).
|
|
@@ -70051,6 +70379,26 @@ const SlideContainer = ({
|
|
|
70051
70379
|
const paintCurrentArea = area => {
|
|
70052
70380
|
containerRef.current?.setAttribute(SLIDE_CURRENT_ATTRIBUTE, area);
|
|
70053
70381
|
};
|
|
70382
|
+
|
|
70383
|
+
// Where the row is going, for as long as the current slide has not changed:
|
|
70384
|
+
// anything saying where one is reads this first, and so does what a press would
|
|
70385
|
+
// do from there (see readSlideOnScreen). Left to the caller to announce, a landing
|
|
70386
|
+
// having more to say in the same breath.
|
|
70387
|
+
const writeHeading = headingElement => {
|
|
70388
|
+
const containerEl = containerRef.current;
|
|
70389
|
+
const area = headingElement ? readArea(headingElement) : null;
|
|
70390
|
+
// A window does not change slide, it rolls: it has nowhere to be heading.
|
|
70391
|
+
const heading = loop || area === null || area === containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ? null : area;
|
|
70392
|
+
if (heading === containerEl.getAttribute(SLIDE_HEADING_ATTRIBUTE)) {
|
|
70393
|
+
return;
|
|
70394
|
+
}
|
|
70395
|
+
if (heading === null) {
|
|
70396
|
+
containerEl.removeAttribute(SLIDE_HEADING_ATTRIBUTE);
|
|
70397
|
+
} else {
|
|
70398
|
+
containerEl.setAttribute(SLIDE_HEADING_ATTRIBUTE, heading);
|
|
70399
|
+
}
|
|
70400
|
+
paintWays();
|
|
70401
|
+
};
|
|
70054
70402
|
const markAnswered = area => {
|
|
70055
70403
|
const order = readMap().slideElements.map(readArea);
|
|
70056
70404
|
const rank = order.indexOf(area);
|
|
@@ -70216,6 +70564,7 @@ const SlideContainer = ({
|
|
|
70216
70564
|
offsetRef.current = "0% 0%";
|
|
70217
70565
|
track.style.setProperty("--slide-container-offset", "0% 0%");
|
|
70218
70566
|
containerRef.current.removeAttribute("data-slide-travelling");
|
|
70567
|
+
containerRef.current.removeAttribute("data-slide-row-taken");
|
|
70219
70568
|
sendRowToRest(currentElement);
|
|
70220
70569
|
renderNear(currentArea, {
|
|
70221
70570
|
alongside: true
|
|
@@ -70256,6 +70605,10 @@ const SlideContainer = ({
|
|
|
70256
70605
|
slideElements[0];
|
|
70257
70606
|
const currentArea = readArea(currentElement);
|
|
70258
70607
|
paintCurrentArea(currentArea);
|
|
70608
|
+
// In the commit the slide changes in, so nothing reads the slide left in between.
|
|
70609
|
+
if (!handMovingRef.current) {
|
|
70610
|
+
writeHeading(null);
|
|
70611
|
+
}
|
|
70259
70612
|
const realPlaceOf = area => placeOf.get(area) || {
|
|
70260
70613
|
x: 0,
|
|
70261
70614
|
y: 0
|
|
@@ -70286,8 +70639,26 @@ const SlideContainer = ({
|
|
|
70286
70639
|
// between the picture and the slide arriving, on the axis it walks.
|
|
70287
70640
|
let travelStep = null;
|
|
70288
70641
|
const drawnArea = stage ? stage.area : drawnAreaRef.current;
|
|
70289
|
-
const
|
|
70290
|
-
|
|
70642
|
+
const rowBackAsked = rowBackAskedRef.current;
|
|
70643
|
+
rowBackAskedRef.current = false;
|
|
70644
|
+
// Asked for over a row a hand has left moving: the row belongs to what was asked
|
|
70645
|
+
// for, and where the hand's snap would have ended decides nothing.
|
|
70646
|
+
const rowTaken = handMovingRef.current && !handLanded && (drawnArea !== currentArea || rowBackAsked);
|
|
70647
|
+
const rowStand = rowTaken ? readRowStand(containerRef.current, slideElements) : null;
|
|
70648
|
+
if (rowTaken) {
|
|
70649
|
+
handMovingRef.current = false;
|
|
70650
|
+
writeHeading(null);
|
|
70651
|
+
paintTravelProgress();
|
|
70652
|
+
}
|
|
70653
|
+
const travelStarts = !still && durationMs > 0 && drawnArea !== undefined && (drawnArea !== currentArea || rowTaken) && slideElements.some(slideElement => readArea(slideElement) === drawnArea);
|
|
70654
|
+
let overRow = null;
|
|
70655
|
+
if (travelStarts && rowStand) {
|
|
70656
|
+
overRow = stageOverRow(rowStand, currentArea, realPlaceOf, travelStepRef.current);
|
|
70657
|
+
stage = stageRef.current = {
|
|
70658
|
+
placeByArea: overRow.placeByArea,
|
|
70659
|
+
area: currentArea
|
|
70660
|
+
};
|
|
70661
|
+
} else if (travelStarts) {
|
|
70291
70662
|
// Where the slide being left IS — the place the stage gave it if a travel
|
|
70292
70663
|
// was already playing (a press landing mid-travel departs from where the
|
|
70293
70664
|
// eye is, not from the map), its own place otherwise.
|
|
@@ -70372,9 +70743,16 @@ const SlideContainer = ({
|
|
|
70372
70743
|
}
|
|
70373
70744
|
const track = trackRef.current;
|
|
70374
70745
|
containerRef.current.toggleAttribute("data-slide-travelling", Boolean(stage));
|
|
70375
|
-
|
|
70746
|
+
if (rowTaken) {
|
|
70747
|
+
containerRef.current.setAttribute("data-slide-row-taken", "");
|
|
70748
|
+
}
|
|
70749
|
+
// How far the track stands from where the scroll position rests: none at rest. A
|
|
70750
|
+
// box that is no scroller has no position, and the track says it all.
|
|
70376
70751
|
const rowPlace = onRow(currentPlace);
|
|
70377
|
-
const restPlace =
|
|
70752
|
+
const restPlace = containerRef.current.hasAttribute("data-slide-row-taken") ? {
|
|
70753
|
+
x: 0,
|
|
70754
|
+
y: 0
|
|
70755
|
+
} : restPlaceRef.current;
|
|
70378
70756
|
const offset = `${(restPlace.x - rowPlace.x) * 100}% ${(restPlace.y - rowPlace.y) * 100}%`;
|
|
70379
70757
|
// The travel that was ASKED for is still one box, whatever is left of it to
|
|
70380
70758
|
// cover: what is left takes what is left of the duration.
|
|
@@ -70397,6 +70775,9 @@ const SlideContainer = ({
|
|
|
70397
70775
|
const onScreenPx = trackOffsetPx(track, containerRef.current);
|
|
70398
70776
|
offsetOnScreen = `${onScreenPx.x}px ${onScreenPx.y}px`;
|
|
70399
70777
|
}
|
|
70778
|
+
if (overRow) {
|
|
70779
|
+
offsetOnScreen = rowStand.offset;
|
|
70780
|
+
}
|
|
70400
70781
|
const offsetBefore = offsetOnScreen ?? offsetRef.current;
|
|
70401
70782
|
offsetRef.current = offset;
|
|
70402
70783
|
track.style.setProperty("--slide-container-offset", offset);
|
|
@@ -70415,10 +70796,10 @@ const SlideContainer = ({
|
|
|
70415
70796
|
// short distance for a full duration. One box in `duration` is the pace;
|
|
70416
70797
|
// this only ever shortens it (a longer travel is not made slower, which
|
|
70417
70798
|
// would make a two-box move drag).
|
|
70418
|
-
const travelRatio = ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
70799
|
+
const travelRatio = overRow ? ratioOfOneBox(track, offsetBefore, offset) : ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
70419
70800
|
// Already moving, so there is no ease-in to play: it would stall the track
|
|
70420
70801
|
// for an instant right where the eye is following it.
|
|
70421
|
-
const easing = travelInFlight ? "ease-out" : "ease";
|
|
70802
|
+
const easing = travelInFlight || overRow ? "ease-out" : "ease";
|
|
70422
70803
|
// Cancelled rather than layered: two animations on the same property
|
|
70423
70804
|
// would blend, and what one sees then is neither of the two moves.
|
|
70424
70805
|
trackAnimationRef.current?.cancel();
|
|
@@ -70426,15 +70807,21 @@ const SlideContainer = ({
|
|
|
70426
70807
|
translate: offsetBefore
|
|
70427
70808
|
}, {
|
|
70428
70809
|
translate: offset
|
|
70429
|
-
}],
|
|
70810
|
+
}],
|
|
70811
|
+
// Backwards: the first frame's clock can read a fraction of a millisecond
|
|
70812
|
+
// before the start time Chrome gives the animation, which would show the
|
|
70813
|
+
// travel already over.
|
|
70814
|
+
{
|
|
70430
70815
|
duration: durationMs * travelRatio,
|
|
70431
|
-
easing
|
|
70816
|
+
easing,
|
|
70817
|
+
fill: "backwards"
|
|
70432
70818
|
});
|
|
70433
70819
|
// The trait travels with the slides, from a whole box away.
|
|
70434
|
-
const
|
|
70820
|
+
const stepProgress = travelStep ? travelStep.x || travelStep.y : 0;
|
|
70821
|
+
const progressFrom = overRow ? overRow.progress : stepProgress;
|
|
70435
70822
|
// The slide the picture leans on for the length of it is the one being
|
|
70436
70823
|
// LEFT: it is the second one in the frame until the travel is over.
|
|
70437
|
-
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, drawnArea);
|
|
70824
|
+
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, overRow ? overRow.leaningArea : drawnArea);
|
|
70438
70825
|
// Presses still waiting behind this one: it is already late, so it is
|
|
70439
70826
|
// sent home at once rather than played out at the pace of someone who
|
|
70440
70827
|
// has stopped pressing. Someone pressing → four times is asking to be
|
|
@@ -70455,6 +70842,17 @@ const SlideContainer = ({
|
|
|
70455
70842
|
// thing it was standing for is over.
|
|
70456
70843
|
settleTravel();
|
|
70457
70844
|
}
|
|
70845
|
+
// The row taken with nothing playing to give it back (no duration, a page change
|
|
70846
|
+
// carrying the slide): no scroller for as long as the compositor takes to hear it.
|
|
70847
|
+
if (!stageRef.current && containerRef.current.hasAttribute("data-slide-row-taken")) {
|
|
70848
|
+
requestAnimationFrame(() => {
|
|
70849
|
+
requestAnimationFrame(() => {
|
|
70850
|
+
if (trackAnimationRef.current?.playState !== "running") {
|
|
70851
|
+
settleTravel();
|
|
70852
|
+
}
|
|
70853
|
+
});
|
|
70854
|
+
});
|
|
70855
|
+
}
|
|
70458
70856
|
// A window waiting for its travel to be over (see goToArea's own loop
|
|
70459
70857
|
// branch): the animation says when, and says it about the move that just
|
|
70460
70858
|
// started rather than about a duration counted out beside it.
|
|
@@ -70518,9 +70916,9 @@ const SlideContainer = ({
|
|
|
70518
70916
|
sendRowToRest(currentElement);
|
|
70519
70917
|
}
|
|
70520
70918
|
// Said last, and after data-current has been written: what a travel would do
|
|
70521
|
-
// is read off the map from the slide
|
|
70522
|
-
//
|
|
70523
|
-
paintWays(
|
|
70919
|
+
// is read off the map from the slide on screen, and off the locks that slide is
|
|
70920
|
+
// wearing in this very commit.
|
|
70921
|
+
paintWays();
|
|
70524
70922
|
// …and once everything this commit had to write is written, the box says so
|
|
70525
70923
|
// — a single announcement for a render that may have changed several of the
|
|
70526
70924
|
// facts at once.
|
|
@@ -70567,14 +70965,18 @@ const SlideContainer = ({
|
|
|
70567
70965
|
placeOf
|
|
70568
70966
|
} = readMap();
|
|
70569
70967
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
70570
|
-
if (!area
|
|
70968
|
+
if (!area) {
|
|
70571
70969
|
return false;
|
|
70572
70970
|
}
|
|
70971
|
+
// What a press leaves is the slide on screen, which is what paintWays offers from.
|
|
70972
|
+
// A landing is the row arriving there, and what it leaves is the current one.
|
|
70973
|
+
const areaLeft = landed ? readArea(currentElement) : readSlideOnScreen(containerRef.current) ?? readArea(currentElement);
|
|
70974
|
+
const elementLeft = slideElements.find(slideElement => readArea(slideElement) === areaLeft) || currentElement;
|
|
70573
70975
|
if (forward === undefined) {
|
|
70574
70976
|
// Asked for by name (--navi-go-to-slide, --navi-back) rather than by a
|
|
70575
70977
|
// direction: which way it is is still a fact, read off the map, and it is
|
|
70576
70978
|
// what says which of the two locks below applies.
|
|
70577
|
-
const from = placeOf.get(
|
|
70979
|
+
const from = placeOf.get(areaLeft) || {
|
|
70578
70980
|
x: 0,
|
|
70579
70981
|
y: 0
|
|
70580
70982
|
};
|
|
@@ -70592,9 +70994,18 @@ const SlideContainer = ({
|
|
|
70592
70994
|
// one departure the forward hold does not apply to. Said here rather than
|
|
70593
70995
|
// by dropping the attribute, which stays the render's to write — a hold
|
|
70594
70996
|
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
70595
|
-
if (forward ? !released &&
|
|
70997
|
+
if (area !== areaLeft && (forward ? !released && elementLeft.hasAttribute("data-prevent-nav-next") : elementLeft.hasAttribute("data-prevent-nav-previous"))) {
|
|
70596
70998
|
return false;
|
|
70597
70999
|
}
|
|
71000
|
+
if (area === readArea(currentElement)) {
|
|
71001
|
+
// Nowhere to go, unless a hand has left the row on its way off this slide.
|
|
71002
|
+
if (!handMovingRef.current) {
|
|
71003
|
+
return false;
|
|
71004
|
+
}
|
|
71005
|
+
rowBackAskedRef.current = true;
|
|
71006
|
+
askRowBack(count => count + 1);
|
|
71007
|
+
return true;
|
|
71008
|
+
}
|
|
70598
71009
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
70599
71010
|
// who pressed what, and whether they were on the keyboard, is knowable now
|
|
70600
71011
|
// and gone a render later. The slide arriving is made reachable on the spot
|
|
@@ -70976,7 +71387,7 @@ const SlideContainer = ({
|
|
|
70976
71387
|
const move = (dx, dy, event, {
|
|
70977
71388
|
value,
|
|
70978
71389
|
released
|
|
70979
|
-
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
71390
|
+
} = {}) => goToArea(areaTowards(dx, dy, readSlideOnScreen(containerRef.current)), {
|
|
70980
71391
|
forward: dx > 0 || dy > 0,
|
|
70981
71392
|
event,
|
|
70982
71393
|
value,
|
|
@@ -71054,11 +71465,13 @@ const SlideContainer = ({
|
|
|
71054
71465
|
// Where a hand has the row, in the terms of --slide-travel-progress: from the
|
|
71055
71466
|
// current slide while it stands next to it, and past that between the two it
|
|
71056
71467
|
// stands between, as far as the hand carries it.
|
|
71057
|
-
const paintNativeRow = (
|
|
71058
|
-
|
|
71468
|
+
const paintNativeRow = ({
|
|
71469
|
+
finger
|
|
71470
|
+
}) => {
|
|
71471
|
+
const containerEl = containerRef.current;
|
|
71472
|
+
if (trackAnimationRef.current?.playState === "running" || containerEl.hasAttribute("data-slide-row-taken")) {
|
|
71059
71473
|
return;
|
|
71060
71474
|
}
|
|
71061
|
-
const containerEl = containerRef.current;
|
|
71062
71475
|
const slideElements = Array.from(trackRef.current.children);
|
|
71063
71476
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
71064
71477
|
if (!currentElement) {
|
|
@@ -71066,6 +71479,9 @@ const SlideContainer = ({
|
|
|
71066
71479
|
}
|
|
71067
71480
|
if (restsOn(containerEl, currentElement)) {
|
|
71068
71481
|
if (handMovingRef.current) {
|
|
71482
|
+
if (!finger) {
|
|
71483
|
+
writeHeading(null);
|
|
71484
|
+
}
|
|
71069
71485
|
paintTravelProgress();
|
|
71070
71486
|
}
|
|
71071
71487
|
return;
|
|
@@ -71086,6 +71502,12 @@ const SlideContainer = ({
|
|
|
71086
71502
|
}
|
|
71087
71503
|
element.style.setProperty("--slide-travel-progress", forward ? -fraction : fraction);
|
|
71088
71504
|
}
|
|
71505
|
+
// Nothing will say when a trackpad's fingers leave, and a long stroke would
|
|
71506
|
+
// keep the first tab lit for the whole trip: the nearest slide answers. A finger
|
|
71507
|
+
// has a lift to wait for (headNativeRow), and until then the slide held stands.
|
|
71508
|
+
if (!finger) {
|
|
71509
|
+
writeHeading(fraction >= 0.5 && toward ? toward : base);
|
|
71510
|
+
}
|
|
71089
71511
|
paintTravelToward(toward ? readArea(toward) : null);
|
|
71090
71512
|
// What the hand reaches ahead of the neighbours built at rest is asked for on
|
|
71091
71513
|
// the way: an empty slide goes by otherwise.
|
|
@@ -71093,6 +71515,28 @@ const SlideContainer = ({
|
|
|
71093
71515
|
alongside: true
|
|
71094
71516
|
});
|
|
71095
71517
|
};
|
|
71518
|
+
|
|
71519
|
+
// A finger has lifted and the row moves: on past the slide it stands after, or
|
|
71520
|
+
// back to it. Already standing on one, it has arrived, whichever way it came.
|
|
71521
|
+
const headNativeRow = movedForward => {
|
|
71522
|
+
if (!handMovingRef.current || trackAnimationRef.current?.playState === "running") {
|
|
71523
|
+
return;
|
|
71524
|
+
}
|
|
71525
|
+
const slideElements = Array.from(trackRef.current.children);
|
|
71526
|
+
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
71527
|
+
if (!currentElement) {
|
|
71528
|
+
return;
|
|
71529
|
+
}
|
|
71530
|
+
const {
|
|
71531
|
+
base,
|
|
71532
|
+
toward,
|
|
71533
|
+
fraction,
|
|
71534
|
+
forward
|
|
71535
|
+
} = readRowStretch(containerRef.current, slideElements, currentElement);
|
|
71536
|
+
const goesOn = fraction > 0 && movedForward === forward;
|
|
71537
|
+
writeHeading(goesOn && toward ? toward : base);
|
|
71538
|
+
announceState();
|
|
71539
|
+
};
|
|
71096
71540
|
const landNativeRow = scrollEndEvent => {
|
|
71097
71541
|
if (!handMovingRef.current) {
|
|
71098
71542
|
return;
|
|
@@ -71107,13 +71551,15 @@ const SlideContainer = ({
|
|
|
71107
71551
|
return;
|
|
71108
71552
|
}
|
|
71109
71553
|
handMovingRef.current = false;
|
|
71110
|
-
paintTravelProgress();
|
|
71111
71554
|
const area = readArea(landedElement);
|
|
71112
71555
|
const currentArea = containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
71113
71556
|
if (area === currentArea) {
|
|
71557
|
+
writeHeading(null);
|
|
71558
|
+
paintTravelProgress();
|
|
71114
71559
|
settleTravel();
|
|
71115
71560
|
return;
|
|
71116
71561
|
}
|
|
71562
|
+
paintTravelProgress();
|
|
71117
71563
|
const event = scrollEndEvent ?? new Event("scrollend");
|
|
71118
71564
|
if (loop) {
|
|
71119
71565
|
// A window does not change slide, it rolls: back at rest in the render the
|
|
@@ -71144,6 +71590,8 @@ const SlideContainer = ({
|
|
|
71144
71590
|
}
|
|
71145
71591
|
// Refused: the row is put back in view of the slide that stays.
|
|
71146
71592
|
handLandedRef.current = null;
|
|
71593
|
+
writeHeading(null);
|
|
71594
|
+
announceState();
|
|
71147
71595
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
71148
71596
|
containerEl.scrollTo({
|
|
71149
71597
|
left: currentElement.offsetLeft,
|
|
@@ -71157,11 +71605,13 @@ const SlideContainer = ({
|
|
|
71157
71605
|
const nativeRowHandlersRef = useRef(null);
|
|
71158
71606
|
nativeRowHandlersRef.current = {
|
|
71159
71607
|
paintNativeRow,
|
|
71608
|
+
headNativeRow,
|
|
71160
71609
|
landNativeRow
|
|
71161
71610
|
};
|
|
71162
71611
|
useLayoutEffect(() => {
|
|
71163
71612
|
return watchRowScroll(containerRef.current, {
|
|
71164
|
-
onScroll:
|
|
71613
|
+
onScroll: move => nativeRowHandlersRef.current.paintNativeRow(move),
|
|
71614
|
+
onLeftToSnap: forward => nativeRowHandlersRef.current.headNativeRow(forward),
|
|
71165
71615
|
onLand: event => nativeRowHandlersRef.current.landNativeRow(event)
|
|
71166
71616
|
});
|
|
71167
71617
|
}, []);
|
|
@@ -71239,13 +71689,19 @@ const SlideContainer = ({
|
|
|
71239
71689
|
// ([data-slide-ways~="right"]), or through useSlideContainer, which watches
|
|
71240
71690
|
// them. Nothing is told, everything is read: it is what lets them sit
|
|
71241
71691
|
// anywhere, and what lets a container nobody drives still be followed.
|
|
71242
|
-
const paintWays =
|
|
71692
|
+
const paintWays = () => {
|
|
71243
71693
|
const ways = [];
|
|
71244
71694
|
const held = [];
|
|
71695
|
+
const {
|
|
71696
|
+
slideElements,
|
|
71697
|
+
order
|
|
71698
|
+
} = readMap();
|
|
71699
|
+
const areaOnScreen = readSlideOnScreen(containerRef.current);
|
|
71700
|
+
const elementOnScreen = slideElements.find(slideElement => readArea(slideElement) === areaOnScreen);
|
|
71245
71701
|
// Whether the slide being left says no, this way. The one gate again, read
|
|
71246
71702
|
// exactly as goToArea reads it at the moment of the travel — so what is
|
|
71247
71703
|
// published here and what would happen cannot disagree.
|
|
71248
|
-
const holdsTowards = forward =>
|
|
71704
|
+
const holdsTowards = forward => elementOnScreen?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
71249
71705
|
const say = (name, offered, forward) => {
|
|
71250
71706
|
if (!offered) {
|
|
71251
71707
|
return;
|
|
@@ -71262,18 +71718,14 @@ const SlideContainer = ({
|
|
|
71262
71718
|
// would be saying it about every row on the page.
|
|
71263
71719
|
continue;
|
|
71264
71720
|
}
|
|
71265
|
-
say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
|
|
71721
|
+
say(direction, Boolean(areaTowards(dx, dy, areaOnScreen)), dx > 0 || dy > 0);
|
|
71266
71722
|
}
|
|
71267
71723
|
// The two ends are ways out like the others — "all the way that way", said
|
|
71268
71724
|
// by the map's own order rather than by a direction (see goToEnd, and
|
|
71269
71725
|
// SlideContainer.First / .Last). Offered while one is not already standing
|
|
71270
71726
|
// there, and held by the same lock the direction they lie in would be.
|
|
71271
|
-
|
|
71272
|
-
|
|
71273
|
-
} = readMap();
|
|
71274
|
-
const currentArea = currentElement ? readArea(currentElement) : undefined;
|
|
71275
|
-
say("first", order.length > 0 && order[0] !== currentArea, false);
|
|
71276
|
-
say("last", order.length > 0 && order[order.length - 1] !== currentArea, true);
|
|
71727
|
+
say("first", order.length > 0 && order[0] !== areaOnScreen, false);
|
|
71728
|
+
say("last", order.length > 0 && order[order.length - 1] !== areaOnScreen, true);
|
|
71277
71729
|
for (const element of travelPainters()) {
|
|
71278
71730
|
if (ways.length) {
|
|
71279
71731
|
element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
|
|
@@ -71366,7 +71818,8 @@ const SlideContainer = ({
|
|
|
71366
71818
|
"--slide-travel-progress": 0
|
|
71367
71819
|
}], {
|
|
71368
71820
|
duration: durationMs,
|
|
71369
|
-
easing
|
|
71821
|
+
easing,
|
|
71822
|
+
fill: "backwards"
|
|
71370
71823
|
}));
|
|
71371
71824
|
progressAnimationsRef.current[0].finished.then(() => {
|
|
71372
71825
|
progressAnimationsRef.current = [];
|
|
@@ -107832,7 +108285,9 @@ const StepList = ({
|
|
|
107832
108285
|
const read = () => {
|
|
107833
108286
|
const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
107834
108287
|
const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
|
|
107835
|
-
|
|
108288
|
+
// Which step is lit follows the screen. The halo counts from the current slide,
|
|
108289
|
+
// which is what the progress it draws with is measured against.
|
|
108290
|
+
setContainerCurrent(readSlideOnScreen(containerElement));
|
|
107836
108291
|
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
107837
108292
|
if (currentIdx === -1) {
|
|
107838
108293
|
// A slide no step names (a confirmation screen): the halo is not
|