@jsenv/navi 0.30.5 → 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 +357 -65
- package/dist/dev/jsenv_navi.js.map +8 -7
- package/dist/jsenv_navi.js +357 -65
- package/dist/jsenv_navi.js.map +8 -7
- package/docs/drag_to_travel.md +12 -0
- package/docs/mobile_touch.md +64 -5
- package/docs/navigation.md +4 -3
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -24953,7 +24953,7 @@ defineInteractionDetector({
|
|
|
24953
24953
|
const transitionAfter = peekLastViewTransition();
|
|
24954
24954
|
const drawn =
|
|
24955
24955
|
transitionAfter === transitionBefore
|
|
24956
|
-
? afterFrames(2)
|
|
24956
|
+
? afterFrames$1(2)
|
|
24957
24957
|
: transitionAfter.finished.then(NOOP, NOOP);
|
|
24958
24958
|
const write = {
|
|
24959
24959
|
settled: false,
|
|
@@ -25212,7 +25212,7 @@ defineInteractionDetector({
|
|
|
25212
25212
|
|
|
25213
25213
|
const NOOP = () => {};
|
|
25214
25214
|
|
|
25215
|
-
const afterFrames = (count) =>
|
|
25215
|
+
const afterFrames$1 = (count) =>
|
|
25216
25216
|
new Promise((resolve) => {
|
|
25217
25217
|
const next = (remaining) => {
|
|
25218
25218
|
if (remaining === 0) {
|
|
@@ -40302,8 +40302,9 @@ const publishRouteRowMove = (move) => {
|
|
|
40302
40302
|
* itself; a counter, a title or a breadcrumb around the box reads it first.
|
|
40303
40303
|
*
|
|
40304
40304
|
* It goes back to `null` as soon as the hand has let go and the row moves. Nothing
|
|
40305
|
-
* says when the fingers leave a trackpad: there it
|
|
40306
|
-
* rest. A travel no hand set off holds nothing: it goes where it
|
|
40305
|
+
* says when the fingers leave a trackpad: there it is the page nearest the row, until
|
|
40306
|
+
* the row has come to rest. A travel no hand set off holds nothing: it goes where it
|
|
40307
|
+
* goes.
|
|
40307
40308
|
*
|
|
40308
40309
|
* One for the document. `route` says which row, for a page holding several boxes.
|
|
40309
40310
|
*/
|
|
@@ -40321,6 +40322,13 @@ const holdPage = (page, headingPage) => {
|
|
|
40321
40322
|
}
|
|
40322
40323
|
};
|
|
40323
40324
|
|
|
40325
|
+
// A hand no browser says has let go: the page nearest the row is where the reader is,
|
|
40326
|
+
// and the address, wherever it stands, is not.
|
|
40327
|
+
const followPageOnScreen = (page, addressPage) => {
|
|
40328
|
+
headingPageSignal.value = addressPage;
|
|
40329
|
+
routeTravelSettledPageSignal.value = page;
|
|
40330
|
+
};
|
|
40331
|
+
|
|
40324
40332
|
const releaseHeldPage = () => {
|
|
40325
40333
|
routeTravelSettledPageSignal.value = null;
|
|
40326
40334
|
headingPageSignal.value = null;
|
|
@@ -40482,12 +40490,6 @@ const watchRowScroll = (
|
|
|
40482
40490
|
liftedAt = null;
|
|
40483
40491
|
onLeftToSnap?.(moved > 0);
|
|
40484
40492
|
};
|
|
40485
|
-
const onTouchStart = () => {
|
|
40486
|
-
touching = true;
|
|
40487
|
-
touchedSinceStop = true;
|
|
40488
|
-
liftedAt = null;
|
|
40489
|
-
clearTimeout(stoppedTimeout);
|
|
40490
|
-
};
|
|
40491
40493
|
const onTouchEnd = (touchEvent) => {
|
|
40492
40494
|
touching = touchEvent.touches.length > 0;
|
|
40493
40495
|
if (!touching) {
|
|
@@ -40495,18 +40497,31 @@ const watchRowScroll = (
|
|
|
40495
40497
|
armSilence();
|
|
40496
40498
|
}
|
|
40497
40499
|
};
|
|
40500
|
+
const onTouchStart = (touchEvent) => {
|
|
40501
|
+
touching = true;
|
|
40502
|
+
touchedSinceStop = true;
|
|
40503
|
+
liftedAt = null;
|
|
40504
|
+
clearTimeout(stoppedTimeout);
|
|
40505
|
+
// Listened for on what the finger landed on, where the end of a touch is always
|
|
40506
|
+
// told: a page changing under the finger may have taken it out of the document by
|
|
40507
|
+
// then, and nothing comes up to the row from there.
|
|
40508
|
+
const { target } = touchEvent;
|
|
40509
|
+
const onEnd = (endEvent) => {
|
|
40510
|
+
target.removeEventListener("touchend", onEnd);
|
|
40511
|
+
target.removeEventListener("touchcancel", onEnd);
|
|
40512
|
+
onTouchEnd(endEvent);
|
|
40513
|
+
};
|
|
40514
|
+
target.addEventListener("touchend", onEnd, { passive: true });
|
|
40515
|
+
target.addEventListener("touchcancel", onEnd, { passive: true });
|
|
40516
|
+
};
|
|
40498
40517
|
scroller.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
40499
40518
|
scroller.addEventListener("scrollend", onLand);
|
|
40500
40519
|
scroller.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
40501
|
-
scroller.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
40502
|
-
scroller.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
40503
40520
|
return () => {
|
|
40504
40521
|
clearTimeout(stoppedTimeout);
|
|
40505
40522
|
scroller.removeEventListener("scroll", onScrollEvent);
|
|
40506
40523
|
scroller.removeEventListener("scrollend", onLand);
|
|
40507
40524
|
scroller.removeEventListener("touchstart", onTouchStart);
|
|
40508
|
-
scroller.removeEventListener("touchend", onTouchEnd);
|
|
40509
|
-
scroller.removeEventListener("touchcancel", onTouchEnd);
|
|
40510
40525
|
};
|
|
40511
40526
|
};
|
|
40512
40527
|
|
|
@@ -40544,6 +40559,19 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
40544
40559
|
overflow: visible;
|
|
40545
40560
|
}
|
|
40546
40561
|
|
|
40562
|
+
&[data-route-row-taken] {
|
|
40563
|
+
scroll-snap-type: none;
|
|
40564
|
+
}
|
|
40565
|
+
|
|
40566
|
+
&[data-route-row-taken="clip"] {
|
|
40567
|
+
overflow: clip;
|
|
40568
|
+
|
|
40569
|
+
& > [data-route-row-pane], & > [data-route-row-live], & > [data-route-row-pictures] > * {
|
|
40570
|
+
left: var(--navi-route-row-stand);
|
|
40571
|
+
position: relative;
|
|
40572
|
+
}
|
|
40573
|
+
}
|
|
40574
|
+
|
|
40547
40575
|
& > [data-route-row-pane] {
|
|
40548
40576
|
scroll-snap-align: start;
|
|
40549
40577
|
scroll-snap-stop: always;
|
|
@@ -40801,6 +40829,37 @@ const RouteTravel = ({
|
|
|
40801
40829
|
});
|
|
40802
40830
|
};
|
|
40803
40831
|
|
|
40832
|
+
// A tab pressed over a row a hand has left moving, which the browser plays on and
|
|
40833
|
+
// overwrites a travel with once that travel is over: the snap is ended first. Given
|
|
40834
|
+
// back, the row stands where it stood. A frame later, because until then WebKit still
|
|
40835
|
+
// starts from where a box that is no scroller stood: a smooth scroll to the first page
|
|
40836
|
+
// sees nothing to do.
|
|
40837
|
+
const takeRowFromHand = movement => {
|
|
40838
|
+
const row = rowRef.current;
|
|
40839
|
+
const position = row.scrollLeft;
|
|
40840
|
+
movement.rowTaken = true;
|
|
40841
|
+
// A travel no hand set off holds nothing: it goes where it goes.
|
|
40842
|
+
releaseHeldPage();
|
|
40843
|
+
row.style.setProperty("--navi-route-row-stand", `${-position}px`);
|
|
40844
|
+
row.setAttribute("data-route-row-taken", "clip");
|
|
40845
|
+
afterFrames(2, () => {
|
|
40846
|
+
if (movementRef.current !== movement) {
|
|
40847
|
+
return;
|
|
40848
|
+
}
|
|
40849
|
+
row.setAttribute("data-route-row-taken", "");
|
|
40850
|
+
row.style.removeProperty("--navi-route-row-stand");
|
|
40851
|
+
afterFrames(1, () => {
|
|
40852
|
+
const pane = panesOf(row)[latestRef.current.currentIndex];
|
|
40853
|
+
if (movementRef.current === movement && pane) {
|
|
40854
|
+
row.scrollTo({
|
|
40855
|
+
left: pane.offsetLeft,
|
|
40856
|
+
behavior: "smooth"
|
|
40857
|
+
});
|
|
40858
|
+
}
|
|
40859
|
+
});
|
|
40860
|
+
});
|
|
40861
|
+
};
|
|
40862
|
+
|
|
40804
40863
|
// The row has stopped, on the page of the address: everything held for the
|
|
40805
40864
|
// movement is let go of in one go, before anything paints.
|
|
40806
40865
|
const endMovement = () => {
|
|
@@ -40811,6 +40870,7 @@ const RouteTravel = ({
|
|
|
40811
40870
|
}
|
|
40812
40871
|
movementRef.current = null;
|
|
40813
40872
|
row.removeAttribute("data-moving");
|
|
40873
|
+
row.removeAttribute("data-route-row-taken");
|
|
40814
40874
|
publishRouteRowMove(null);
|
|
40815
40875
|
releaseHeldPage();
|
|
40816
40876
|
if (liveRef.current) {
|
|
@@ -40878,16 +40938,23 @@ const RouteTravel = ({
|
|
|
40878
40938
|
}
|
|
40879
40939
|
return;
|
|
40880
40940
|
}
|
|
40881
|
-
|
|
40941
|
+
const movementBefore = movementRef.current;
|
|
40942
|
+
const movement = beginMovement({
|
|
40882
40943
|
travelling: true
|
|
40883
40944
|
});
|
|
40884
|
-
row.scrollTo({
|
|
40885
|
-
left: pane.offsetLeft,
|
|
40886
|
-
behavior: "smooth"
|
|
40887
|
-
});
|
|
40888
40945
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
40889
40946
|
// scroll event is a frame away.
|
|
40890
40947
|
sayRowMoves();
|
|
40948
|
+
if (movementBefore?.byHand && !movementBefore.rowTaken) {
|
|
40949
|
+
takeRowFromHand(movement);
|
|
40950
|
+
return;
|
|
40951
|
+
}
|
|
40952
|
+
if (row.getAttribute("data-route-row-taken") !== "clip") {
|
|
40953
|
+
row.scrollTo({
|
|
40954
|
+
left: pane.offsetLeft,
|
|
40955
|
+
behavior: "smooth"
|
|
40956
|
+
});
|
|
40957
|
+
}
|
|
40891
40958
|
});
|
|
40892
40959
|
|
|
40893
40960
|
// Somebody else is about to move the address to another page of a window: its
|
|
@@ -40919,14 +40986,17 @@ const RouteTravel = ({
|
|
|
40919
40986
|
}, [windowed]);
|
|
40920
40987
|
useLayoutEffect(() => {
|
|
40921
40988
|
const row = rowRef.current;
|
|
40922
|
-
const
|
|
40989
|
+
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
40990
|
+
const onScroll = ({
|
|
40991
|
+
finger
|
|
40992
|
+
}) => {
|
|
40923
40993
|
const {
|
|
40924
40994
|
pages,
|
|
40925
40995
|
currentIndex,
|
|
40926
40996
|
travelByDrag
|
|
40927
40997
|
} = latestRef.current;
|
|
40928
40998
|
const panes = panesOf(row);
|
|
40929
|
-
if (panes.length < 2) {
|
|
40999
|
+
if (panes.length < 2 || isClipped()) {
|
|
40930
41000
|
return;
|
|
40931
41001
|
}
|
|
40932
41002
|
const moving = movementRef.current;
|
|
@@ -40935,11 +41005,24 @@ const RouteTravel = ({
|
|
|
40935
41005
|
// Written by this box, at rest: nobody is moving anything.
|
|
40936
41006
|
return;
|
|
40937
41007
|
}
|
|
40938
|
-
const movement = moving || beginMovement({
|
|
41008
|
+
const movement = moving || beginMovement({
|
|
41009
|
+
byHand: true
|
|
41010
|
+
});
|
|
40939
41011
|
sayRowMoves();
|
|
40940
41012
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
40941
41013
|
return;
|
|
40942
41014
|
}
|
|
41015
|
+
// Nothing will say when a trackpad's fingers leave, and a long stroke would keep
|
|
41016
|
+
// the first tab lit for the whole trip: the nearest page answers. A finger has a
|
|
41017
|
+
// lift to wait for (onLeftToSnap), and until then the page held stands.
|
|
41018
|
+
if (!finger) {
|
|
41019
|
+
const {
|
|
41020
|
+
base,
|
|
41021
|
+
toward,
|
|
41022
|
+
fraction
|
|
41023
|
+
} = readRowStretch(row, panes, panes[0]);
|
|
41024
|
+
followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
|
|
41025
|
+
}
|
|
40943
41026
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
40944
41027
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
40945
41028
|
return;
|
|
@@ -40992,7 +41075,7 @@ const RouteTravel = ({
|
|
|
40992
41075
|
};
|
|
40993
41076
|
const onLand = () => {
|
|
40994
41077
|
const movement = movementRef.current;
|
|
40995
|
-
if (!movement) {
|
|
41078
|
+
if (!movement || isClipped()) {
|
|
40996
41079
|
return;
|
|
40997
41080
|
}
|
|
40998
41081
|
const panes = panesOf(row);
|
|
@@ -41021,8 +41104,48 @@ const RouteTravel = ({
|
|
|
41021
41104
|
onLand,
|
|
41022
41105
|
onLeftToSnap
|
|
41023
41106
|
});
|
|
41107
|
+
// A hand back on a row taken from one: it is the hand's again, snapping included.
|
|
41108
|
+
const onHandBack = () => {
|
|
41109
|
+
if (row.getAttribute("data-route-row-taken") === "") {
|
|
41110
|
+
row.removeAttribute("data-route-row-taken");
|
|
41111
|
+
}
|
|
41112
|
+
};
|
|
41113
|
+
// A press for the page the address is already on changes nothing the router can
|
|
41114
|
+
// tell, and a stroke no lift ends leaves the address where it was: it is heard here,
|
|
41115
|
+
// the way a tab says which page it is through its address.
|
|
41116
|
+
const onPress = clickEvent => {
|
|
41117
|
+
const movement = movementRef.current;
|
|
41118
|
+
if (!movement?.byHand || movement.rowTaken) {
|
|
41119
|
+
return;
|
|
41120
|
+
}
|
|
41121
|
+
const link = clickEvent.target.closest?.("a[href]");
|
|
41122
|
+
const {
|
|
41123
|
+
pages,
|
|
41124
|
+
currentIndex
|
|
41125
|
+
} = latestRef.current;
|
|
41126
|
+
const page = pages[currentIndex];
|
|
41127
|
+
if (!link || !page || new URL(link.href).pathname !== pathOf(page)) {
|
|
41128
|
+
return;
|
|
41129
|
+
}
|
|
41130
|
+
movement.travelling = true;
|
|
41131
|
+
takeRowFromHand(movement);
|
|
41132
|
+
};
|
|
41133
|
+
row.addEventListener("touchstart", onHandBack, {
|
|
41134
|
+
passive: true
|
|
41135
|
+
});
|
|
41136
|
+
row.addEventListener("wheel", onHandBack, {
|
|
41137
|
+
passive: true
|
|
41138
|
+
});
|
|
41139
|
+
document.addEventListener("click", onPress, {
|
|
41140
|
+
capture: true
|
|
41141
|
+
});
|
|
41024
41142
|
return () => {
|
|
41025
41143
|
stopWatching();
|
|
41144
|
+
row.removeEventListener("touchstart", onHandBack);
|
|
41145
|
+
row.removeEventListener("wheel", onHandBack);
|
|
41146
|
+
document.removeEventListener("click", onPress, {
|
|
41147
|
+
capture: true
|
|
41148
|
+
});
|
|
41026
41149
|
movementRef.current?.resumeScrollRecording?.();
|
|
41027
41150
|
movementRef.current = null;
|
|
41028
41151
|
publishRouteRowMove(null);
|
|
@@ -41150,6 +41273,15 @@ const copyScrollPositions = (from, to) => {
|
|
|
41150
41273
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
41151
41274
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
41152
41275
|
// routes of their own — which is the same page with nothing to tell apart.
|
|
41276
|
+
const afterFrames = (count, callback) => {
|
|
41277
|
+
if (count === 0) {
|
|
41278
|
+
callback();
|
|
41279
|
+
return;
|
|
41280
|
+
}
|
|
41281
|
+
requestAnimationFrame(() => {
|
|
41282
|
+
afterFrames(count - 1, callback);
|
|
41283
|
+
});
|
|
41284
|
+
};
|
|
41153
41285
|
const normalizePage = page => page.isRoute ? {
|
|
41154
41286
|
route: page,
|
|
41155
41287
|
params: undefined
|
|
@@ -56641,8 +56773,9 @@ const wordsOf = (element, attribute) => {
|
|
|
56641
56773
|
return value ? value.split(" ") : NO_WAYS;
|
|
56642
56774
|
};
|
|
56643
56775
|
|
|
56644
|
-
// The slide
|
|
56645
|
-
//
|
|
56776
|
+
// The slide one is on, for whatever tells the reader where they are or what a press
|
|
56777
|
+
// would do: the current one while a hand holds the row, where the row is going once
|
|
56778
|
+
// it has let go. data-slide-current only changes at rest.
|
|
56646
56779
|
const readSlideOnScreen = (element) =>
|
|
56647
56780
|
element.getAttribute(SLIDE_HEADING_ATTRIBUTE) ??
|
|
56648
56781
|
element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ??
|
|
@@ -56692,10 +56825,9 @@ const sameSlideContainerState = (a, b) =>
|
|
|
56692
56825
|
* has let go, because that is what one is looking at. It can come before the
|
|
56693
56826
|
* slide's own signal and `onCurrentChange`, which wait for the row to rest.
|
|
56694
56827
|
* `toward` is the other slide in the frame while the picture is between two,
|
|
56695
|
-
* and nothing at rest. `can` is "asking for it would do something", from
|
|
56696
|
-
* slide
|
|
56697
|
-
*
|
|
56698
|
-
* the reader.
|
|
56828
|
+
* and nothing at rest. `can` is "asking for it would do something", from that
|
|
56829
|
+
* same slide, `held` is "it is there and this slide says no" — a way out is dead
|
|
56830
|
+
* in both cases, and only the second is worth a word to the reader.
|
|
56699
56831
|
* `known` is false until the box has been read: no box was named and none is
|
|
56700
56832
|
* above, the id names nothing, or — for one commit — this mounted before the
|
|
56701
56833
|
* box had painted. Until then `can` answers YES, because it is the answer that
|
|
@@ -67667,6 +67799,10 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
67667
67799
|
overflow-y: auto;
|
|
67668
67800
|
}
|
|
67669
67801
|
|
|
67802
|
+
&[data-slide-row-taken] {
|
|
67803
|
+
overflow: clip;
|
|
67804
|
+
}
|
|
67805
|
+
|
|
67670
67806
|
&:focus {
|
|
67671
67807
|
outline: none;
|
|
67672
67808
|
}
|
|
@@ -67718,6 +67854,77 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
67718
67854
|
}
|
|
67719
67855
|
`;
|
|
67720
67856
|
|
|
67857
|
+
// What a row left moving shows when a travel is asked for over it, read before the
|
|
67858
|
+
// slides are written: the one or two slides in the frame, and how far the track has
|
|
67859
|
+
// to stand, in a box that is no scroller, for that frame not to move.
|
|
67860
|
+
const readRowStand = (containerEl, slideElements) => {
|
|
67861
|
+
const restElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
67862
|
+
if (!restElement) {
|
|
67863
|
+
return null;
|
|
67864
|
+
}
|
|
67865
|
+
const {
|
|
67866
|
+
base,
|
|
67867
|
+
toward,
|
|
67868
|
+
fraction
|
|
67869
|
+
} = readRowStretch(containerEl, slideElements, restElement);
|
|
67870
|
+
return {
|
|
67871
|
+
base: readArea(base),
|
|
67872
|
+
toward: toward && fraction > 0 ? readArea(toward) : null,
|
|
67873
|
+
fraction,
|
|
67874
|
+
offset: `${-containerEl.scrollLeft}px ${-containerEl.scrollTop}px`
|
|
67875
|
+
};
|
|
67876
|
+
};
|
|
67877
|
+
// The stage of a travel asked for over a row left moving. What is in the frame stays
|
|
67878
|
+
// where it is, and the slide asked for comes one box past it, unless it is already
|
|
67879
|
+
// there: then what is left to make is a part of a box.
|
|
67880
|
+
const stageOverRow = (rowStand, area, realPlaceOf, stepAsked) => {
|
|
67881
|
+
const {
|
|
67882
|
+
base,
|
|
67883
|
+
toward,
|
|
67884
|
+
fraction
|
|
67885
|
+
} = rowStand;
|
|
67886
|
+
const basePlace = realPlaceOf(base);
|
|
67887
|
+
const placeByArea = new Map([[base, basePlace]]);
|
|
67888
|
+
const towardPlace = toward ? realPlaceOf(toward) : null;
|
|
67889
|
+
if (toward) {
|
|
67890
|
+
placeByArea.set(toward, towardPlace);
|
|
67891
|
+
}
|
|
67892
|
+
if (area === base || area === toward) {
|
|
67893
|
+
const otherArea = area === base ? toward : base;
|
|
67894
|
+
if (!otherArea) {
|
|
67895
|
+
return {
|
|
67896
|
+
placeByArea,
|
|
67897
|
+
leaningArea: null,
|
|
67898
|
+
progress: 0
|
|
67899
|
+
};
|
|
67900
|
+
}
|
|
67901
|
+
const from = realPlaceOf(otherArea);
|
|
67902
|
+
const to = realPlaceOf(area);
|
|
67903
|
+
const sign = Math.sign(to.x - from.x) || Math.sign(to.y - from.y);
|
|
67904
|
+
return {
|
|
67905
|
+
placeByArea,
|
|
67906
|
+
leaningArea: otherArea,
|
|
67907
|
+
progress: sign * (area === base ? fraction : 1 - fraction)
|
|
67908
|
+
};
|
|
67909
|
+
}
|
|
67910
|
+
const to = realPlaceOf(area);
|
|
67911
|
+
const step = stepAsked || {
|
|
67912
|
+
x: Math.sign(to.x - basePlace.x),
|
|
67913
|
+
y: Math.sign(to.y - basePlace.y)
|
|
67914
|
+
};
|
|
67915
|
+
const pastToward = towardPlace && (towardPlace.x - basePlace.x) * step.x + (towardPlace.y - basePlace.y) * step.y > 0;
|
|
67916
|
+
const side = pastToward ? towardPlace : basePlace;
|
|
67917
|
+
placeByArea.set(area, {
|
|
67918
|
+
x: side.x + step.x,
|
|
67919
|
+
y: side.y + step.y
|
|
67920
|
+
});
|
|
67921
|
+
return {
|
|
67922
|
+
placeByArea,
|
|
67923
|
+
leaningArea: toward && fraction >= 0.5 ? toward : base,
|
|
67924
|
+
progress: step.x || step.y
|
|
67925
|
+
};
|
|
67926
|
+
};
|
|
67927
|
+
|
|
67721
67928
|
// How much of a travel is left to make, as a fraction of the travel that was
|
|
67722
67929
|
// asked for: 1 for a move starting from rest, less for one picked up while the
|
|
67723
67930
|
// track was already moving. Everything is measured in px against the track's
|
|
@@ -67734,6 +67941,19 @@ const ratioOfOneTravel = (track, from, to, targetBefore) => {
|
|
|
67734
67941
|
const ratio = left / asked;
|
|
67735
67942
|
return ratio > 1 ? 1 : ratio;
|
|
67736
67943
|
};
|
|
67944
|
+
// The same for a travel taken over a row, which nobody asked for from anywhere in
|
|
67945
|
+
// particular: what is left, in boxes. Laid out sizes, like the scroll position that
|
|
67946
|
+
// `from` is read off: a row inside a popup still growing is not its transformed size.
|
|
67947
|
+
const ratioOfOneBox = (track, from, to) => {
|
|
67948
|
+
const box = {
|
|
67949
|
+
width: track.offsetWidth,
|
|
67950
|
+
height: track.offsetHeight
|
|
67951
|
+
};
|
|
67952
|
+
const start = offsetToPx(from, box);
|
|
67953
|
+
const end = offsetToPx(to, box);
|
|
67954
|
+
const ratio = Math.abs(end.x - start.x) / box.width + Math.abs(end.y - start.y) / box.height;
|
|
67955
|
+
return ratio > 1 ? 1 : ratio;
|
|
67956
|
+
};
|
|
67737
67957
|
|
|
67738
67958
|
// A translate ("-100% 0%", "-260px 0px", "none") as two numbers of pixels.
|
|
67739
67959
|
// Percentages are the size of the box, which is what a translate resolves them
|
|
@@ -68311,6 +68531,10 @@ const SlideContainer = ({
|
|
|
68311
68531
|
// The slide a hand has just landed on, for the render it asks for: it is
|
|
68312
68532
|
// already there, so there is nothing to play.
|
|
68313
68533
|
const handLandedRef = useRef(null);
|
|
68534
|
+
// The slide a row is being carried away from, pressed: it stays current, so only
|
|
68535
|
+
// a render says the row has to come back to it (see goToArea).
|
|
68536
|
+
const rowBackAskedRef = useRef(false);
|
|
68537
|
+
const [, askRowBack] = useState(0);
|
|
68314
68538
|
// Where the box rests on the row, and what the row starts from: a slide that
|
|
68315
68539
|
// holds on to the user ends the row on that side, and a scroll range starts at
|
|
68316
68540
|
// its first reachable slide (see writeRowAtRest).
|
|
@@ -68550,17 +68774,23 @@ const SlideContainer = ({
|
|
|
68550
68774
|
};
|
|
68551
68775
|
|
|
68552
68776
|
// Where the row is going, for as long as the current slide has not changed:
|
|
68553
|
-
// anything saying where one is reads this first
|
|
68554
|
-
// the caller to announce, a landing
|
|
68777
|
+
// anything saying where one is reads this first, and so does what a press would
|
|
68778
|
+
// do from there (see readSlideOnScreen). Left to the caller to announce, a landing
|
|
68779
|
+
// having more to say in the same breath.
|
|
68555
68780
|
const writeHeading = headingElement => {
|
|
68556
68781
|
const containerEl = containerRef.current;
|
|
68557
68782
|
const area = headingElement ? readArea(headingElement) : null;
|
|
68558
68783
|
// A window does not change slide, it rolls: it has nowhere to be heading.
|
|
68559
|
-
|
|
68560
|
-
|
|
68784
|
+
const heading = loop || area === null || area === containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ? null : area;
|
|
68785
|
+
if (heading === containerEl.getAttribute(SLIDE_HEADING_ATTRIBUTE)) {
|
|
68561
68786
|
return;
|
|
68562
68787
|
}
|
|
68563
|
-
|
|
68788
|
+
if (heading === null) {
|
|
68789
|
+
containerEl.removeAttribute(SLIDE_HEADING_ATTRIBUTE);
|
|
68790
|
+
} else {
|
|
68791
|
+
containerEl.setAttribute(SLIDE_HEADING_ATTRIBUTE, heading);
|
|
68792
|
+
}
|
|
68793
|
+
paintWays();
|
|
68564
68794
|
};
|
|
68565
68795
|
const markAnswered = area => {
|
|
68566
68796
|
const order = readMap().slideElements.map(readArea);
|
|
@@ -68727,6 +68957,7 @@ const SlideContainer = ({
|
|
|
68727
68957
|
offsetRef.current = "0% 0%";
|
|
68728
68958
|
track.style.setProperty("--slide-container-offset", "0% 0%");
|
|
68729
68959
|
containerRef.current.removeAttribute("data-slide-travelling");
|
|
68960
|
+
containerRef.current.removeAttribute("data-slide-row-taken");
|
|
68730
68961
|
sendRowToRest(currentElement);
|
|
68731
68962
|
renderNear(currentArea, {
|
|
68732
68963
|
alongside: true
|
|
@@ -68801,10 +69032,26 @@ const SlideContainer = ({
|
|
|
68801
69032
|
// between the picture and the slide arriving, on the axis it walks.
|
|
68802
69033
|
let travelStep = null;
|
|
68803
69034
|
const drawnArea = stage ? stage.area : drawnAreaRef.current;
|
|
68804
|
-
const
|
|
68805
|
-
|
|
68806
|
-
|
|
69035
|
+
const rowBackAsked = rowBackAskedRef.current;
|
|
69036
|
+
rowBackAskedRef.current = false;
|
|
69037
|
+
// Asked for over a row a hand has left moving: the row belongs to what was asked
|
|
69038
|
+
// for, and where the hand's snap would have ended decides nothing.
|
|
69039
|
+
const rowTaken = handMovingRef.current && !handLanded && (drawnArea !== currentArea || rowBackAsked);
|
|
69040
|
+
const rowStand = rowTaken ? readRowStand(containerRef.current, slideElements) : null;
|
|
69041
|
+
if (rowTaken) {
|
|
69042
|
+
handMovingRef.current = false;
|
|
68807
69043
|
writeHeading(null);
|
|
69044
|
+
paintTravelProgress();
|
|
69045
|
+
}
|
|
69046
|
+
const travelStarts = !still && durationMs > 0 && drawnArea !== undefined && (drawnArea !== currentArea || rowTaken) && slideElements.some(slideElement => readArea(slideElement) === drawnArea);
|
|
69047
|
+
let overRow = null;
|
|
69048
|
+
if (travelStarts && rowStand) {
|
|
69049
|
+
overRow = stageOverRow(rowStand, currentArea, realPlaceOf, travelStepRef.current);
|
|
69050
|
+
stage = stageRef.current = {
|
|
69051
|
+
placeByArea: overRow.placeByArea,
|
|
69052
|
+
area: currentArea
|
|
69053
|
+
};
|
|
69054
|
+
} else if (travelStarts) {
|
|
68808
69055
|
// Where the slide being left IS — the place the stage gave it if a travel
|
|
68809
69056
|
// was already playing (a press landing mid-travel departs from where the
|
|
68810
69057
|
// eye is, not from the map), its own place otherwise.
|
|
@@ -68889,9 +69136,16 @@ const SlideContainer = ({
|
|
|
68889
69136
|
}
|
|
68890
69137
|
const track = trackRef.current;
|
|
68891
69138
|
containerRef.current.toggleAttribute("data-slide-travelling", Boolean(stage));
|
|
68892
|
-
|
|
69139
|
+
if (rowTaken) {
|
|
69140
|
+
containerRef.current.setAttribute("data-slide-row-taken", "");
|
|
69141
|
+
}
|
|
69142
|
+
// How far the track stands from where the scroll position rests: none at rest. A
|
|
69143
|
+
// box that is no scroller has no position, and the track says it all.
|
|
68893
69144
|
const rowPlace = onRow(currentPlace);
|
|
68894
|
-
const restPlace =
|
|
69145
|
+
const restPlace = containerRef.current.hasAttribute("data-slide-row-taken") ? {
|
|
69146
|
+
x: 0,
|
|
69147
|
+
y: 0
|
|
69148
|
+
} : restPlaceRef.current;
|
|
68895
69149
|
const offset = `${(restPlace.x - rowPlace.x) * 100}% ${(restPlace.y - rowPlace.y) * 100}%`;
|
|
68896
69150
|
// The travel that was ASKED for is still one box, whatever is left of it to
|
|
68897
69151
|
// cover: what is left takes what is left of the duration.
|
|
@@ -68914,6 +69168,9 @@ const SlideContainer = ({
|
|
|
68914
69168
|
const onScreenPx = trackOffsetPx(track, containerRef.current);
|
|
68915
69169
|
offsetOnScreen = `${onScreenPx.x}px ${onScreenPx.y}px`;
|
|
68916
69170
|
}
|
|
69171
|
+
if (overRow) {
|
|
69172
|
+
offsetOnScreen = rowStand.offset;
|
|
69173
|
+
}
|
|
68917
69174
|
const offsetBefore = offsetOnScreen ?? offsetRef.current;
|
|
68918
69175
|
offsetRef.current = offset;
|
|
68919
69176
|
track.style.setProperty("--slide-container-offset", offset);
|
|
@@ -68932,10 +69189,10 @@ const SlideContainer = ({
|
|
|
68932
69189
|
// short distance for a full duration. One box in `duration` is the pace;
|
|
68933
69190
|
// this only ever shortens it (a longer travel is not made slower, which
|
|
68934
69191
|
// would make a two-box move drag).
|
|
68935
|
-
const travelRatio = ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
69192
|
+
const travelRatio = overRow ? ratioOfOneBox(track, offsetBefore, offset) : ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
68936
69193
|
// Already moving, so there is no ease-in to play: it would stall the track
|
|
68937
69194
|
// for an instant right where the eye is following it.
|
|
68938
|
-
const easing = travelInFlight ? "ease-out" : "ease";
|
|
69195
|
+
const easing = travelInFlight || overRow ? "ease-out" : "ease";
|
|
68939
69196
|
// Cancelled rather than layered: two animations on the same property
|
|
68940
69197
|
// would blend, and what one sees then is neither of the two moves.
|
|
68941
69198
|
trackAnimationRef.current?.cancel();
|
|
@@ -68943,15 +69200,21 @@ const SlideContainer = ({
|
|
|
68943
69200
|
translate: offsetBefore
|
|
68944
69201
|
}, {
|
|
68945
69202
|
translate: offset
|
|
68946
|
-
}],
|
|
69203
|
+
}],
|
|
69204
|
+
// Backwards: the first frame's clock can read a fraction of a millisecond
|
|
69205
|
+
// before the start time Chrome gives the animation, which would show the
|
|
69206
|
+
// travel already over.
|
|
69207
|
+
{
|
|
68947
69208
|
duration: durationMs * travelRatio,
|
|
68948
|
-
easing
|
|
69209
|
+
easing,
|
|
69210
|
+
fill: "backwards"
|
|
68949
69211
|
});
|
|
68950
69212
|
// The trait travels with the slides, from a whole box away.
|
|
68951
|
-
const
|
|
69213
|
+
const stepProgress = travelStep ? travelStep.x || travelStep.y : 0;
|
|
69214
|
+
const progressFrom = overRow ? overRow.progress : stepProgress;
|
|
68952
69215
|
// The slide the picture leans on for the length of it is the one being
|
|
68953
69216
|
// LEFT: it is the second one in the frame until the travel is over.
|
|
68954
|
-
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, drawnArea);
|
|
69217
|
+
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, overRow ? overRow.leaningArea : drawnArea);
|
|
68955
69218
|
// Presses still waiting behind this one: it is already late, so it is
|
|
68956
69219
|
// sent home at once rather than played out at the pace of someone who
|
|
68957
69220
|
// has stopped pressing. Someone pressing → four times is asking to be
|
|
@@ -68972,6 +69235,17 @@ const SlideContainer = ({
|
|
|
68972
69235
|
// thing it was standing for is over.
|
|
68973
69236
|
settleTravel();
|
|
68974
69237
|
}
|
|
69238
|
+
// The row taken with nothing playing to give it back (no duration, a page change
|
|
69239
|
+
// carrying the slide): no scroller for as long as the compositor takes to hear it.
|
|
69240
|
+
if (!stageRef.current && containerRef.current.hasAttribute("data-slide-row-taken")) {
|
|
69241
|
+
requestAnimationFrame(() => {
|
|
69242
|
+
requestAnimationFrame(() => {
|
|
69243
|
+
if (trackAnimationRef.current?.playState !== "running") {
|
|
69244
|
+
settleTravel();
|
|
69245
|
+
}
|
|
69246
|
+
});
|
|
69247
|
+
});
|
|
69248
|
+
}
|
|
68975
69249
|
// A window waiting for its travel to be over (see goToArea's own loop
|
|
68976
69250
|
// branch): the animation says when, and says it about the move that just
|
|
68977
69251
|
// started rather than about a duration counted out beside it.
|
|
@@ -69035,9 +69309,9 @@ const SlideContainer = ({
|
|
|
69035
69309
|
sendRowToRest(currentElement);
|
|
69036
69310
|
}
|
|
69037
69311
|
// Said last, and after data-current has been written: what a travel would do
|
|
69038
|
-
// is read off the map from the slide
|
|
69039
|
-
//
|
|
69040
|
-
paintWays(
|
|
69312
|
+
// is read off the map from the slide on screen, and off the locks that slide is
|
|
69313
|
+
// wearing in this very commit.
|
|
69314
|
+
paintWays();
|
|
69041
69315
|
// …and once everything this commit had to write is written, the box says so
|
|
69042
69316
|
// — a single announcement for a render that may have changed several of the
|
|
69043
69317
|
// facts at once.
|
|
@@ -69084,14 +69358,18 @@ const SlideContainer = ({
|
|
|
69084
69358
|
placeOf
|
|
69085
69359
|
} = readMap();
|
|
69086
69360
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
69087
|
-
if (!area
|
|
69361
|
+
if (!area) {
|
|
69088
69362
|
return false;
|
|
69089
69363
|
}
|
|
69364
|
+
// What a press leaves is the slide on screen, which is what paintWays offers from.
|
|
69365
|
+
// A landing is the row arriving there, and what it leaves is the current one.
|
|
69366
|
+
const areaLeft = landed ? readArea(currentElement) : readSlideOnScreen(containerRef.current) ?? readArea(currentElement);
|
|
69367
|
+
const elementLeft = slideElements.find(slideElement => readArea(slideElement) === areaLeft) || currentElement;
|
|
69090
69368
|
if (forward === undefined) {
|
|
69091
69369
|
// Asked for by name (--navi-go-to-slide, --navi-back) rather than by a
|
|
69092
69370
|
// direction: which way it is is still a fact, read off the map, and it is
|
|
69093
69371
|
// what says which of the two locks below applies.
|
|
69094
|
-
const from = placeOf.get(
|
|
69372
|
+
const from = placeOf.get(areaLeft) || {
|
|
69095
69373
|
x: 0,
|
|
69096
69374
|
y: 0
|
|
69097
69375
|
};
|
|
@@ -69109,9 +69387,18 @@ const SlideContainer = ({
|
|
|
69109
69387
|
// one departure the forward hold does not apply to. Said here rather than
|
|
69110
69388
|
// by dropping the attribute, which stays the render's to write — a hold
|
|
69111
69389
|
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
69112
|
-
if (forward ? !released &&
|
|
69390
|
+
if (area !== areaLeft && (forward ? !released && elementLeft.hasAttribute("data-prevent-nav-next") : elementLeft.hasAttribute("data-prevent-nav-previous"))) {
|
|
69113
69391
|
return false;
|
|
69114
69392
|
}
|
|
69393
|
+
if (area === readArea(currentElement)) {
|
|
69394
|
+
// Nowhere to go, unless a hand has left the row on its way off this slide.
|
|
69395
|
+
if (!handMovingRef.current) {
|
|
69396
|
+
return false;
|
|
69397
|
+
}
|
|
69398
|
+
rowBackAskedRef.current = true;
|
|
69399
|
+
askRowBack(count => count + 1);
|
|
69400
|
+
return true;
|
|
69401
|
+
}
|
|
69115
69402
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
69116
69403
|
// who pressed what, and whether they were on the keyboard, is knowable now
|
|
69117
69404
|
// and gone a render later. The slide arriving is made reachable on the spot
|
|
@@ -69493,7 +69780,7 @@ const SlideContainer = ({
|
|
|
69493
69780
|
const move = (dx, dy, event, {
|
|
69494
69781
|
value,
|
|
69495
69782
|
released
|
|
69496
|
-
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
69783
|
+
} = {}) => goToArea(areaTowards(dx, dy, readSlideOnScreen(containerRef.current)), {
|
|
69497
69784
|
forward: dx > 0 || dy > 0,
|
|
69498
69785
|
event,
|
|
69499
69786
|
value,
|
|
@@ -69574,10 +69861,10 @@ const SlideContainer = ({
|
|
|
69574
69861
|
const paintNativeRow = ({
|
|
69575
69862
|
finger
|
|
69576
69863
|
}) => {
|
|
69577
|
-
|
|
69864
|
+
const containerEl = containerRef.current;
|
|
69865
|
+
if (trackAnimationRef.current?.playState === "running" || containerEl.hasAttribute("data-slide-row-taken")) {
|
|
69578
69866
|
return;
|
|
69579
69867
|
}
|
|
69580
|
-
const containerEl = containerRef.current;
|
|
69581
69868
|
const slideElements = Array.from(trackRef.current.children);
|
|
69582
69869
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69583
69870
|
if (!currentElement) {
|
|
@@ -69795,13 +70082,19 @@ const SlideContainer = ({
|
|
|
69795
70082
|
// ([data-slide-ways~="right"]), or through useSlideContainer, which watches
|
|
69796
70083
|
// them. Nothing is told, everything is read: it is what lets them sit
|
|
69797
70084
|
// anywhere, and what lets a container nobody drives still be followed.
|
|
69798
|
-
const paintWays =
|
|
70085
|
+
const paintWays = () => {
|
|
69799
70086
|
const ways = [];
|
|
69800
70087
|
const held = [];
|
|
70088
|
+
const {
|
|
70089
|
+
slideElements,
|
|
70090
|
+
order
|
|
70091
|
+
} = readMap();
|
|
70092
|
+
const areaOnScreen = readSlideOnScreen(containerRef.current);
|
|
70093
|
+
const elementOnScreen = slideElements.find(slideElement => readArea(slideElement) === areaOnScreen);
|
|
69801
70094
|
// Whether the slide being left says no, this way. The one gate again, read
|
|
69802
70095
|
// exactly as goToArea reads it at the moment of the travel — so what is
|
|
69803
70096
|
// published here and what would happen cannot disagree.
|
|
69804
|
-
const holdsTowards = forward =>
|
|
70097
|
+
const holdsTowards = forward => elementOnScreen?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
69805
70098
|
const say = (name, offered, forward) => {
|
|
69806
70099
|
if (!offered) {
|
|
69807
70100
|
return;
|
|
@@ -69818,18 +70111,14 @@ const SlideContainer = ({
|
|
|
69818
70111
|
// would be saying it about every row on the page.
|
|
69819
70112
|
continue;
|
|
69820
70113
|
}
|
|
69821
|
-
say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
|
|
70114
|
+
say(direction, Boolean(areaTowards(dx, dy, areaOnScreen)), dx > 0 || dy > 0);
|
|
69822
70115
|
}
|
|
69823
70116
|
// The two ends are ways out like the others — "all the way that way", said
|
|
69824
70117
|
// by the map's own order rather than by a direction (see goToEnd, and
|
|
69825
70118
|
// SlideContainer.First / .Last). Offered while one is not already standing
|
|
69826
70119
|
// there, and held by the same lock the direction they lie in would be.
|
|
69827
|
-
|
|
69828
|
-
|
|
69829
|
-
} = readMap();
|
|
69830
|
-
const currentArea = currentElement ? readArea(currentElement) : undefined;
|
|
69831
|
-
say("first", order.length > 0 && order[0] !== currentArea, false);
|
|
69832
|
-
say("last", order.length > 0 && order[order.length - 1] !== currentArea, true);
|
|
70120
|
+
say("first", order.length > 0 && order[0] !== areaOnScreen, false);
|
|
70121
|
+
say("last", order.length > 0 && order[order.length - 1] !== areaOnScreen, true);
|
|
69833
70122
|
for (const element of travelPainters()) {
|
|
69834
70123
|
if (ways.length) {
|
|
69835
70124
|
element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
|
|
@@ -69922,7 +70211,8 @@ const SlideContainer = ({
|
|
|
69922
70211
|
"--slide-travel-progress": 0
|
|
69923
70212
|
}], {
|
|
69924
70213
|
duration: durationMs,
|
|
69925
|
-
easing
|
|
70214
|
+
easing,
|
|
70215
|
+
fill: "backwards"
|
|
69926
70216
|
}));
|
|
69927
70217
|
progressAnimationsRef.current[0].finished.then(() => {
|
|
69928
70218
|
progressAnimationsRef.current = [];
|
|
@@ -105867,7 +106157,9 @@ const StepList = ({
|
|
|
105867
106157
|
const read = () => {
|
|
105868
106158
|
const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
105869
106159
|
const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
|
|
105870
|
-
|
|
106160
|
+
// Which step is lit follows the screen. The halo counts from the current slide,
|
|
106161
|
+
// which is what the progress it draws with is measured against.
|
|
106162
|
+
setContainerCurrent(readSlideOnScreen(containerElement));
|
|
105871
106163
|
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
105872
106164
|
if (currentIdx === -1) {
|
|
105873
106165
|
// A slide no step names (a confirmation screen): the halo is not
|