@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/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;
|
|
@@ -41773,12 +41781,6 @@ const watchRowScroll = (
|
|
|
41773
41781
|
liftedAt = null;
|
|
41774
41782
|
onLeftToSnap?.(moved > 0);
|
|
41775
41783
|
};
|
|
41776
|
-
const onTouchStart = () => {
|
|
41777
|
-
touching = true;
|
|
41778
|
-
touchedSinceStop = true;
|
|
41779
|
-
liftedAt = null;
|
|
41780
|
-
clearTimeout(stoppedTimeout);
|
|
41781
|
-
};
|
|
41782
41784
|
const onTouchEnd = (touchEvent) => {
|
|
41783
41785
|
touching = touchEvent.touches.length > 0;
|
|
41784
41786
|
if (!touching) {
|
|
@@ -41786,18 +41788,31 @@ const watchRowScroll = (
|
|
|
41786
41788
|
armSilence();
|
|
41787
41789
|
}
|
|
41788
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
|
+
};
|
|
41789
41808
|
scroller.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
41790
41809
|
scroller.addEventListener("scrollend", onLand);
|
|
41791
41810
|
scroller.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
41792
|
-
scroller.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
41793
|
-
scroller.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
41794
41811
|
return () => {
|
|
41795
41812
|
clearTimeout(stoppedTimeout);
|
|
41796
41813
|
scroller.removeEventListener("scroll", onScrollEvent);
|
|
41797
41814
|
scroller.removeEventListener("scrollend", onLand);
|
|
41798
41815
|
scroller.removeEventListener("touchstart", onTouchStart);
|
|
41799
|
-
scroller.removeEventListener("touchend", onTouchEnd);
|
|
41800
|
-
scroller.removeEventListener("touchcancel", onTouchEnd);
|
|
41801
41816
|
};
|
|
41802
41817
|
};
|
|
41803
41818
|
|
|
@@ -41835,6 +41850,19 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
41835
41850
|
overflow: visible;
|
|
41836
41851
|
}
|
|
41837
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
|
+
|
|
41838
41866
|
& > [data-route-row-pane] {
|
|
41839
41867
|
scroll-snap-align: start;
|
|
41840
41868
|
scroll-snap-stop: always;
|
|
@@ -42092,6 +42120,37 @@ const RouteTravel = ({
|
|
|
42092
42120
|
});
|
|
42093
42121
|
};
|
|
42094
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
|
+
|
|
42095
42154
|
// The row has stopped, on the page of the address: everything held for the
|
|
42096
42155
|
// movement is let go of in one go, before anything paints.
|
|
42097
42156
|
const endMovement = () => {
|
|
@@ -42102,6 +42161,7 @@ const RouteTravel = ({
|
|
|
42102
42161
|
}
|
|
42103
42162
|
movementRef.current = null;
|
|
42104
42163
|
row.removeAttribute("data-moving");
|
|
42164
|
+
row.removeAttribute("data-route-row-taken");
|
|
42105
42165
|
publishRouteRowMove(null);
|
|
42106
42166
|
releaseHeldPage();
|
|
42107
42167
|
if (liveRef.current) {
|
|
@@ -42169,16 +42229,23 @@ const RouteTravel = ({
|
|
|
42169
42229
|
}
|
|
42170
42230
|
return;
|
|
42171
42231
|
}
|
|
42172
|
-
|
|
42232
|
+
const movementBefore = movementRef.current;
|
|
42233
|
+
const movement = beginMovement({
|
|
42173
42234
|
travelling: true
|
|
42174
42235
|
});
|
|
42175
|
-
row.scrollTo({
|
|
42176
|
-
left: pane.offsetLeft,
|
|
42177
|
-
behavior: "smooth"
|
|
42178
|
-
});
|
|
42179
42236
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
42180
42237
|
// scroll event is a frame away.
|
|
42181
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
|
+
}
|
|
42182
42249
|
});
|
|
42183
42250
|
|
|
42184
42251
|
// Somebody else is about to move the address to another page of a window: its
|
|
@@ -42210,14 +42277,17 @@ const RouteTravel = ({
|
|
|
42210
42277
|
}, [windowed]);
|
|
42211
42278
|
useLayoutEffect(() => {
|
|
42212
42279
|
const row = rowRef.current;
|
|
42213
|
-
const
|
|
42280
|
+
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
42281
|
+
const onScroll = ({
|
|
42282
|
+
finger
|
|
42283
|
+
}) => {
|
|
42214
42284
|
const {
|
|
42215
42285
|
pages,
|
|
42216
42286
|
currentIndex,
|
|
42217
42287
|
travelByDrag
|
|
42218
42288
|
} = latestRef.current;
|
|
42219
42289
|
const panes = panesOf(row);
|
|
42220
|
-
if (panes.length < 2) {
|
|
42290
|
+
if (panes.length < 2 || isClipped()) {
|
|
42221
42291
|
return;
|
|
42222
42292
|
}
|
|
42223
42293
|
const moving = movementRef.current;
|
|
@@ -42226,11 +42296,24 @@ const RouteTravel = ({
|
|
|
42226
42296
|
// Written by this box, at rest: nobody is moving anything.
|
|
42227
42297
|
return;
|
|
42228
42298
|
}
|
|
42229
|
-
const movement = moving || beginMovement({
|
|
42299
|
+
const movement = moving || beginMovement({
|
|
42300
|
+
byHand: true
|
|
42301
|
+
});
|
|
42230
42302
|
sayRowMoves();
|
|
42231
42303
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
42232
42304
|
return;
|
|
42233
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
|
+
}
|
|
42234
42317
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
42235
42318
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
42236
42319
|
return;
|
|
@@ -42283,7 +42366,7 @@ const RouteTravel = ({
|
|
|
42283
42366
|
};
|
|
42284
42367
|
const onLand = () => {
|
|
42285
42368
|
const movement = movementRef.current;
|
|
42286
|
-
if (!movement) {
|
|
42369
|
+
if (!movement || isClipped()) {
|
|
42287
42370
|
return;
|
|
42288
42371
|
}
|
|
42289
42372
|
const panes = panesOf(row);
|
|
@@ -42312,8 +42395,48 @@ const RouteTravel = ({
|
|
|
42312
42395
|
onLand,
|
|
42313
42396
|
onLeftToSnap
|
|
42314
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
|
+
});
|
|
42315
42433
|
return () => {
|
|
42316
42434
|
stopWatching();
|
|
42435
|
+
row.removeEventListener("touchstart", onHandBack);
|
|
42436
|
+
row.removeEventListener("wheel", onHandBack);
|
|
42437
|
+
document.removeEventListener("click", onPress, {
|
|
42438
|
+
capture: true
|
|
42439
|
+
});
|
|
42317
42440
|
movementRef.current?.resumeScrollRecording?.();
|
|
42318
42441
|
movementRef.current = null;
|
|
42319
42442
|
publishRouteRowMove(null);
|
|
@@ -42441,6 +42564,15 @@ const copyScrollPositions = (from, to) => {
|
|
|
42441
42564
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
42442
42565
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
42443
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
|
+
};
|
|
42444
42576
|
const normalizePage = page => page.isRoute ? {
|
|
42445
42577
|
route: page,
|
|
42446
42578
|
params: undefined
|
|
@@ -58187,8 +58319,9 @@ const wordsOf = (element, attribute) => {
|
|
|
58187
58319
|
return value ? value.split(" ") : NO_WAYS;
|
|
58188
58320
|
};
|
|
58189
58321
|
|
|
58190
|
-
// The slide
|
|
58191
|
-
//
|
|
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.
|
|
58192
58325
|
const readSlideOnScreen = (element) =>
|
|
58193
58326
|
element.getAttribute(SLIDE_HEADING_ATTRIBUTE) ??
|
|
58194
58327
|
element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ??
|
|
@@ -58238,10 +58371,9 @@ const sameSlideContainerState = (a, b) =>
|
|
|
58238
58371
|
* has let go, because that is what one is looking at. It can come before the
|
|
58239
58372
|
* slide's own signal and `onCurrentChange`, which wait for the row to rest.
|
|
58240
58373
|
* `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.
|
|
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.
|
|
58245
58377
|
* `known` is false until the box has been read: no box was named and none is
|
|
58246
58378
|
* above, the id names nothing, or — for one commit — this mounted before the
|
|
58247
58379
|
* box had painted. Until then `can` answers YES, because it is the answer that
|
|
@@ -69267,6 +69399,10 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
69267
69399
|
overflow-y: auto;
|
|
69268
69400
|
}
|
|
69269
69401
|
|
|
69402
|
+
&[data-slide-row-taken] {
|
|
69403
|
+
overflow: clip;
|
|
69404
|
+
}
|
|
69405
|
+
|
|
69270
69406
|
&:focus {
|
|
69271
69407
|
outline: none;
|
|
69272
69408
|
}
|
|
@@ -69318,6 +69454,77 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
69318
69454
|
}
|
|
69319
69455
|
`;
|
|
69320
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
|
+
|
|
69321
69528
|
// How much of a travel is left to make, as a fraction of the travel that was
|
|
69322
69529
|
// asked for: 1 for a move starting from rest, less for one picked up while the
|
|
69323
69530
|
// track was already moving. Everything is measured in px against the track's
|
|
@@ -69334,6 +69541,19 @@ const ratioOfOneTravel = (track, from, to, targetBefore) => {
|
|
|
69334
69541
|
const ratio = left / asked;
|
|
69335
69542
|
return ratio > 1 ? 1 : ratio;
|
|
69336
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
|
+
};
|
|
69337
69557
|
|
|
69338
69558
|
// A translate ("-100% 0%", "-260px 0px", "none") as two numbers of pixels.
|
|
69339
69559
|
// Percentages are the size of the box, which is what a translate resolves them
|
|
@@ -69911,6 +70131,10 @@ const SlideContainer = ({
|
|
|
69911
70131
|
// The slide a hand has just landed on, for the render it asks for: it is
|
|
69912
70132
|
// already there, so there is nothing to play.
|
|
69913
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);
|
|
69914
70138
|
// Where the box rests on the row, and what the row starts from: a slide that
|
|
69915
70139
|
// holds on to the user ends the row on that side, and a scroll range starts at
|
|
69916
70140
|
// its first reachable slide (see writeRowAtRest).
|
|
@@ -70157,17 +70381,23 @@ const SlideContainer = ({
|
|
|
70157
70381
|
};
|
|
70158
70382
|
|
|
70159
70383
|
// 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
|
|
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.
|
|
70162
70387
|
const writeHeading = headingElement => {
|
|
70163
70388
|
const containerEl = containerRef.current;
|
|
70164
70389
|
const area = headingElement ? readArea(headingElement) : null;
|
|
70165
70390
|
// A window does not change slide, it rolls: it has nowhere to be heading.
|
|
70166
|
-
|
|
70167
|
-
|
|
70391
|
+
const heading = loop || area === null || area === containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ? null : area;
|
|
70392
|
+
if (heading === containerEl.getAttribute(SLIDE_HEADING_ATTRIBUTE)) {
|
|
70168
70393
|
return;
|
|
70169
70394
|
}
|
|
70170
|
-
|
|
70395
|
+
if (heading === null) {
|
|
70396
|
+
containerEl.removeAttribute(SLIDE_HEADING_ATTRIBUTE);
|
|
70397
|
+
} else {
|
|
70398
|
+
containerEl.setAttribute(SLIDE_HEADING_ATTRIBUTE, heading);
|
|
70399
|
+
}
|
|
70400
|
+
paintWays();
|
|
70171
70401
|
};
|
|
70172
70402
|
const markAnswered = area => {
|
|
70173
70403
|
const order = readMap().slideElements.map(readArea);
|
|
@@ -70334,6 +70564,7 @@ const SlideContainer = ({
|
|
|
70334
70564
|
offsetRef.current = "0% 0%";
|
|
70335
70565
|
track.style.setProperty("--slide-container-offset", "0% 0%");
|
|
70336
70566
|
containerRef.current.removeAttribute("data-slide-travelling");
|
|
70567
|
+
containerRef.current.removeAttribute("data-slide-row-taken");
|
|
70337
70568
|
sendRowToRest(currentElement);
|
|
70338
70569
|
renderNear(currentArea, {
|
|
70339
70570
|
alongside: true
|
|
@@ -70408,10 +70639,26 @@ const SlideContainer = ({
|
|
|
70408
70639
|
// between the picture and the slide arriving, on the axis it walks.
|
|
70409
70640
|
let travelStep = null;
|
|
70410
70641
|
const drawnArea = stage ? stage.area : drawnAreaRef.current;
|
|
70411
|
-
const
|
|
70412
|
-
|
|
70413
|
-
|
|
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;
|
|
70414
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) {
|
|
70415
70662
|
// Where the slide being left IS — the place the stage gave it if a travel
|
|
70416
70663
|
// was already playing (a press landing mid-travel departs from where the
|
|
70417
70664
|
// eye is, not from the map), its own place otherwise.
|
|
@@ -70496,9 +70743,16 @@ const SlideContainer = ({
|
|
|
70496
70743
|
}
|
|
70497
70744
|
const track = trackRef.current;
|
|
70498
70745
|
containerRef.current.toggleAttribute("data-slide-travelling", Boolean(stage));
|
|
70499
|
-
|
|
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.
|
|
70500
70751
|
const rowPlace = onRow(currentPlace);
|
|
70501
|
-
const restPlace =
|
|
70752
|
+
const restPlace = containerRef.current.hasAttribute("data-slide-row-taken") ? {
|
|
70753
|
+
x: 0,
|
|
70754
|
+
y: 0
|
|
70755
|
+
} : restPlaceRef.current;
|
|
70502
70756
|
const offset = `${(restPlace.x - rowPlace.x) * 100}% ${(restPlace.y - rowPlace.y) * 100}%`;
|
|
70503
70757
|
// The travel that was ASKED for is still one box, whatever is left of it to
|
|
70504
70758
|
// cover: what is left takes what is left of the duration.
|
|
@@ -70521,6 +70775,9 @@ const SlideContainer = ({
|
|
|
70521
70775
|
const onScreenPx = trackOffsetPx(track, containerRef.current);
|
|
70522
70776
|
offsetOnScreen = `${onScreenPx.x}px ${onScreenPx.y}px`;
|
|
70523
70777
|
}
|
|
70778
|
+
if (overRow) {
|
|
70779
|
+
offsetOnScreen = rowStand.offset;
|
|
70780
|
+
}
|
|
70524
70781
|
const offsetBefore = offsetOnScreen ?? offsetRef.current;
|
|
70525
70782
|
offsetRef.current = offset;
|
|
70526
70783
|
track.style.setProperty("--slide-container-offset", offset);
|
|
@@ -70539,10 +70796,10 @@ const SlideContainer = ({
|
|
|
70539
70796
|
// short distance for a full duration. One box in `duration` is the pace;
|
|
70540
70797
|
// this only ever shortens it (a longer travel is not made slower, which
|
|
70541
70798
|
// would make a two-box move drag).
|
|
70542
|
-
const travelRatio = ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
70799
|
+
const travelRatio = overRow ? ratioOfOneBox(track, offsetBefore, offset) : ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
70543
70800
|
// Already moving, so there is no ease-in to play: it would stall the track
|
|
70544
70801
|
// for an instant right where the eye is following it.
|
|
70545
|
-
const easing = travelInFlight ? "ease-out" : "ease";
|
|
70802
|
+
const easing = travelInFlight || overRow ? "ease-out" : "ease";
|
|
70546
70803
|
// Cancelled rather than layered: two animations on the same property
|
|
70547
70804
|
// would blend, and what one sees then is neither of the two moves.
|
|
70548
70805
|
trackAnimationRef.current?.cancel();
|
|
@@ -70550,15 +70807,21 @@ const SlideContainer = ({
|
|
|
70550
70807
|
translate: offsetBefore
|
|
70551
70808
|
}, {
|
|
70552
70809
|
translate: offset
|
|
70553
|
-
}],
|
|
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
|
+
{
|
|
70554
70815
|
duration: durationMs * travelRatio,
|
|
70555
|
-
easing
|
|
70816
|
+
easing,
|
|
70817
|
+
fill: "backwards"
|
|
70556
70818
|
});
|
|
70557
70819
|
// The trait travels with the slides, from a whole box away.
|
|
70558
|
-
const
|
|
70820
|
+
const stepProgress = travelStep ? travelStep.x || travelStep.y : 0;
|
|
70821
|
+
const progressFrom = overRow ? overRow.progress : stepProgress;
|
|
70559
70822
|
// The slide the picture leans on for the length of it is the one being
|
|
70560
70823
|
// LEFT: it is the second one in the frame until the travel is over.
|
|
70561
|
-
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, drawnArea);
|
|
70824
|
+
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, overRow ? overRow.leaningArea : drawnArea);
|
|
70562
70825
|
// Presses still waiting behind this one: it is already late, so it is
|
|
70563
70826
|
// sent home at once rather than played out at the pace of someone who
|
|
70564
70827
|
// has stopped pressing. Someone pressing → four times is asking to be
|
|
@@ -70579,6 +70842,17 @@ const SlideContainer = ({
|
|
|
70579
70842
|
// thing it was standing for is over.
|
|
70580
70843
|
settleTravel();
|
|
70581
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
|
+
}
|
|
70582
70856
|
// A window waiting for its travel to be over (see goToArea's own loop
|
|
70583
70857
|
// branch): the animation says when, and says it about the move that just
|
|
70584
70858
|
// started rather than about a duration counted out beside it.
|
|
@@ -70642,9 +70916,9 @@ const SlideContainer = ({
|
|
|
70642
70916
|
sendRowToRest(currentElement);
|
|
70643
70917
|
}
|
|
70644
70918
|
// 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(
|
|
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();
|
|
70648
70922
|
// …and once everything this commit had to write is written, the box says so
|
|
70649
70923
|
// — a single announcement for a render that may have changed several of the
|
|
70650
70924
|
// facts at once.
|
|
@@ -70691,14 +70965,18 @@ const SlideContainer = ({
|
|
|
70691
70965
|
placeOf
|
|
70692
70966
|
} = readMap();
|
|
70693
70967
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
70694
|
-
if (!area
|
|
70968
|
+
if (!area) {
|
|
70695
70969
|
return false;
|
|
70696
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;
|
|
70697
70975
|
if (forward === undefined) {
|
|
70698
70976
|
// Asked for by name (--navi-go-to-slide, --navi-back) rather than by a
|
|
70699
70977
|
// direction: which way it is is still a fact, read off the map, and it is
|
|
70700
70978
|
// what says which of the two locks below applies.
|
|
70701
|
-
const from = placeOf.get(
|
|
70979
|
+
const from = placeOf.get(areaLeft) || {
|
|
70702
70980
|
x: 0,
|
|
70703
70981
|
y: 0
|
|
70704
70982
|
};
|
|
@@ -70716,9 +70994,18 @@ const SlideContainer = ({
|
|
|
70716
70994
|
// one departure the forward hold does not apply to. Said here rather than
|
|
70717
70995
|
// by dropping the attribute, which stays the render's to write — a hold
|
|
70718
70996
|
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
70719
|
-
if (forward ? !released &&
|
|
70997
|
+
if (area !== areaLeft && (forward ? !released && elementLeft.hasAttribute("data-prevent-nav-next") : elementLeft.hasAttribute("data-prevent-nav-previous"))) {
|
|
70720
70998
|
return false;
|
|
70721
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
|
+
}
|
|
70722
71009
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
70723
71010
|
// who pressed what, and whether they were on the keyboard, is knowable now
|
|
70724
71011
|
// and gone a render later. The slide arriving is made reachable on the spot
|
|
@@ -71100,7 +71387,7 @@ const SlideContainer = ({
|
|
|
71100
71387
|
const move = (dx, dy, event, {
|
|
71101
71388
|
value,
|
|
71102
71389
|
released
|
|
71103
|
-
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
71390
|
+
} = {}) => goToArea(areaTowards(dx, dy, readSlideOnScreen(containerRef.current)), {
|
|
71104
71391
|
forward: dx > 0 || dy > 0,
|
|
71105
71392
|
event,
|
|
71106
71393
|
value,
|
|
@@ -71181,10 +71468,10 @@ const SlideContainer = ({
|
|
|
71181
71468
|
const paintNativeRow = ({
|
|
71182
71469
|
finger
|
|
71183
71470
|
}) => {
|
|
71184
|
-
|
|
71471
|
+
const containerEl = containerRef.current;
|
|
71472
|
+
if (trackAnimationRef.current?.playState === "running" || containerEl.hasAttribute("data-slide-row-taken")) {
|
|
71185
71473
|
return;
|
|
71186
71474
|
}
|
|
71187
|
-
const containerEl = containerRef.current;
|
|
71188
71475
|
const slideElements = Array.from(trackRef.current.children);
|
|
71189
71476
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
71190
71477
|
if (!currentElement) {
|
|
@@ -71402,13 +71689,19 @@ const SlideContainer = ({
|
|
|
71402
71689
|
// ([data-slide-ways~="right"]), or through useSlideContainer, which watches
|
|
71403
71690
|
// them. Nothing is told, everything is read: it is what lets them sit
|
|
71404
71691
|
// anywhere, and what lets a container nobody drives still be followed.
|
|
71405
|
-
const paintWays =
|
|
71692
|
+
const paintWays = () => {
|
|
71406
71693
|
const ways = [];
|
|
71407
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);
|
|
71408
71701
|
// Whether the slide being left says no, this way. The one gate again, read
|
|
71409
71702
|
// exactly as goToArea reads it at the moment of the travel — so what is
|
|
71410
71703
|
// published here and what would happen cannot disagree.
|
|
71411
|
-
const holdsTowards = forward =>
|
|
71704
|
+
const holdsTowards = forward => elementOnScreen?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
71412
71705
|
const say = (name, offered, forward) => {
|
|
71413
71706
|
if (!offered) {
|
|
71414
71707
|
return;
|
|
@@ -71425,18 +71718,14 @@ const SlideContainer = ({
|
|
|
71425
71718
|
// would be saying it about every row on the page.
|
|
71426
71719
|
continue;
|
|
71427
71720
|
}
|
|
71428
|
-
say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
|
|
71721
|
+
say(direction, Boolean(areaTowards(dx, dy, areaOnScreen)), dx > 0 || dy > 0);
|
|
71429
71722
|
}
|
|
71430
71723
|
// The two ends are ways out like the others — "all the way that way", said
|
|
71431
71724
|
// by the map's own order rather than by a direction (see goToEnd, and
|
|
71432
71725
|
// SlideContainer.First / .Last). Offered while one is not already standing
|
|
71433
71726
|
// 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);
|
|
71727
|
+
say("first", order.length > 0 && order[0] !== areaOnScreen, false);
|
|
71728
|
+
say("last", order.length > 0 && order[order.length - 1] !== areaOnScreen, true);
|
|
71440
71729
|
for (const element of travelPainters()) {
|
|
71441
71730
|
if (ways.length) {
|
|
71442
71731
|
element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
|
|
@@ -71529,7 +71818,8 @@ const SlideContainer = ({
|
|
|
71529
71818
|
"--slide-travel-progress": 0
|
|
71530
71819
|
}], {
|
|
71531
71820
|
duration: durationMs,
|
|
71532
|
-
easing
|
|
71821
|
+
easing,
|
|
71822
|
+
fill: "backwards"
|
|
71533
71823
|
}));
|
|
71534
71824
|
progressAnimationsRef.current[0].finished.then(() => {
|
|
71535
71825
|
progressAnimationsRef.current = [];
|
|
@@ -107995,7 +108285,9 @@ const StepList = ({
|
|
|
107995
108285
|
const read = () => {
|
|
107996
108286
|
const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
107997
108287
|
const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
|
|
107998
|
-
|
|
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));
|
|
107999
108291
|
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
108000
108292
|
if (currentIdx === -1) {
|
|
108001
108293
|
// A slide no step names (a confirmation screen): the halo is not
|