@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/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) {
|
|
@@ -41593,8 +41593,9 @@ const publishRouteRowMove = (move) => {
|
|
|
41593
41593
|
* itself; a counter, a title or a breadcrumb around the box reads it first.
|
|
41594
41594
|
*
|
|
41595
41595
|
* It goes back to `null` as soon as the hand has let go and the row moves. Nothing
|
|
41596
|
-
* says when the fingers leave a trackpad: there it
|
|
41597
|
-
* 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.
|
|
41598
41599
|
*
|
|
41599
41600
|
* One for the document. `route` says which row, for a page holding several boxes.
|
|
41600
41601
|
*/
|
|
@@ -41612,6 +41613,13 @@ const holdPage = (page, headingPage) => {
|
|
|
41612
41613
|
}
|
|
41613
41614
|
};
|
|
41614
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
|
+
|
|
41615
41623
|
const releaseHeldPage = () => {
|
|
41616
41624
|
routeTravelSettledPageSignal.value = null;
|
|
41617
41625
|
headingPageSignal.value = null;
|
|
@@ -41671,17 +41679,19 @@ const STOPPED_SILENCE = 150;
|
|
|
41671
41679
|
const isOnRow = (page) =>
|
|
41672
41680
|
page.offsetWidth > 0 && !page.hasAttribute("data-off-row");
|
|
41673
41681
|
|
|
41674
|
-
|
|
41682
|
+
// `sideways`: see readRowStretch.
|
|
41683
|
+
const restsOn = (scroller, page, { sideways } = {}) =>
|
|
41675
41684
|
Math.abs(page.offsetLeft - scroller.scrollLeft) <= AT_REST_DISTANCE &&
|
|
41676
|
-
|
|
41685
|
+
(sideways ||
|
|
41686
|
+
Math.abs(page.offsetTop - scroller.scrollTop) <= AT_REST_DISTANCE);
|
|
41677
41687
|
|
|
41678
41688
|
/**
|
|
41679
41689
|
* @returns {Element|undefined} the page the row rests on, nothing while it stands
|
|
41680
41690
|
* between two: a hand can pause midway, and an engine may call that the end of
|
|
41681
41691
|
* a scroll.
|
|
41682
41692
|
*/
|
|
41683
|
-
const pageAtRest = (scroller, pages) =>
|
|
41684
|
-
pages.find((page) => isOnRow(page) && restsOn(scroller, page));
|
|
41693
|
+
const pageAtRest = (scroller, pages, options) =>
|
|
41694
|
+
pages.find((page) => isOnRow(page) && restsOn(scroller, page, options));
|
|
41685
41695
|
|
|
41686
41696
|
/**
|
|
41687
41697
|
* Where the row stands between two neighbouring pages, on the side it has moved
|
|
@@ -41689,14 +41699,18 @@ const pageAtRest = (scroller, pages) =>
|
|
|
41689
41699
|
* since is walked over. A map moves along one axis at a time, the one it has
|
|
41690
41700
|
* moved furthest on.
|
|
41691
41701
|
*
|
|
41702
|
+
* @param {object} [options]
|
|
41703
|
+
* @param {boolean} [options.sideways] - a row with one line, whatever `offsetTop` its
|
|
41704
|
+
* pages read: a page pinned with `top` is still on it.
|
|
41692
41705
|
* @returns {{base: Element, toward: Element|undefined, fraction: number, forward: boolean}}
|
|
41693
41706
|
* the last page the row has passed (`page` itself until then), the one after,
|
|
41694
41707
|
* and how far between the two from 0 to 1. Nothing `toward` past the last.
|
|
41695
41708
|
*/
|
|
41696
|
-
const readRowStretch = (scroller, pages, page) => {
|
|
41709
|
+
const readRowStretch = (scroller, pages, page, { sideways } = {}) => {
|
|
41697
41710
|
const alongX =
|
|
41711
|
+
sideways ||
|
|
41698
41712
|
Math.abs(scroller.scrollLeft - page.offsetLeft) >=
|
|
41699
|
-
|
|
41713
|
+
Math.abs(scroller.scrollTop - page.offsetTop);
|
|
41700
41714
|
const position = alongX ? "offsetLeft" : "offsetTop";
|
|
41701
41715
|
const cross = alongX ? "offsetTop" : "offsetLeft";
|
|
41702
41716
|
const scrolled = alongX ? scroller.scrollLeft : scroller.scrollTop;
|
|
@@ -41704,7 +41718,8 @@ const readRowStretch = (scroller, pages, page) => {
|
|
|
41704
41718
|
const step = forward ? 1 : -1;
|
|
41705
41719
|
const line = pages
|
|
41706
41720
|
.filter(
|
|
41707
|
-
(candidate) =>
|
|
41721
|
+
(candidate) =>
|
|
41722
|
+
isOnRow(candidate) && (sideways || candidate[cross] === page[cross]),
|
|
41708
41723
|
)
|
|
41709
41724
|
.sort((pageA, pageB) => pageA[position] - pageB[position]);
|
|
41710
41725
|
let index = line.indexOf(page);
|
|
@@ -41773,12 +41788,6 @@ const watchRowScroll = (
|
|
|
41773
41788
|
liftedAt = null;
|
|
41774
41789
|
onLeftToSnap?.(moved > 0);
|
|
41775
41790
|
};
|
|
41776
|
-
const onTouchStart = () => {
|
|
41777
|
-
touching = true;
|
|
41778
|
-
touchedSinceStop = true;
|
|
41779
|
-
liftedAt = null;
|
|
41780
|
-
clearTimeout(stoppedTimeout);
|
|
41781
|
-
};
|
|
41782
41791
|
const onTouchEnd = (touchEvent) => {
|
|
41783
41792
|
touching = touchEvent.touches.length > 0;
|
|
41784
41793
|
if (!touching) {
|
|
@@ -41786,18 +41795,31 @@ const watchRowScroll = (
|
|
|
41786
41795
|
armSilence();
|
|
41787
41796
|
}
|
|
41788
41797
|
};
|
|
41798
|
+
const onTouchStart = (touchEvent) => {
|
|
41799
|
+
touching = true;
|
|
41800
|
+
touchedSinceStop = true;
|
|
41801
|
+
liftedAt = null;
|
|
41802
|
+
clearTimeout(stoppedTimeout);
|
|
41803
|
+
// Listened for on what the finger landed on, where the end of a touch is always
|
|
41804
|
+
// told: a page changing under the finger may have taken it out of the document by
|
|
41805
|
+
// then, and nothing comes up to the row from there.
|
|
41806
|
+
const { target } = touchEvent;
|
|
41807
|
+
const onEnd = (endEvent) => {
|
|
41808
|
+
target.removeEventListener("touchend", onEnd);
|
|
41809
|
+
target.removeEventListener("touchcancel", onEnd);
|
|
41810
|
+
onTouchEnd(endEvent);
|
|
41811
|
+
};
|
|
41812
|
+
target.addEventListener("touchend", onEnd, { passive: true });
|
|
41813
|
+
target.addEventListener("touchcancel", onEnd, { passive: true });
|
|
41814
|
+
};
|
|
41789
41815
|
scroller.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
41790
41816
|
scroller.addEventListener("scrollend", onLand);
|
|
41791
41817
|
scroller.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
41792
|
-
scroller.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
41793
|
-
scroller.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
41794
41818
|
return () => {
|
|
41795
41819
|
clearTimeout(stoppedTimeout);
|
|
41796
41820
|
scroller.removeEventListener("scroll", onScrollEvent);
|
|
41797
41821
|
scroller.removeEventListener("scrollend", onLand);
|
|
41798
41822
|
scroller.removeEventListener("touchstart", onTouchStart);
|
|
41799
|
-
scroller.removeEventListener("touchend", onTouchEnd);
|
|
41800
|
-
scroller.removeEventListener("touchcancel", onTouchEnd);
|
|
41801
41823
|
};
|
|
41802
41824
|
};
|
|
41803
41825
|
|
|
@@ -41835,6 +41857,19 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41835
41857
|
overflow: visible;
|
|
41836
41858
|
}
|
|
41837
41859
|
|
|
41860
|
+
&[data-route-row-taken] {
|
|
41861
|
+
scroll-snap-type: none;
|
|
41862
|
+
}
|
|
41863
|
+
|
|
41864
|
+
&[data-route-row-taken="clip"] {
|
|
41865
|
+
overflow: clip;
|
|
41866
|
+
|
|
41867
|
+
& > [data-route-row-pane], & > [data-route-row-live], & > [data-route-row-pictures] > * {
|
|
41868
|
+
left: var(--navi-route-row-stand);
|
|
41869
|
+
position: relative;
|
|
41870
|
+
}
|
|
41871
|
+
}
|
|
41872
|
+
|
|
41838
41873
|
& > [data-route-row-pane] {
|
|
41839
41874
|
scroll-snap-align: start;
|
|
41840
41875
|
scroll-snap-stop: always;
|
|
@@ -41891,6 +41926,11 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41891
41926
|
// there yet: past what a hand that only shook covers, so that a shake costs
|
|
41892
41927
|
// neither a navigation nor the requests of a page.
|
|
41893
41928
|
const SET_OFF_PULL = 16;
|
|
41929
|
+
// A row of routes is one line, whatever `offsetTop` its pinned panes read (see the pin
|
|
41930
|
+
// above): they are on it all the same.
|
|
41931
|
+
const ROW_READING = {
|
|
41932
|
+
sideways: true
|
|
41933
|
+
};
|
|
41894
41934
|
|
|
41895
41935
|
/**
|
|
41896
41936
|
* @type {import("preact").FunctionComponent<{
|
|
@@ -42064,10 +42104,7 @@ const RouteTravel = ({
|
|
|
42064
42104
|
const paneCurrent = panesOf(row)[currentIndex];
|
|
42065
42105
|
if (paneCurrent) {
|
|
42066
42106
|
movement.travelling = true;
|
|
42067
|
-
|
|
42068
|
-
left: paneCurrent.offsetLeft,
|
|
42069
|
-
behavior: "smooth"
|
|
42070
|
-
});
|
|
42107
|
+
travelRowTo(currentIndex, movement);
|
|
42071
42108
|
}
|
|
42072
42109
|
});
|
|
42073
42110
|
};
|
|
@@ -42084,7 +42121,7 @@ const RouteTravel = ({
|
|
|
42084
42121
|
base,
|
|
42085
42122
|
toward,
|
|
42086
42123
|
fraction
|
|
42087
|
-
} =
|
|
42124
|
+
} = readRowFrame(row, panes);
|
|
42088
42125
|
publishRouteRowMove({
|
|
42089
42126
|
basePath: pathOf(pages[panes.indexOf(base)]),
|
|
42090
42127
|
towardPath: toward ? pathOf(pages[panes.indexOf(toward)]) : null,
|
|
@@ -42092,6 +42129,108 @@ const RouteTravel = ({
|
|
|
42092
42129
|
});
|
|
42093
42130
|
};
|
|
42094
42131
|
|
|
42132
|
+
// A travel no hand set off is one box long however far the page is, as in a
|
|
42133
|
+
// SlideContainer: the browser's smooth scroll would fly past every page in between.
|
|
42134
|
+
// So the page asked for takes the column next to what is in the frame, on the side
|
|
42135
|
+
// it already stands, which moves nothing on screen, and endMovement puts the columns
|
|
42136
|
+
// back. Columns rather than offsets: every page stays a place to snap to, and none
|
|
42137
|
+
// has to be hidden.
|
|
42138
|
+
const travelRowTo = (index, movement) => {
|
|
42139
|
+
const row = rowRef.current;
|
|
42140
|
+
const panes = panesOf(row);
|
|
42141
|
+
const pane = panes[index];
|
|
42142
|
+
if (!pane) {
|
|
42143
|
+
return;
|
|
42144
|
+
}
|
|
42145
|
+
const {
|
|
42146
|
+
base,
|
|
42147
|
+
toward,
|
|
42148
|
+
fraction
|
|
42149
|
+
} = readRowFrame(row, panes);
|
|
42150
|
+
const inFrame = toward && fraction > 0 ? [base, toward] : [base];
|
|
42151
|
+
if (!inFrame.includes(pane)) {
|
|
42152
|
+
// In a window, what the window put in a column moves with its pane.
|
|
42153
|
+
const standingIn = paneIndex => {
|
|
42154
|
+
const standing = [panes[paneIndex]];
|
|
42155
|
+
if (windowed) {
|
|
42156
|
+
const picture = movement.pictures.get(paneIndex);
|
|
42157
|
+
if (picture) {
|
|
42158
|
+
standing.push(picture);
|
|
42159
|
+
}
|
|
42160
|
+
if (paneIndex === latestRef.current.liveIndex) {
|
|
42161
|
+
standing.push(liveRef.current);
|
|
42162
|
+
}
|
|
42163
|
+
}
|
|
42164
|
+
return standing;
|
|
42165
|
+
};
|
|
42166
|
+
const columns = [...panes].sort((a, b) => a.offsetLeft - b.offsetLeft);
|
|
42167
|
+
const first = columns.indexOf(inFrame[0]);
|
|
42168
|
+
const last = columns.indexOf(inFrame[inFrame.length - 1]);
|
|
42169
|
+
const from = columns.indexOf(pane);
|
|
42170
|
+
columns.splice(from, 1);
|
|
42171
|
+
columns.splice(from < first ? first - 1 : last + 1, 0, pane);
|
|
42172
|
+
movement.placed ||= new Map();
|
|
42173
|
+
columns.forEach((eachPane, column) => {
|
|
42174
|
+
for (const element of standingIn(panes.indexOf(eachPane))) {
|
|
42175
|
+
if (!movement.placed.has(element)) {
|
|
42176
|
+
movement.placed.set(element, element.style.gridColumn);
|
|
42177
|
+
}
|
|
42178
|
+
element.style.gridColumn = String(column + 1);
|
|
42179
|
+
}
|
|
42180
|
+
});
|
|
42181
|
+
}
|
|
42182
|
+
row.scrollTo({
|
|
42183
|
+
left: pane.offsetLeft,
|
|
42184
|
+
behavior: "smooth"
|
|
42185
|
+
});
|
|
42186
|
+
};
|
|
42187
|
+
|
|
42188
|
+
// Every column back where the map says, and the row on the page of the address, in
|
|
42189
|
+
// one go: that page stands at the same place on screen either way.
|
|
42190
|
+
const putRowBack = movement => {
|
|
42191
|
+
const row = rowRef.current;
|
|
42192
|
+
if (movement.placed) {
|
|
42193
|
+
for (const [element, column] of movement.placed) {
|
|
42194
|
+
element.style.gridColumn = column;
|
|
42195
|
+
}
|
|
42196
|
+
movement.placed = null;
|
|
42197
|
+
}
|
|
42198
|
+
const pane = panesOf(row)[latestRef.current.currentIndex];
|
|
42199
|
+
if (pane && !restsOn(row, pane, ROW_READING)) {
|
|
42200
|
+
row.scrollTo({
|
|
42201
|
+
left: pane.offsetLeft,
|
|
42202
|
+
behavior: "instant"
|
|
42203
|
+
});
|
|
42204
|
+
}
|
|
42205
|
+
};
|
|
42206
|
+
|
|
42207
|
+
// A tab pressed over a row a hand has left moving, which the browser plays on and
|
|
42208
|
+
// overwrites a travel with once that travel is over: the snap is ended first. Given
|
|
42209
|
+
// back, the row stands where it stood. A frame later, because until then WebKit still
|
|
42210
|
+
// starts from where a box that is no scroller stood: a smooth scroll to the first page
|
|
42211
|
+
// sees nothing to do.
|
|
42212
|
+
const takeRowFromHand = movement => {
|
|
42213
|
+
const row = rowRef.current;
|
|
42214
|
+
const position = row.scrollLeft;
|
|
42215
|
+
movement.rowTaken = true;
|
|
42216
|
+
// A travel no hand set off holds nothing: it goes where it goes.
|
|
42217
|
+
releaseHeldPage();
|
|
42218
|
+
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
42219
|
+
row.setAttribute("data-route-row-taken", "clip");
|
|
42220
|
+
afterFrames(2, () => {
|
|
42221
|
+
if (movementRef.current !== movement) {
|
|
42222
|
+
return;
|
|
42223
|
+
}
|
|
42224
|
+
row.setAttribute("data-route-row-taken", "");
|
|
42225
|
+
row.style.removeProperty("--navi-route-row-stand");
|
|
42226
|
+
afterFrames(1, () => {
|
|
42227
|
+
if (movementRef.current === movement) {
|
|
42228
|
+
travelRowTo(latestRef.current.currentIndex, movement);
|
|
42229
|
+
}
|
|
42230
|
+
});
|
|
42231
|
+
});
|
|
42232
|
+
};
|
|
42233
|
+
|
|
42095
42234
|
// The row has stopped, on the page of the address: everything held for the
|
|
42096
42235
|
// movement is let go of in one go, before anything paints.
|
|
42097
42236
|
const endMovement = () => {
|
|
@@ -42101,7 +42240,9 @@ const RouteTravel = ({
|
|
|
42101
42240
|
return;
|
|
42102
42241
|
}
|
|
42103
42242
|
movementRef.current = null;
|
|
42243
|
+
putRowBack(movement);
|
|
42104
42244
|
row.removeAttribute("data-moving");
|
|
42245
|
+
row.removeAttribute("data-route-row-taken");
|
|
42105
42246
|
publishRouteRowMove(null);
|
|
42106
42247
|
releaseHeldPage();
|
|
42107
42248
|
if (liveRef.current) {
|
|
@@ -42139,7 +42280,7 @@ const RouteTravel = ({
|
|
|
42139
42280
|
const pane = panesOf(row)[currentIndex];
|
|
42140
42281
|
const answered = answeredPageRef.current;
|
|
42141
42282
|
if (samePage(answered, page)) {
|
|
42142
|
-
if (pane && !movementRef.current && !restsOn(row, pane)) {
|
|
42283
|
+
if (pane && !movementRef.current && !restsOn(row, pane, ROW_READING)) {
|
|
42143
42284
|
row.scrollTo({
|
|
42144
42285
|
left: pane.offsetLeft,
|
|
42145
42286
|
behavior: "instant"
|
|
@@ -42155,13 +42296,13 @@ const RouteTravel = ({
|
|
|
42155
42296
|
if (asked && samePage(asked, page)) {
|
|
42156
42297
|
askedPageRef.current = null;
|
|
42157
42298
|
// The hand landed before the address did.
|
|
42158
|
-
if (movementRef.current?.landed && restsOn(row, pane)) {
|
|
42299
|
+
if (movementRef.current?.landed && restsOn(row, pane, ROW_READING)) {
|
|
42159
42300
|
Promise.resolve().then(endMovement);
|
|
42160
42301
|
}
|
|
42161
42302
|
return;
|
|
42162
42303
|
}
|
|
42163
|
-
if (!answered || restsOn(row, pane)) {
|
|
42164
|
-
if (!restsOn(row, pane)) {
|
|
42304
|
+
if (!answered || restsOn(row, pane, ROW_READING)) {
|
|
42305
|
+
if (!restsOn(row, pane, ROW_READING)) {
|
|
42165
42306
|
row.scrollTo({
|
|
42166
42307
|
left: pane.offsetLeft,
|
|
42167
42308
|
behavior: "instant"
|
|
@@ -42169,13 +42310,18 @@ const RouteTravel = ({
|
|
|
42169
42310
|
}
|
|
42170
42311
|
return;
|
|
42171
42312
|
}
|
|
42172
|
-
|
|
42313
|
+
const movementBefore = movementRef.current;
|
|
42314
|
+
const movement = beginMovement({
|
|
42173
42315
|
travelling: true
|
|
42174
42316
|
});
|
|
42175
|
-
|
|
42176
|
-
|
|
42177
|
-
|
|
42178
|
-
|
|
42317
|
+
if (movementBefore?.byHand && !movementBefore.rowTaken) {
|
|
42318
|
+
sayRowMoves();
|
|
42319
|
+
takeRowFromHand(movement);
|
|
42320
|
+
return;
|
|
42321
|
+
}
|
|
42322
|
+
if (row.getAttribute("data-route-row-taken") !== "clip") {
|
|
42323
|
+
travelRowTo(currentIndex, movement);
|
|
42324
|
+
}
|
|
42179
42325
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
42180
42326
|
// scroll event is a frame away.
|
|
42181
42327
|
sayRowMoves();
|
|
@@ -42210,27 +42356,43 @@ const RouteTravel = ({
|
|
|
42210
42356
|
}, [windowed]);
|
|
42211
42357
|
useLayoutEffect(() => {
|
|
42212
42358
|
const row = rowRef.current;
|
|
42213
|
-
const
|
|
42359
|
+
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
42360
|
+
const onScroll = ({
|
|
42361
|
+
finger
|
|
42362
|
+
}) => {
|
|
42214
42363
|
const {
|
|
42215
42364
|
pages,
|
|
42216
42365
|
currentIndex,
|
|
42217
42366
|
travelByDrag
|
|
42218
42367
|
} = latestRef.current;
|
|
42219
42368
|
const panes = panesOf(row);
|
|
42220
|
-
if (panes.length < 2) {
|
|
42369
|
+
if (panes.length < 2 || isClipped()) {
|
|
42221
42370
|
return;
|
|
42222
42371
|
}
|
|
42223
42372
|
const moving = movementRef.current;
|
|
42224
42373
|
const pane = panes[currentIndex];
|
|
42225
|
-
if (!moving && pane && restsOn(row, pane)) {
|
|
42374
|
+
if (!moving && pane && restsOn(row, pane, ROW_READING)) {
|
|
42226
42375
|
// Written by this box, at rest: nobody is moving anything.
|
|
42227
42376
|
return;
|
|
42228
42377
|
}
|
|
42229
|
-
const movement = moving || beginMovement({
|
|
42378
|
+
const movement = moving || beginMovement({
|
|
42379
|
+
byHand: true
|
|
42380
|
+
});
|
|
42230
42381
|
sayRowMoves();
|
|
42231
42382
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
42232
42383
|
return;
|
|
42233
42384
|
}
|
|
42385
|
+
// Nothing will say when a trackpad's fingers leave, and a long stroke would keep
|
|
42386
|
+
// the first tab lit for the whole trip: the nearest page answers. A finger has a
|
|
42387
|
+
// lift to wait for (onLeftToSnap), and until then the page held stands.
|
|
42388
|
+
if (!finger) {
|
|
42389
|
+
const {
|
|
42390
|
+
base,
|
|
42391
|
+
toward,
|
|
42392
|
+
fraction
|
|
42393
|
+
} = readRowFrame(row, panes);
|
|
42394
|
+
followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
|
|
42395
|
+
}
|
|
42234
42396
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
42235
42397
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
42236
42398
|
return;
|
|
@@ -42268,7 +42430,7 @@ const RouteTravel = ({
|
|
|
42268
42430
|
const {
|
|
42269
42431
|
base,
|
|
42270
42432
|
toward
|
|
42271
|
-
} =
|
|
42433
|
+
} = readRowFrame(row, panes);
|
|
42272
42434
|
const index = panes.indexOf(forward && toward ? toward : base);
|
|
42273
42435
|
const page = pages[index];
|
|
42274
42436
|
if (!page) {
|
|
@@ -42283,12 +42445,22 @@ const RouteTravel = ({
|
|
|
42283
42445
|
};
|
|
42284
42446
|
const onLand = () => {
|
|
42285
42447
|
const movement = movementRef.current;
|
|
42286
|
-
if (!movement) {
|
|
42448
|
+
if (!movement || isClipped()) {
|
|
42287
42449
|
return;
|
|
42288
42450
|
}
|
|
42289
42451
|
const panes = panesOf(row);
|
|
42290
|
-
const pane = pageAtRest(row, panes);
|
|
42452
|
+
const pane = pageAtRest(row, panes, ROW_READING);
|
|
42291
42453
|
if (!pane) {
|
|
42454
|
+
// A travel stopped short of its page: the layout moved under it (a scrollbar
|
|
42455
|
+
// coming in narrows the row), and where it was sent is not where the page
|
|
42456
|
+
// stands. Sent again: the row then rests, or does not move and is not heard.
|
|
42457
|
+
const target = panes[latestRef.current.currentIndex];
|
|
42458
|
+
if (movement.travelling && !isClipped() && target) {
|
|
42459
|
+
row.scrollTo({
|
|
42460
|
+
left: target.offsetLeft,
|
|
42461
|
+
behavior: "smooth"
|
|
42462
|
+
});
|
|
42463
|
+
}
|
|
42292
42464
|
return;
|
|
42293
42465
|
}
|
|
42294
42466
|
movement.landed = true;
|
|
@@ -42312,8 +42484,55 @@ const RouteTravel = ({
|
|
|
42312
42484
|
onLand,
|
|
42313
42485
|
onLeftToSnap
|
|
42314
42486
|
});
|
|
42487
|
+
// A finger on a travel still playing: the row is the finger's from here, and it
|
|
42488
|
+
// stands where the travel was going, as in a SlideContainer. A wheel only gives the
|
|
42489
|
+
// row its snapping back: a page scrolled vertically over it is no hand on it.
|
|
42490
|
+
const onTouchOnTravel = () => {
|
|
42491
|
+
if (movementRef.current?.travelling && !isClipped()) {
|
|
42492
|
+
endMovement();
|
|
42493
|
+
}
|
|
42494
|
+
};
|
|
42495
|
+
const onWheelOnTravel = () => {
|
|
42496
|
+
if (row.getAttribute("data-route-row-taken") === "") {
|
|
42497
|
+
row.removeAttribute("data-route-row-taken");
|
|
42498
|
+
}
|
|
42499
|
+
};
|
|
42500
|
+
// A press for the page the address is already on changes nothing the router can
|
|
42501
|
+
// tell, and a stroke no lift ends leaves the address where it was: it is heard here,
|
|
42502
|
+
// the way a tab says which page it is through its address.
|
|
42503
|
+
const onPress = clickEvent => {
|
|
42504
|
+
const movement = movementRef.current;
|
|
42505
|
+
if (!movement?.byHand || movement.rowTaken) {
|
|
42506
|
+
return;
|
|
42507
|
+
}
|
|
42508
|
+
const link = clickEvent.target.closest?.("a[href]");
|
|
42509
|
+
const {
|
|
42510
|
+
pages,
|
|
42511
|
+
currentIndex
|
|
42512
|
+
} = latestRef.current;
|
|
42513
|
+
const page = pages[currentIndex];
|
|
42514
|
+
if (!link || !page || new URL(link.href).pathname !== pathOf(page)) {
|
|
42515
|
+
return;
|
|
42516
|
+
}
|
|
42517
|
+
movement.travelling = true;
|
|
42518
|
+
takeRowFromHand(movement);
|
|
42519
|
+
};
|
|
42520
|
+
row.addEventListener("touchstart", onTouchOnTravel, {
|
|
42521
|
+
passive: true
|
|
42522
|
+
});
|
|
42523
|
+
row.addEventListener("wheel", onWheelOnTravel, {
|
|
42524
|
+
passive: true
|
|
42525
|
+
});
|
|
42526
|
+
document.addEventListener("click", onPress, {
|
|
42527
|
+
capture: true
|
|
42528
|
+
});
|
|
42315
42529
|
return () => {
|
|
42316
42530
|
stopWatching();
|
|
42531
|
+
row.removeEventListener("touchstart", onTouchOnTravel);
|
|
42532
|
+
row.removeEventListener("wheel", onWheelOnTravel);
|
|
42533
|
+
document.removeEventListener("click", onPress, {
|
|
42534
|
+
capture: true
|
|
42535
|
+
});
|
|
42317
42536
|
movementRef.current?.resumeScrollRecording?.();
|
|
42318
42537
|
movementRef.current = null;
|
|
42319
42538
|
publishRouteRowMove(null);
|
|
@@ -42329,7 +42548,7 @@ const RouteTravel = ({
|
|
|
42329
42548
|
return;
|
|
42330
42549
|
}
|
|
42331
42550
|
const pane = panesOf(rowRef.current)[currentIndex];
|
|
42332
|
-
if (pane && restsOn(rowRef.current, pane)) {
|
|
42551
|
+
if (pane && restsOn(rowRef.current, pane, ROW_READING)) {
|
|
42333
42552
|
Promise.resolve().then(endMovement);
|
|
42334
42553
|
}
|
|
42335
42554
|
}, [currentIndex]);
|
|
@@ -42437,6 +42656,18 @@ const copyScrollPositions = (from, to) => {
|
|
|
42437
42656
|
}
|
|
42438
42657
|
}
|
|
42439
42658
|
};
|
|
42659
|
+
const afterFrames = (count, callback) => {
|
|
42660
|
+
if (count === 0) {
|
|
42661
|
+
callback();
|
|
42662
|
+
return;
|
|
42663
|
+
}
|
|
42664
|
+
requestAnimationFrame(() => {
|
|
42665
|
+
afterFrames(count - 1, callback);
|
|
42666
|
+
});
|
|
42667
|
+
};
|
|
42668
|
+
|
|
42669
|
+
// The two pages the row stands between.
|
|
42670
|
+
const readRowFrame = (row, panes) => readRowStretch(row, panes, panes[0], ROW_READING);
|
|
42440
42671
|
|
|
42441
42672
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
42442
42673
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
@@ -58187,8 +58418,9 @@ const wordsOf = (element, attribute) => {
|
|
|
58187
58418
|
return value ? value.split(" ") : NO_WAYS;
|
|
58188
58419
|
};
|
|
58189
58420
|
|
|
58190
|
-
// The slide
|
|
58191
|
-
//
|
|
58421
|
+
// The slide one is on, for whatever tells the reader where they are or what a press
|
|
58422
|
+
// would do: the current one while a hand holds the row, where the row is going once
|
|
58423
|
+
// it has let go. data-slide-current only changes at rest.
|
|
58192
58424
|
const readSlideOnScreen = (element) =>
|
|
58193
58425
|
element.getAttribute(SLIDE_HEADING_ATTRIBUTE) ??
|
|
58194
58426
|
element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ??
|
|
@@ -58238,10 +58470,9 @@ const sameSlideContainerState = (a, b) =>
|
|
|
58238
58470
|
* has let go, because that is what one is looking at. It can come before the
|
|
58239
58471
|
* slide's own signal and `onCurrentChange`, which wait for the row to rest.
|
|
58240
58472
|
* `toward` is the other slide in the frame while the picture is between two,
|
|
58241
|
-
* and nothing at rest. `can` is "asking for it would do something", from
|
|
58242
|
-
* slide
|
|
58243
|
-
*
|
|
58244
|
-
* the reader.
|
|
58473
|
+
* and nothing at rest. `can` is "asking for it would do something", from that
|
|
58474
|
+
* same slide, `held` is "it is there and this slide says no" — a way out is dead
|
|
58475
|
+
* in both cases, and only the second is worth a word to the reader.
|
|
58245
58476
|
* `known` is false until the box has been read: no box was named and none is
|
|
58246
58477
|
* above, the id names nothing, or — for one commit — this mounted before the
|
|
58247
58478
|
* box had painted. Until then `can` answers YES, because it is the answer that
|
|
@@ -69267,6 +69498,10 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
69267
69498
|
overflow-y: auto;
|
|
69268
69499
|
}
|
|
69269
69500
|
|
|
69501
|
+
&[data-slide-row-taken] {
|
|
69502
|
+
overflow: clip;
|
|
69503
|
+
}
|
|
69504
|
+
|
|
69270
69505
|
&:focus {
|
|
69271
69506
|
outline: none;
|
|
69272
69507
|
}
|
|
@@ -69318,6 +69553,77 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
69318
69553
|
}
|
|
69319
69554
|
`;
|
|
69320
69555
|
|
|
69556
|
+
// What a row left moving shows when a travel is asked for over it, read before the
|
|
69557
|
+
// slides are written: the one or two slides in the frame, and how far the track has
|
|
69558
|
+
// to stand, in a box that is no scroller, for that frame not to move.
|
|
69559
|
+
const readRowStand = (containerEl, slideElements) => {
|
|
69560
|
+
const restElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69561
|
+
if (!restElement) {
|
|
69562
|
+
return null;
|
|
69563
|
+
}
|
|
69564
|
+
const {
|
|
69565
|
+
base,
|
|
69566
|
+
toward,
|
|
69567
|
+
fraction
|
|
69568
|
+
} = readRowStretch(containerEl, slideElements, restElement);
|
|
69569
|
+
return {
|
|
69570
|
+
base: readArea(base),
|
|
69571
|
+
toward: toward && fraction > 0 ? readArea(toward) : null,
|
|
69572
|
+
fraction,
|
|
69573
|
+
offset: `${-containerEl.scrollLeft}px ${-containerEl.scrollTop}px`
|
|
69574
|
+
};
|
|
69575
|
+
};
|
|
69576
|
+
// The stage of a travel asked for over a row left moving. What is in the frame stays
|
|
69577
|
+
// where it is, and the slide asked for comes one box past it, unless it is already
|
|
69578
|
+
// there: then what is left to make is a part of a box.
|
|
69579
|
+
const stageOverRow = (rowStand, area, realPlaceOf, stepAsked) => {
|
|
69580
|
+
const {
|
|
69581
|
+
base,
|
|
69582
|
+
toward,
|
|
69583
|
+
fraction
|
|
69584
|
+
} = rowStand;
|
|
69585
|
+
const basePlace = realPlaceOf(base);
|
|
69586
|
+
const placeByArea = new Map([[base, basePlace]]);
|
|
69587
|
+
const towardPlace = toward ? realPlaceOf(toward) : null;
|
|
69588
|
+
if (toward) {
|
|
69589
|
+
placeByArea.set(toward, towardPlace);
|
|
69590
|
+
}
|
|
69591
|
+
if (area === base || area === toward) {
|
|
69592
|
+
const otherArea = area === base ? toward : base;
|
|
69593
|
+
if (!otherArea) {
|
|
69594
|
+
return {
|
|
69595
|
+
placeByArea,
|
|
69596
|
+
leaningArea: null,
|
|
69597
|
+
progress: 0
|
|
69598
|
+
};
|
|
69599
|
+
}
|
|
69600
|
+
const from = realPlaceOf(otherArea);
|
|
69601
|
+
const to = realPlaceOf(area);
|
|
69602
|
+
const sign = Math.sign(to.x - from.x) || Math.sign(to.y - from.y);
|
|
69603
|
+
return {
|
|
69604
|
+
placeByArea,
|
|
69605
|
+
leaningArea: otherArea,
|
|
69606
|
+
progress: sign * (area === base ? fraction : 1 - fraction)
|
|
69607
|
+
};
|
|
69608
|
+
}
|
|
69609
|
+
const to = realPlaceOf(area);
|
|
69610
|
+
const step = stepAsked || {
|
|
69611
|
+
x: Math.sign(to.x - basePlace.x),
|
|
69612
|
+
y: Math.sign(to.y - basePlace.y)
|
|
69613
|
+
};
|
|
69614
|
+
const pastToward = towardPlace && (towardPlace.x - basePlace.x) * step.x + (towardPlace.y - basePlace.y) * step.y > 0;
|
|
69615
|
+
const side = pastToward ? towardPlace : basePlace;
|
|
69616
|
+
placeByArea.set(area, {
|
|
69617
|
+
x: side.x + step.x,
|
|
69618
|
+
y: side.y + step.y
|
|
69619
|
+
});
|
|
69620
|
+
return {
|
|
69621
|
+
placeByArea,
|
|
69622
|
+
leaningArea: toward && fraction >= 0.5 ? toward : base,
|
|
69623
|
+
progress: step.x || step.y
|
|
69624
|
+
};
|
|
69625
|
+
};
|
|
69626
|
+
|
|
69321
69627
|
// How much of a travel is left to make, as a fraction of the travel that was
|
|
69322
69628
|
// asked for: 1 for a move starting from rest, less for one picked up while the
|
|
69323
69629
|
// track was already moving. Everything is measured in px against the track's
|
|
@@ -69334,6 +69640,19 @@ const ratioOfOneTravel = (track, from, to, targetBefore) => {
|
|
|
69334
69640
|
const ratio = left / asked;
|
|
69335
69641
|
return ratio > 1 ? 1 : ratio;
|
|
69336
69642
|
};
|
|
69643
|
+
// The same for a travel taken over a row, which nobody asked for from anywhere in
|
|
69644
|
+
// particular: what is left, in boxes. Laid out sizes, like the scroll position that
|
|
69645
|
+
// `from` is read off: a row inside a popup still growing is not its transformed size.
|
|
69646
|
+
const ratioOfOneBox = (track, from, to) => {
|
|
69647
|
+
const box = {
|
|
69648
|
+
width: track.offsetWidth,
|
|
69649
|
+
height: track.offsetHeight
|
|
69650
|
+
};
|
|
69651
|
+
const start = offsetToPx(from, box);
|
|
69652
|
+
const end = offsetToPx(to, box);
|
|
69653
|
+
const ratio = Math.abs(end.x - start.x) / box.width + Math.abs(end.y - start.y) / box.height;
|
|
69654
|
+
return ratio > 1 ? 1 : ratio;
|
|
69655
|
+
};
|
|
69337
69656
|
|
|
69338
69657
|
// A translate ("-100% 0%", "-260px 0px", "none") as two numbers of pixels.
|
|
69339
69658
|
// Percentages are the size of the box, which is what a translate resolves them
|
|
@@ -69911,6 +70230,10 @@ const SlideContainer = ({
|
|
|
69911
70230
|
// The slide a hand has just landed on, for the render it asks for: it is
|
|
69912
70231
|
// already there, so there is nothing to play.
|
|
69913
70232
|
const handLandedRef = useRef(null);
|
|
70233
|
+
// The slide a row is being carried away from, pressed: it stays current, so only
|
|
70234
|
+
// a render says the row has to come back to it (see goToArea).
|
|
70235
|
+
const rowBackAskedRef = useRef(false);
|
|
70236
|
+
const [, askRowBack] = useState(0);
|
|
69914
70237
|
// Where the box rests on the row, and what the row starts from: a slide that
|
|
69915
70238
|
// holds on to the user ends the row on that side, and a scroll range starts at
|
|
69916
70239
|
// its first reachable slide (see writeRowAtRest).
|
|
@@ -70157,17 +70480,23 @@ const SlideContainer = ({
|
|
|
70157
70480
|
};
|
|
70158
70481
|
|
|
70159
70482
|
// Where the row is going, for as long as the current slide has not changed:
|
|
70160
|
-
// anything saying where one is reads this first
|
|
70161
|
-
// the caller to announce, a landing
|
|
70483
|
+
// anything saying where one is reads this first, and so does what a press would
|
|
70484
|
+
// do from there (see readSlideOnScreen). Left to the caller to announce, a landing
|
|
70485
|
+
// having more to say in the same breath.
|
|
70162
70486
|
const writeHeading = headingElement => {
|
|
70163
70487
|
const containerEl = containerRef.current;
|
|
70164
70488
|
const area = headingElement ? readArea(headingElement) : null;
|
|
70165
70489
|
// A window does not change slide, it rolls: it has nowhere to be heading.
|
|
70166
|
-
|
|
70167
|
-
|
|
70490
|
+
const heading = loop || area === null || area === containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ? null : area;
|
|
70491
|
+
if (heading === containerEl.getAttribute(SLIDE_HEADING_ATTRIBUTE)) {
|
|
70168
70492
|
return;
|
|
70169
70493
|
}
|
|
70170
|
-
|
|
70494
|
+
if (heading === null) {
|
|
70495
|
+
containerEl.removeAttribute(SLIDE_HEADING_ATTRIBUTE);
|
|
70496
|
+
} else {
|
|
70497
|
+
containerEl.setAttribute(SLIDE_HEADING_ATTRIBUTE, heading);
|
|
70498
|
+
}
|
|
70499
|
+
paintWays();
|
|
70171
70500
|
};
|
|
70172
70501
|
const markAnswered = area => {
|
|
70173
70502
|
const order = readMap().slideElements.map(readArea);
|
|
@@ -70334,6 +70663,7 @@ const SlideContainer = ({
|
|
|
70334
70663
|
offsetRef.current = "0% 0%";
|
|
70335
70664
|
track.style.setProperty("--slide-container-offset", "0% 0%");
|
|
70336
70665
|
containerRef.current.removeAttribute("data-slide-travelling");
|
|
70666
|
+
containerRef.current.removeAttribute("data-slide-row-taken");
|
|
70337
70667
|
sendRowToRest(currentElement);
|
|
70338
70668
|
renderNear(currentArea, {
|
|
70339
70669
|
alongside: true
|
|
@@ -70408,10 +70738,26 @@ const SlideContainer = ({
|
|
|
70408
70738
|
// between the picture and the slide arriving, on the axis it walks.
|
|
70409
70739
|
let travelStep = null;
|
|
70410
70740
|
const drawnArea = stage ? stage.area : drawnAreaRef.current;
|
|
70411
|
-
const
|
|
70412
|
-
|
|
70413
|
-
|
|
70741
|
+
const rowBackAsked = rowBackAskedRef.current;
|
|
70742
|
+
rowBackAskedRef.current = false;
|
|
70743
|
+
// Asked for over a row a hand has left moving: the row belongs to what was asked
|
|
70744
|
+
// for, and where the hand's snap would have ended decides nothing.
|
|
70745
|
+
const rowTaken = handMovingRef.current && !handLanded && (drawnArea !== currentArea || rowBackAsked);
|
|
70746
|
+
const rowStand = rowTaken ? readRowStand(containerRef.current, slideElements) : null;
|
|
70747
|
+
if (rowTaken) {
|
|
70748
|
+
handMovingRef.current = false;
|
|
70414
70749
|
writeHeading(null);
|
|
70750
|
+
paintTravelProgress();
|
|
70751
|
+
}
|
|
70752
|
+
const travelStarts = !still && durationMs > 0 && drawnArea !== undefined && (drawnArea !== currentArea || rowTaken) && slideElements.some(slideElement => readArea(slideElement) === drawnArea);
|
|
70753
|
+
let overRow = null;
|
|
70754
|
+
if (travelStarts && rowStand) {
|
|
70755
|
+
overRow = stageOverRow(rowStand, currentArea, realPlaceOf, travelStepRef.current);
|
|
70756
|
+
stage = stageRef.current = {
|
|
70757
|
+
placeByArea: overRow.placeByArea,
|
|
70758
|
+
area: currentArea
|
|
70759
|
+
};
|
|
70760
|
+
} else if (travelStarts) {
|
|
70415
70761
|
// Where the slide being left IS — the place the stage gave it if a travel
|
|
70416
70762
|
// was already playing (a press landing mid-travel departs from where the
|
|
70417
70763
|
// eye is, not from the map), its own place otherwise.
|
|
@@ -70496,9 +70842,16 @@ const SlideContainer = ({
|
|
|
70496
70842
|
}
|
|
70497
70843
|
const track = trackRef.current;
|
|
70498
70844
|
containerRef.current.toggleAttribute("data-slide-travelling", Boolean(stage));
|
|
70499
|
-
|
|
70845
|
+
if (rowTaken) {
|
|
70846
|
+
containerRef.current.setAttribute("data-slide-row-taken", "");
|
|
70847
|
+
}
|
|
70848
|
+
// How far the track stands from where the scroll position rests: none at rest. A
|
|
70849
|
+
// box that is no scroller has no position, and the track says it all.
|
|
70500
70850
|
const rowPlace = onRow(currentPlace);
|
|
70501
|
-
const restPlace =
|
|
70851
|
+
const restPlace = containerRef.current.hasAttribute("data-slide-row-taken") ? {
|
|
70852
|
+
x: 0,
|
|
70853
|
+
y: 0
|
|
70854
|
+
} : restPlaceRef.current;
|
|
70502
70855
|
const offset = `${(restPlace.x - rowPlace.x) * 100}% ${(restPlace.y - rowPlace.y) * 100}%`;
|
|
70503
70856
|
// The travel that was ASKED for is still one box, whatever is left of it to
|
|
70504
70857
|
// cover: what is left takes what is left of the duration.
|
|
@@ -70521,6 +70874,9 @@ const SlideContainer = ({
|
|
|
70521
70874
|
const onScreenPx = trackOffsetPx(track, containerRef.current);
|
|
70522
70875
|
offsetOnScreen = `${onScreenPx.x}px ${onScreenPx.y}px`;
|
|
70523
70876
|
}
|
|
70877
|
+
if (overRow) {
|
|
70878
|
+
offsetOnScreen = rowStand.offset;
|
|
70879
|
+
}
|
|
70524
70880
|
const offsetBefore = offsetOnScreen ?? offsetRef.current;
|
|
70525
70881
|
offsetRef.current = offset;
|
|
70526
70882
|
track.style.setProperty("--slide-container-offset", offset);
|
|
@@ -70539,10 +70895,10 @@ const SlideContainer = ({
|
|
|
70539
70895
|
// short distance for a full duration. One box in `duration` is the pace;
|
|
70540
70896
|
// this only ever shortens it (a longer travel is not made slower, which
|
|
70541
70897
|
// would make a two-box move drag).
|
|
70542
|
-
const travelRatio = ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
70898
|
+
const travelRatio = overRow ? ratioOfOneBox(track, offsetBefore, offset) : ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
70543
70899
|
// Already moving, so there is no ease-in to play: it would stall the track
|
|
70544
70900
|
// for an instant right where the eye is following it.
|
|
70545
|
-
const easing = travelInFlight ? "ease-out" : "ease";
|
|
70901
|
+
const easing = travelInFlight || overRow ? "ease-out" : "ease";
|
|
70546
70902
|
// Cancelled rather than layered: two animations on the same property
|
|
70547
70903
|
// would blend, and what one sees then is neither of the two moves.
|
|
70548
70904
|
trackAnimationRef.current?.cancel();
|
|
@@ -70550,15 +70906,21 @@ const SlideContainer = ({
|
|
|
70550
70906
|
translate: offsetBefore
|
|
70551
70907
|
}, {
|
|
70552
70908
|
translate: offset
|
|
70553
|
-
}],
|
|
70909
|
+
}],
|
|
70910
|
+
// Backwards: the first frame's clock can read a fraction of a millisecond
|
|
70911
|
+
// before the start time Chrome gives the animation, which would show the
|
|
70912
|
+
// travel already over.
|
|
70913
|
+
{
|
|
70554
70914
|
duration: durationMs * travelRatio,
|
|
70555
|
-
easing
|
|
70915
|
+
easing,
|
|
70916
|
+
fill: "backwards"
|
|
70556
70917
|
});
|
|
70557
70918
|
// The trait travels with the slides, from a whole box away.
|
|
70558
|
-
const
|
|
70919
|
+
const stepProgress = travelStep ? travelStep.x || travelStep.y : 0;
|
|
70920
|
+
const progressFrom = overRow ? overRow.progress : stepProgress;
|
|
70559
70921
|
// The slide the picture leans on for the length of it is the one being
|
|
70560
70922
|
// LEFT: it is the second one in the frame until the travel is over.
|
|
70561
|
-
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, drawnArea);
|
|
70923
|
+
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, overRow ? overRow.leaningArea : drawnArea);
|
|
70562
70924
|
// Presses still waiting behind this one: it is already late, so it is
|
|
70563
70925
|
// sent home at once rather than played out at the pace of someone who
|
|
70564
70926
|
// has stopped pressing. Someone pressing → four times is asking to be
|
|
@@ -70579,6 +70941,17 @@ const SlideContainer = ({
|
|
|
70579
70941
|
// thing it was standing for is over.
|
|
70580
70942
|
settleTravel();
|
|
70581
70943
|
}
|
|
70944
|
+
// The row taken with nothing playing to give it back (no duration, a page change
|
|
70945
|
+
// carrying the slide): no scroller for as long as the compositor takes to hear it.
|
|
70946
|
+
if (!stageRef.current && containerRef.current.hasAttribute("data-slide-row-taken")) {
|
|
70947
|
+
requestAnimationFrame(() => {
|
|
70948
|
+
requestAnimationFrame(() => {
|
|
70949
|
+
if (trackAnimationRef.current?.playState !== "running") {
|
|
70950
|
+
settleTravel();
|
|
70951
|
+
}
|
|
70952
|
+
});
|
|
70953
|
+
});
|
|
70954
|
+
}
|
|
70582
70955
|
// A window waiting for its travel to be over (see goToArea's own loop
|
|
70583
70956
|
// branch): the animation says when, and says it about the move that just
|
|
70584
70957
|
// started rather than about a duration counted out beside it.
|
|
@@ -70642,9 +71015,9 @@ const SlideContainer = ({
|
|
|
70642
71015
|
sendRowToRest(currentElement);
|
|
70643
71016
|
}
|
|
70644
71017
|
// Said last, and after data-current has been written: what a travel would do
|
|
70645
|
-
// is read off the map from the slide
|
|
70646
|
-
//
|
|
70647
|
-
paintWays(
|
|
71018
|
+
// is read off the map from the slide on screen, and off the locks that slide is
|
|
71019
|
+
// wearing in this very commit.
|
|
71020
|
+
paintWays();
|
|
70648
71021
|
// …and once everything this commit had to write is written, the box says so
|
|
70649
71022
|
// — a single announcement for a render that may have changed several of the
|
|
70650
71023
|
// facts at once.
|
|
@@ -70691,14 +71064,18 @@ const SlideContainer = ({
|
|
|
70691
71064
|
placeOf
|
|
70692
71065
|
} = readMap();
|
|
70693
71066
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
70694
|
-
if (!area
|
|
71067
|
+
if (!area) {
|
|
70695
71068
|
return false;
|
|
70696
71069
|
}
|
|
71070
|
+
// What a press leaves is the slide on screen, which is what paintWays offers from.
|
|
71071
|
+
// A landing is the row arriving there, and what it leaves is the current one.
|
|
71072
|
+
const areaLeft = landed ? readArea(currentElement) : readSlideOnScreen(containerRef.current) ?? readArea(currentElement);
|
|
71073
|
+
const elementLeft = slideElements.find(slideElement => readArea(slideElement) === areaLeft) || currentElement;
|
|
70697
71074
|
if (forward === undefined) {
|
|
70698
71075
|
// Asked for by name (--navi-go-to-slide, --navi-back) rather than by a
|
|
70699
71076
|
// direction: which way it is is still a fact, read off the map, and it is
|
|
70700
71077
|
// what says which of the two locks below applies.
|
|
70701
|
-
const from = placeOf.get(
|
|
71078
|
+
const from = placeOf.get(areaLeft) || {
|
|
70702
71079
|
x: 0,
|
|
70703
71080
|
y: 0
|
|
70704
71081
|
};
|
|
@@ -70716,9 +71093,18 @@ const SlideContainer = ({
|
|
|
70716
71093
|
// one departure the forward hold does not apply to. Said here rather than
|
|
70717
71094
|
// by dropping the attribute, which stays the render's to write — a hold
|
|
70718
71095
|
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
70719
|
-
if (forward ? !released &&
|
|
71096
|
+
if (area !== areaLeft && (forward ? !released && elementLeft.hasAttribute("data-prevent-nav-next") : elementLeft.hasAttribute("data-prevent-nav-previous"))) {
|
|
70720
71097
|
return false;
|
|
70721
71098
|
}
|
|
71099
|
+
if (area === readArea(currentElement)) {
|
|
71100
|
+
// Nowhere to go, unless a hand has left the row on its way off this slide.
|
|
71101
|
+
if (!handMovingRef.current) {
|
|
71102
|
+
return false;
|
|
71103
|
+
}
|
|
71104
|
+
rowBackAskedRef.current = true;
|
|
71105
|
+
askRowBack(count => count + 1);
|
|
71106
|
+
return true;
|
|
71107
|
+
}
|
|
70722
71108
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
70723
71109
|
// who pressed what, and whether they were on the keyboard, is knowable now
|
|
70724
71110
|
// and gone a render later. The slide arriving is made reachable on the spot
|
|
@@ -71100,7 +71486,7 @@ const SlideContainer = ({
|
|
|
71100
71486
|
const move = (dx, dy, event, {
|
|
71101
71487
|
value,
|
|
71102
71488
|
released
|
|
71103
|
-
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
71489
|
+
} = {}) => goToArea(areaTowards(dx, dy, readSlideOnScreen(containerRef.current)), {
|
|
71104
71490
|
forward: dx > 0 || dy > 0,
|
|
71105
71491
|
event,
|
|
71106
71492
|
value,
|
|
@@ -71181,10 +71567,10 @@ const SlideContainer = ({
|
|
|
71181
71567
|
const paintNativeRow = ({
|
|
71182
71568
|
finger
|
|
71183
71569
|
}) => {
|
|
71184
|
-
|
|
71570
|
+
const containerEl = containerRef.current;
|
|
71571
|
+
if (trackAnimationRef.current?.playState === "running" || containerEl.hasAttribute("data-slide-row-taken")) {
|
|
71185
71572
|
return;
|
|
71186
71573
|
}
|
|
71187
|
-
const containerEl = containerRef.current;
|
|
71188
71574
|
const slideElements = Array.from(trackRef.current.children);
|
|
71189
71575
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
71190
71576
|
if (!currentElement) {
|
|
@@ -71402,13 +71788,19 @@ const SlideContainer = ({
|
|
|
71402
71788
|
// ([data-slide-ways~="right"]), or through useSlideContainer, which watches
|
|
71403
71789
|
// them. Nothing is told, everything is read: it is what lets them sit
|
|
71404
71790
|
// anywhere, and what lets a container nobody drives still be followed.
|
|
71405
|
-
const paintWays =
|
|
71791
|
+
const paintWays = () => {
|
|
71406
71792
|
const ways = [];
|
|
71407
71793
|
const held = [];
|
|
71794
|
+
const {
|
|
71795
|
+
slideElements,
|
|
71796
|
+
order
|
|
71797
|
+
} = readMap();
|
|
71798
|
+
const areaOnScreen = readSlideOnScreen(containerRef.current);
|
|
71799
|
+
const elementOnScreen = slideElements.find(slideElement => readArea(slideElement) === areaOnScreen);
|
|
71408
71800
|
// Whether the slide being left says no, this way. The one gate again, read
|
|
71409
71801
|
// exactly as goToArea reads it at the moment of the travel — so what is
|
|
71410
71802
|
// published here and what would happen cannot disagree.
|
|
71411
|
-
const holdsTowards = forward =>
|
|
71803
|
+
const holdsTowards = forward => elementOnScreen?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
71412
71804
|
const say = (name, offered, forward) => {
|
|
71413
71805
|
if (!offered) {
|
|
71414
71806
|
return;
|
|
@@ -71425,18 +71817,14 @@ const SlideContainer = ({
|
|
|
71425
71817
|
// would be saying it about every row on the page.
|
|
71426
71818
|
continue;
|
|
71427
71819
|
}
|
|
71428
|
-
say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
|
|
71820
|
+
say(direction, Boolean(areaTowards(dx, dy, areaOnScreen)), dx > 0 || dy > 0);
|
|
71429
71821
|
}
|
|
71430
71822
|
// The two ends are ways out like the others — "all the way that way", said
|
|
71431
71823
|
// by the map's own order rather than by a direction (see goToEnd, and
|
|
71432
71824
|
// SlideContainer.First / .Last). Offered while one is not already standing
|
|
71433
71825
|
// there, and held by the same lock the direction they lie in would be.
|
|
71434
|
-
|
|
71435
|
-
|
|
71436
|
-
} = readMap();
|
|
71437
|
-
const currentArea = currentElement ? readArea(currentElement) : undefined;
|
|
71438
|
-
say("first", order.length > 0 && order[0] !== currentArea, false);
|
|
71439
|
-
say("last", order.length > 0 && order[order.length - 1] !== currentArea, true);
|
|
71826
|
+
say("first", order.length > 0 && order[0] !== areaOnScreen, false);
|
|
71827
|
+
say("last", order.length > 0 && order[order.length - 1] !== areaOnScreen, true);
|
|
71440
71828
|
for (const element of travelPainters()) {
|
|
71441
71829
|
if (ways.length) {
|
|
71442
71830
|
element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
|
|
@@ -71529,7 +71917,8 @@ const SlideContainer = ({
|
|
|
71529
71917
|
"--slide-travel-progress": 0
|
|
71530
71918
|
}], {
|
|
71531
71919
|
duration: durationMs,
|
|
71532
|
-
easing
|
|
71920
|
+
easing,
|
|
71921
|
+
fill: "backwards"
|
|
71533
71922
|
}));
|
|
71534
71923
|
progressAnimationsRef.current[0].finished.then(() => {
|
|
71535
71924
|
progressAnimationsRef.current = [];
|
|
@@ -78491,7 +78880,10 @@ const KEYBOARD_RAISING_CONTROL_SELECTOR = `
|
|
|
78491
78880
|
[contenteditable]:not([contenteditable="false"])
|
|
78492
78881
|
`;
|
|
78493
78882
|
const warnBottomSheetHoldingKeyboardControl = dialogEl => {
|
|
78494
|
-
|
|
78883
|
+
// A popup opens inside its opener's subtree, so the sheet's subtree also
|
|
78884
|
+
// holds the fields of popups opened from it. Those are typed into in that
|
|
78885
|
+
// popup, and a nested Dialog docked to the bottom runs this check itself.
|
|
78886
|
+
const controlEl = Array.from(dialogEl.querySelectorAll(KEYBOARD_RAISING_CONTROL_SELECTOR)).find(el => el.closest(`[navi-control="dialog"], [navi-control="popover"]`) === dialogEl);
|
|
78495
78887
|
if (!controlEl) {
|
|
78496
78888
|
return;
|
|
78497
78889
|
}
|
|
@@ -107995,7 +108387,9 @@ const StepList = ({
|
|
|
107995
108387
|
const read = () => {
|
|
107996
108388
|
const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
107997
108389
|
const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
|
|
107998
|
-
|
|
108390
|
+
// Which step is lit follows the screen. The halo counts from the current slide,
|
|
108391
|
+
// which is what the progress it draws with is measured against.
|
|
108392
|
+
setContainerCurrent(readSlideOnScreen(containerElement));
|
|
107999
108393
|
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
108000
108394
|
if (currentIdx === -1) {
|
|
108001
108395
|
// A slide no step names (a confirmation screen): the halo is not
|