@jsenv/navi 0.30.4 → 0.30.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dev/jsenv_navi.js +532 -77
- package/dist/dev/jsenv_navi.js.map +10 -9
- package/dist/jsenv_navi.js +532 -77
- package/dist/jsenv_navi.js.map +10 -9
- package/docs/drag_to_travel.md +15 -1
- package/docs/mobile_touch.md +64 -5
- package/docs/navigation.md +54 -3
- package/package.json +1 -1
package/dist/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) {
|
|
@@ -36505,9 +36505,23 @@ const collectRoutePages = children => {
|
|
|
36505
36505
|
* Whether the next container down is a row a hand scrolls: every page has a
|
|
36506
36506
|
* place on it from the start, and the browser moves between them (see
|
|
36507
36507
|
* RouteTravel's `nativeScroll`). Told to one container, not to the pages it holds.
|
|
36508
|
+
* False, or `{ renderNeighbour }`.
|
|
36508
36509
|
*/
|
|
36509
36510
|
const RouteRowContext = createContext(false);
|
|
36510
36511
|
|
|
36512
|
+
/**
|
|
36513
|
+
* What stands in the pane of a neighbour until its page does. A component of its
|
|
36514
|
+
* own: what the application calls in there (a hook, a signal) answers to it, however
|
|
36515
|
+
* many neighbours the row has at that moment.
|
|
36516
|
+
*/
|
|
36517
|
+
const RouteRowNeighbour = ({
|
|
36518
|
+
renderNeighbour,
|
|
36519
|
+
page
|
|
36520
|
+
}) => jsx("div", {
|
|
36521
|
+
"data-route-row-neighbour": "",
|
|
36522
|
+
children: renderNeighbour(page)
|
|
36523
|
+
});
|
|
36524
|
+
|
|
36511
36525
|
// A container as a row: one pane per leaf, in the order they are written. A page
|
|
36512
36526
|
// still builds when its route first matches, since a route's actions only ask
|
|
36513
36527
|
// while it does: before that its pane is empty. Once built it stays, with what
|
|
@@ -36520,6 +36534,9 @@ const RouteRowContext = createContext(false);
|
|
|
36520
36534
|
const RouteRowContainer = ({
|
|
36521
36535
|
children
|
|
36522
36536
|
}) => {
|
|
36537
|
+
const {
|
|
36538
|
+
renderNeighbour
|
|
36539
|
+
} = useContext(RouteRowContext);
|
|
36523
36540
|
const {
|
|
36524
36541
|
activeBranch,
|
|
36525
36542
|
leaves
|
|
@@ -36568,16 +36585,19 @@ const RouteRowContainer = ({
|
|
|
36568
36585
|
shown: true
|
|
36569
36586
|
}, "fallback");
|
|
36570
36587
|
}
|
|
36571
|
-
|
|
36588
|
+
const shownIndex = activeBranch ? leaves.findIndex(leaf => leaf.id === activeBranch.id) : -1;
|
|
36589
|
+
return leaves.map((leaf, index) => jsx(RouteRowPane, {
|
|
36572
36590
|
branch: leaf,
|
|
36573
36591
|
page: pageOf(leaf),
|
|
36574
|
-
shown:
|
|
36592
|
+
shown: index === shownIndex,
|
|
36593
|
+
renderNeighbour: shownIndex !== -1 && (index === shownIndex - 1 || index === shownIndex + 1) ? renderNeighbour : undefined
|
|
36575
36594
|
}, leaf.id));
|
|
36576
36595
|
};
|
|
36577
36596
|
const RouteRowPane = ({
|
|
36578
36597
|
branch,
|
|
36579
36598
|
page,
|
|
36580
|
-
shown
|
|
36599
|
+
shown,
|
|
36600
|
+
renderNeighbour
|
|
36581
36601
|
}) => {
|
|
36582
36602
|
// The very vnode rendered last, while not on screen: Preact does not look into
|
|
36583
36603
|
// it, and a page nobody can see costs nothing when the address moves.
|
|
@@ -36596,7 +36616,10 @@ const RouteRowPane = ({
|
|
|
36596
36616
|
"data-route-row-pane": "",
|
|
36597
36617
|
"data-built": contentRef.current ? "" : undefined,
|
|
36598
36618
|
inert: !shown,
|
|
36599
|
-
children: contentRef.current
|
|
36619
|
+
children: contentRef.current || renderNeighbour && jsx(RouteRowNeighbour, {
|
|
36620
|
+
renderNeighbour: renderNeighbour,
|
|
36621
|
+
page: branchRoutePage(branch)
|
|
36622
|
+
})
|
|
36600
36623
|
});
|
|
36601
36624
|
};
|
|
36602
36625
|
|
|
@@ -40279,8 +40302,9 @@ const publishRouteRowMove = (move) => {
|
|
|
40279
40302
|
* itself; a counter, a title or a breadcrumb around the box reads it first.
|
|
40280
40303
|
*
|
|
40281
40304
|
* It goes back to `null` as soon as the hand has let go and the row moves. Nothing
|
|
40282
|
-
* says when the fingers leave a trackpad: there it
|
|
40283
|
-
* 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.
|
|
40284
40308
|
*
|
|
40285
40309
|
* One for the document. `route` says which row, for a page holding several boxes.
|
|
40286
40310
|
*/
|
|
@@ -40298,6 +40322,13 @@ const holdPage = (page, headingPage) => {
|
|
|
40298
40322
|
}
|
|
40299
40323
|
};
|
|
40300
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
|
+
|
|
40301
40332
|
const releaseHeldPage = () => {
|
|
40302
40333
|
routeTravelSettledPageSignal.value = null;
|
|
40303
40334
|
headingPageSignal.value = null;
|
|
@@ -40412,7 +40443,9 @@ const readRowStretch = (scroller, pages, page) => {
|
|
|
40412
40443
|
/**
|
|
40413
40444
|
* @param {Element} scroller
|
|
40414
40445
|
* @param {object} options
|
|
40415
|
-
* @param {() => void} options.onScroll - the row moved.
|
|
40446
|
+
* @param {(move: {finger: boolean}) => void} options.onScroll - the row moved.
|
|
40447
|
+
* `finger`: a touch has come since the row last stopped, so a lift will say where
|
|
40448
|
+
* it goes (`onLeftToSnap`). Without one (a trackpad, a wheel) nothing will.
|
|
40416
40449
|
* @param {(event: Event|null) => void} options.onLand - the row may have stopped.
|
|
40417
40450
|
* Read `pageAtRest` to know whether it stopped on a page. Heard more than once
|
|
40418
40451
|
* for one landing, so what it does must find nothing to do the second time.
|
|
@@ -40428,11 +40461,13 @@ const watchRowScroll = (
|
|
|
40428
40461
|
) => {
|
|
40429
40462
|
let stoppedTimeout = null;
|
|
40430
40463
|
let touching = false;
|
|
40464
|
+
let touchedSinceStop = false;
|
|
40431
40465
|
let liftedAt = null;
|
|
40432
40466
|
const landOnSilence = () => {
|
|
40433
40467
|
liftedAt = null;
|
|
40434
40468
|
// A finger holding the row still: nothing has stopped.
|
|
40435
40469
|
if (!touching) {
|
|
40470
|
+
touchedSinceStop = false;
|
|
40436
40471
|
onLand(null);
|
|
40437
40472
|
}
|
|
40438
40473
|
};
|
|
@@ -40441,7 +40476,7 @@ const watchRowScroll = (
|
|
|
40441
40476
|
stoppedTimeout = setTimeout(landOnSilence, STOPPED_SILENCE);
|
|
40442
40477
|
};
|
|
40443
40478
|
const onScrollEvent = () => {
|
|
40444
|
-
onScroll();
|
|
40479
|
+
onScroll({ finger: touchedSinceStop });
|
|
40445
40480
|
armSilence();
|
|
40446
40481
|
if (!liftedAt) {
|
|
40447
40482
|
return;
|
|
@@ -40455,11 +40490,6 @@ const watchRowScroll = (
|
|
|
40455
40490
|
liftedAt = null;
|
|
40456
40491
|
onLeftToSnap?.(moved > 0);
|
|
40457
40492
|
};
|
|
40458
|
-
const onTouchStart = () => {
|
|
40459
|
-
touching = true;
|
|
40460
|
-
liftedAt = null;
|
|
40461
|
-
clearTimeout(stoppedTimeout);
|
|
40462
|
-
};
|
|
40463
40493
|
const onTouchEnd = (touchEvent) => {
|
|
40464
40494
|
touching = touchEvent.touches.length > 0;
|
|
40465
40495
|
if (!touching) {
|
|
@@ -40467,23 +40497,38 @@ const watchRowScroll = (
|
|
|
40467
40497
|
armSilence();
|
|
40468
40498
|
}
|
|
40469
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
|
+
};
|
|
40470
40517
|
scroller.addEventListener("scroll", onScrollEvent, { passive: true });
|
|
40471
40518
|
scroller.addEventListener("scrollend", onLand);
|
|
40472
40519
|
scroller.addEventListener("touchstart", onTouchStart, { passive: true });
|
|
40473
|
-
scroller.addEventListener("touchend", onTouchEnd, { passive: true });
|
|
40474
|
-
scroller.addEventListener("touchcancel", onTouchEnd, { passive: true });
|
|
40475
40520
|
return () => {
|
|
40476
40521
|
clearTimeout(stoppedTimeout);
|
|
40477
40522
|
scroller.removeEventListener("scroll", onScrollEvent);
|
|
40478
40523
|
scroller.removeEventListener("scrollend", onLand);
|
|
40479
40524
|
scroller.removeEventListener("touchstart", onTouchStart);
|
|
40480
|
-
scroller.removeEventListener("touchend", onTouchEnd);
|
|
40481
|
-
scroller.removeEventListener("touchcancel", onTouchEnd);
|
|
40482
40525
|
};
|
|
40483
40526
|
};
|
|
40484
40527
|
|
|
40485
40528
|
installImportMetaCssBuild(import.meta);
|
|
40486
40529
|
const css$16 = /* css */`.navi_route_travel {
|
|
40530
|
+
flex-direction: column;
|
|
40531
|
+
display: flex;
|
|
40487
40532
|
position: relative;
|
|
40488
40533
|
}
|
|
40489
40534
|
|
|
@@ -40514,6 +40559,19 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
40514
40559
|
overflow: visible;
|
|
40515
40560
|
}
|
|
40516
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
|
+
|
|
40517
40575
|
& > [data-route-row-pane] {
|
|
40518
40576
|
scroll-snap-align: start;
|
|
40519
40577
|
scroll-snap-stop: always;
|
|
@@ -40535,6 +40593,15 @@ const css$16 = /* css */`.navi_route_travel {
|
|
|
40535
40593
|
overflow: clip;
|
|
40536
40594
|
}
|
|
40537
40595
|
|
|
40596
|
+
& > [data-route-row-pane] > [data-route-row-neighbour] {
|
|
40597
|
+
contain: layout paint;
|
|
40598
|
+
visibility: visible;
|
|
40599
|
+
flex-direction: column;
|
|
40600
|
+
display: flex;
|
|
40601
|
+
position: absolute;
|
|
40602
|
+
inset: 0;
|
|
40603
|
+
}
|
|
40604
|
+
|
|
40538
40605
|
& > [data-route-row-live] {
|
|
40539
40606
|
flex-direction: column;
|
|
40540
40607
|
grid-row: 1;
|
|
@@ -40567,6 +40634,7 @@ const SET_OFF_PULL = 16;
|
|
|
40567
40634
|
* routes?: Array<object|{route: object, params?: object}>,
|
|
40568
40635
|
* travelByDrag?: boolean,
|
|
40569
40636
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
40637
|
+
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
40570
40638
|
* }>}
|
|
40571
40639
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
40572
40640
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -40583,6 +40651,13 @@ const SET_OFF_PULL = 16;
|
|
|
40583
40651
|
* of the page under six entries. A tab pressed goes the way its <Link> says —
|
|
40584
40652
|
* `<Link replace>` for a row of tabs, so a press and a swipe towards the same
|
|
40585
40653
|
* neighbour write the same history.
|
|
40654
|
+
* @param {(page: {route: object, params: object|undefined}) => import("preact").ComponentChildren} [props.renderNeighbour]
|
|
40655
|
+
* - what stands in the pane of a page next to the one on screen, until the hand
|
|
40656
|
+
* has pulled far enough to bring that page: its background, its skeleton, what the
|
|
40657
|
+
* list already knows. A `<Route>` page already built shows itself. Receives
|
|
40658
|
+
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
40659
|
+
* the height of the row, as the page is. Its route does not match while it stands
|
|
40660
|
+
* there: what it reads is not asked for.
|
|
40586
40661
|
*
|
|
40587
40662
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
40588
40663
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -40598,6 +40673,7 @@ const RouteTravel = ({
|
|
|
40598
40673
|
replace: true,
|
|
40599
40674
|
routeTransition: "none"
|
|
40600
40675
|
}),
|
|
40676
|
+
renderNeighbour,
|
|
40601
40677
|
className,
|
|
40602
40678
|
children,
|
|
40603
40679
|
...rest
|
|
@@ -40658,9 +40734,7 @@ const RouteTravel = ({
|
|
|
40658
40734
|
travelling: false,
|
|
40659
40735
|
pictures: new Map(),
|
|
40660
40736
|
resumeScrollRecording: null,
|
|
40661
|
-
scrollsDocument:
|
|
40662
|
-
includeHidden: true
|
|
40663
|
-
}) === document.documentElement,
|
|
40737
|
+
scrollsDocument: rowFollowsDocument(row),
|
|
40664
40738
|
...fields
|
|
40665
40739
|
};
|
|
40666
40740
|
movementRef.current = movement;
|
|
@@ -40755,6 +40829,37 @@ const RouteTravel = ({
|
|
|
40755
40829
|
});
|
|
40756
40830
|
};
|
|
40757
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
|
+
|
|
40758
40863
|
// The row has stopped, on the page of the address: everything held for the
|
|
40759
40864
|
// movement is let go of in one go, before anything paints.
|
|
40760
40865
|
const endMovement = () => {
|
|
@@ -40765,6 +40870,7 @@ const RouteTravel = ({
|
|
|
40765
40870
|
}
|
|
40766
40871
|
movementRef.current = null;
|
|
40767
40872
|
row.removeAttribute("data-moving");
|
|
40873
|
+
row.removeAttribute("data-route-row-taken");
|
|
40768
40874
|
publishRouteRowMove(null);
|
|
40769
40875
|
releaseHeldPage();
|
|
40770
40876
|
if (liveRef.current) {
|
|
@@ -40832,16 +40938,23 @@ const RouteTravel = ({
|
|
|
40832
40938
|
}
|
|
40833
40939
|
return;
|
|
40834
40940
|
}
|
|
40835
|
-
|
|
40941
|
+
const movementBefore = movementRef.current;
|
|
40942
|
+
const movement = beginMovement({
|
|
40836
40943
|
travelling: true
|
|
40837
40944
|
});
|
|
40838
|
-
row.scrollTo({
|
|
40839
|
-
left: pane.offsetLeft,
|
|
40840
|
-
behavior: "smooth"
|
|
40841
|
-
});
|
|
40842
40945
|
// In this very commit: the tab pressed already wears its bar, and the first
|
|
40843
40946
|
// scroll event is a frame away.
|
|
40844
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
|
+
}
|
|
40845
40958
|
});
|
|
40846
40959
|
|
|
40847
40960
|
// Somebody else is about to move the address to another page of a window: its
|
|
@@ -40873,14 +40986,17 @@ const RouteTravel = ({
|
|
|
40873
40986
|
}, [windowed]);
|
|
40874
40987
|
useLayoutEffect(() => {
|
|
40875
40988
|
const row = rowRef.current;
|
|
40876
|
-
const
|
|
40989
|
+
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
40990
|
+
const onScroll = ({
|
|
40991
|
+
finger
|
|
40992
|
+
}) => {
|
|
40877
40993
|
const {
|
|
40878
40994
|
pages,
|
|
40879
40995
|
currentIndex,
|
|
40880
40996
|
travelByDrag
|
|
40881
40997
|
} = latestRef.current;
|
|
40882
40998
|
const panes = panesOf(row);
|
|
40883
|
-
if (panes.length < 2) {
|
|
40999
|
+
if (panes.length < 2 || isClipped()) {
|
|
40884
41000
|
return;
|
|
40885
41001
|
}
|
|
40886
41002
|
const moving = movementRef.current;
|
|
@@ -40889,11 +41005,24 @@ const RouteTravel = ({
|
|
|
40889
41005
|
// Written by this box, at rest: nobody is moving anything.
|
|
40890
41006
|
return;
|
|
40891
41007
|
}
|
|
40892
|
-
const movement = moving || beginMovement({
|
|
41008
|
+
const movement = moving || beginMovement({
|
|
41009
|
+
byHand: true
|
|
41010
|
+
});
|
|
40893
41011
|
sayRowMoves();
|
|
40894
41012
|
if (movement.travelling || !travelByDrag || !pane) {
|
|
40895
41013
|
return;
|
|
40896
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
|
+
}
|
|
40897
41026
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
40898
41027
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
40899
41028
|
return;
|
|
@@ -40946,7 +41075,7 @@ const RouteTravel = ({
|
|
|
40946
41075
|
};
|
|
40947
41076
|
const onLand = () => {
|
|
40948
41077
|
const movement = movementRef.current;
|
|
40949
|
-
if (!movement) {
|
|
41078
|
+
if (!movement || isClipped()) {
|
|
40950
41079
|
return;
|
|
40951
41080
|
}
|
|
40952
41081
|
const panes = panesOf(row);
|
|
@@ -40975,8 +41104,48 @@ const RouteTravel = ({
|
|
|
40975
41104
|
onLand,
|
|
40976
41105
|
onLeftToSnap
|
|
40977
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
|
+
});
|
|
40978
41142
|
return () => {
|
|
40979
41143
|
stopWatching();
|
|
41144
|
+
row.removeEventListener("touchstart", onHandBack);
|
|
41145
|
+
row.removeEventListener("wheel", onHandBack);
|
|
41146
|
+
document.removeEventListener("click", onPress, {
|
|
41147
|
+
capture: true
|
|
41148
|
+
});
|
|
40980
41149
|
movementRef.current?.resumeScrollRecording?.();
|
|
40981
41150
|
movementRef.current = null;
|
|
40982
41151
|
publishRouteRowMove(null);
|
|
@@ -40996,6 +41165,30 @@ const RouteTravel = ({
|
|
|
40996
41165
|
Promise.resolve().then(endMovement);
|
|
40997
41166
|
}
|
|
40998
41167
|
}, [currentIndex]);
|
|
41168
|
+
|
|
41169
|
+
// A neighbour shows from the first frame the compositor moves the row, before
|
|
41170
|
+
// any scroll event: a finger landing is the last word before that.
|
|
41171
|
+
const paintsNeighbours = Boolean(renderNeighbour);
|
|
41172
|
+
useLayoutEffect(() => {
|
|
41173
|
+
if (!paintsNeighbours) {
|
|
41174
|
+
return undefined;
|
|
41175
|
+
}
|
|
41176
|
+
const row = rowRef.current;
|
|
41177
|
+
const onTouchStart = () => {
|
|
41178
|
+
if (!movementRef.current && rowFollowsDocument(row)) {
|
|
41179
|
+
row.style.setProperty("--navi-route-row-pin", `${pinOf(row)}px`);
|
|
41180
|
+
}
|
|
41181
|
+
};
|
|
41182
|
+
row.addEventListener("touchstart", onTouchStart, {
|
|
41183
|
+
passive: true
|
|
41184
|
+
});
|
|
41185
|
+
return () => {
|
|
41186
|
+
row.removeEventListener("touchstart", onTouchStart);
|
|
41187
|
+
};
|
|
41188
|
+
}, [paintsNeighbours]);
|
|
41189
|
+
const rowContext = useMemo(() => ({
|
|
41190
|
+
renderNeighbour
|
|
41191
|
+
}), [renderNeighbour]);
|
|
40999
41192
|
const rowKind = pages.length < 2 && windowed ? "still" : travelByDrag ? "" : "code";
|
|
41000
41193
|
return jsx("div", {
|
|
41001
41194
|
...rest,
|
|
@@ -41008,12 +41201,20 @@ const RouteTravel = ({
|
|
|
41008
41201
|
,
|
|
41009
41202
|
"data-travel-by-drag": travelByDrag && rowKind !== "still" ? "x" : undefined,
|
|
41010
41203
|
children: windowed ? jsxs(Fragment, {
|
|
41011
|
-
children: [pages.map((page, index) =>
|
|
41012
|
-
|
|
41013
|
-
|
|
41014
|
-
|
|
41015
|
-
|
|
41016
|
-
|
|
41204
|
+
children: [pages.map((page, index) => {
|
|
41205
|
+
const holdsNeighbour = paintsNeighbours && isNeighbourPane(index, liveIndex, movementRef.current);
|
|
41206
|
+
return jsx("div", {
|
|
41207
|
+
"data-route-row-pane": "",
|
|
41208
|
+
inert: holdsNeighbour,
|
|
41209
|
+
style: {
|
|
41210
|
+
gridColumn: index + 1
|
|
41211
|
+
},
|
|
41212
|
+
children: holdsNeighbour && jsx(RouteRowNeighbour, {
|
|
41213
|
+
renderNeighbour: renderNeighbour,
|
|
41214
|
+
page: page
|
|
41215
|
+
})
|
|
41216
|
+
}, index);
|
|
41217
|
+
}), jsx("div", {
|
|
41017
41218
|
ref: liveRef,
|
|
41018
41219
|
"data-route-row-live": "",
|
|
41019
41220
|
style: {
|
|
@@ -41025,13 +41226,25 @@ const RouteTravel = ({
|
|
|
41025
41226
|
"data-route-row-pictures": ""
|
|
41026
41227
|
})]
|
|
41027
41228
|
}) : jsx(RouteRowContext.Provider, {
|
|
41028
|
-
value:
|
|
41229
|
+
value: rowContext,
|
|
41029
41230
|
children: children
|
|
41030
41231
|
})
|
|
41031
41232
|
})
|
|
41032
41233
|
});
|
|
41033
41234
|
};
|
|
41034
41235
|
|
|
41236
|
+
// The panes on each side of the live page. Not one holding a picture of the page
|
|
41237
|
+
// left there: a page with no background of its own would show it through.
|
|
41238
|
+
const isNeighbourPane = (index, liveIndex, movement) => {
|
|
41239
|
+
if (index !== liveIndex - 1 && index !== liveIndex + 1) {
|
|
41240
|
+
return false;
|
|
41241
|
+
}
|
|
41242
|
+
return !movement || !movement.pictures.has(index);
|
|
41243
|
+
};
|
|
41244
|
+
const rowFollowsDocument = row => getScrollContainer(row.parentElement, {
|
|
41245
|
+
includeHidden: true
|
|
41246
|
+
}) === document.documentElement;
|
|
41247
|
+
|
|
41035
41248
|
// How far down the row the reader is looking, for a page brought in: a page on
|
|
41036
41249
|
// screen scrolls with the document, and one arriving has to start from there.
|
|
41037
41250
|
const pinOf = row => {
|
|
@@ -41060,6 +41273,15 @@ const copyScrollPositions = (from, to) => {
|
|
|
41060
41273
|
// A page of the row: a route, and the params that say which of its tabs when
|
|
41061
41274
|
// several of them share it. Written as a bare route by a caller whose tabs are
|
|
41062
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
|
+
};
|
|
41063
41285
|
const normalizePage = page => page.isRoute ? {
|
|
41064
41286
|
route: page,
|
|
41065
41287
|
params: undefined
|
|
@@ -56513,6 +56735,10 @@ const SLIDE_TOWARD_ATTRIBUTE = "data-slide-travel-toward";
|
|
|
56513
56735
|
// one: a hand on a row the browser scrolls can carry it past several slides, and
|
|
56514
56736
|
// what follows it goes on between the two it is standing between.
|
|
56515
56737
|
const SLIDE_BASE_ATTRIBUTE = "data-slide-travel-base";
|
|
56738
|
+
// The slide the row is on its way to while the current one has not changed yet,
|
|
56739
|
+
// because that only happens at rest: nothing is rendered and no address is written
|
|
56740
|
+
// under a snap, and what says where one is should not wait for it.
|
|
56741
|
+
const SLIDE_HEADING_ATTRIBUTE = "data-slide-heading";
|
|
56516
56742
|
// Every way out that would DO something right now, in the words the commands
|
|
56517
56743
|
// use: a direction ("left", "right", "up", "down") when there is a slide that
|
|
56518
56744
|
// way, plus "first" and "last" when one is not already standing at that end of
|
|
@@ -56547,9 +56773,17 @@ const wordsOf = (element, attribute) => {
|
|
|
56547
56773
|
return value ? value.split(" ") : NO_WAYS;
|
|
56548
56774
|
};
|
|
56549
56775
|
|
|
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.
|
|
56779
|
+
const readSlideOnScreen = (element) =>
|
|
56780
|
+
element.getAttribute(SLIDE_HEADING_ATTRIBUTE) ??
|
|
56781
|
+
element.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ??
|
|
56782
|
+
undefined;
|
|
56783
|
+
|
|
56550
56784
|
const readSlideContainerState = (element) => ({
|
|
56551
56785
|
known: true,
|
|
56552
|
-
current: element
|
|
56786
|
+
current: readSlideOnScreen(element),
|
|
56553
56787
|
toward: element.getAttribute(SLIDE_TOWARD_ATTRIBUTE) ?? undefined,
|
|
56554
56788
|
// In DOM order — which is the order of the WALK for a line, and not
|
|
56555
56789
|
// necessarily for a map: there the order is the one the areas are written in
|
|
@@ -56587,11 +56821,13 @@ const sameSlideContainerState = (a, b) =>
|
|
|
56587
56821
|
* can: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
|
|
56588
56822
|
* held: (wayOut: "left"|"right"|"up"|"down"|"first"|"last") => boolean,
|
|
56589
56823
|
* }} where the box stands. `current` is the slide on screen — the one being
|
|
56590
|
-
* travelled TO while a travel plays,
|
|
56824
|
+
* travelled TO while a travel plays, and the one the row is going to once a hand
|
|
56825
|
+
* has let go, because that is what one is looking at. It can come before the
|
|
56826
|
+
* slide's own signal and `onCurrentChange`, which wait for the row to rest.
|
|
56591
56827
|
* `toward` is the other slide in the frame while the picture is between two,
|
|
56592
|
-
* and nothing at rest. `can` is "asking for it would do something",
|
|
56593
|
-
* "it is there and this slide says no" — a way out is dead
|
|
56594
|
-
* only the second is worth a word to 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.
|
|
56595
56831
|
* `known` is false until the box has been read: no box was named and none is
|
|
56596
56832
|
* above, the id names nothing, or — for one commit — this mounted before the
|
|
56597
56833
|
* box had painted. Until then `can` answers YES, because it is the answer that
|
|
@@ -57445,7 +57681,7 @@ const Nav = ({
|
|
|
57445
57681
|
}
|
|
57446
57682
|
slideContainerElementRef.current = containerElement;
|
|
57447
57683
|
const readContainer = () => {
|
|
57448
|
-
setCurrentSlideArea(containerElement
|
|
57684
|
+
setCurrentSlideArea(readSlideOnScreen(containerElement));
|
|
57449
57685
|
paintIndicatorGeometryRef.current();
|
|
57450
57686
|
scheduleTabScrollSyncRef.current();
|
|
57451
57687
|
};
|
|
@@ -67563,6 +67799,10 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
67563
67799
|
overflow-y: auto;
|
|
67564
67800
|
}
|
|
67565
67801
|
|
|
67802
|
+
&[data-slide-row-taken] {
|
|
67803
|
+
overflow: clip;
|
|
67804
|
+
}
|
|
67805
|
+
|
|
67566
67806
|
&:focus {
|
|
67567
67807
|
outline: none;
|
|
67568
67808
|
}
|
|
@@ -67614,6 +67854,77 @@ const css$I = /* css */`@property --slide-travel-progress {
|
|
|
67614
67854
|
}
|
|
67615
67855
|
`;
|
|
67616
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
|
+
|
|
67617
67928
|
// How much of a travel is left to make, as a fraction of the travel that was
|
|
67618
67929
|
// asked for: 1 for a move starting from rest, less for one picked up while the
|
|
67619
67930
|
// track was already moving. Everything is measured in px against the track's
|
|
@@ -67630,6 +67941,19 @@ const ratioOfOneTravel = (track, from, to, targetBefore) => {
|
|
|
67630
67941
|
const ratio = left / asked;
|
|
67631
67942
|
return ratio > 1 ? 1 : ratio;
|
|
67632
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
|
+
};
|
|
67633
67957
|
|
|
67634
67958
|
// A translate ("-100% 0%", "-260px 0px", "none") as two numbers of pixels.
|
|
67635
67959
|
// Percentages are the size of the box, which is what a translate resolves them
|
|
@@ -68207,6 +68531,10 @@ const SlideContainer = ({
|
|
|
68207
68531
|
// The slide a hand has just landed on, for the render it asks for: it is
|
|
68208
68532
|
// already there, so there is nothing to play.
|
|
68209
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);
|
|
68210
68538
|
// Where the box rests on the row, and what the row starts from: a slide that
|
|
68211
68539
|
// holds on to the user ends the row on that side, and a scroll range starts at
|
|
68212
68540
|
// its first reachable slide (see writeRowAtRest).
|
|
@@ -68444,6 +68772,26 @@ const SlideContainer = ({
|
|
|
68444
68772
|
const paintCurrentArea = area => {
|
|
68445
68773
|
containerRef.current?.setAttribute(SLIDE_CURRENT_ATTRIBUTE, area);
|
|
68446
68774
|
};
|
|
68775
|
+
|
|
68776
|
+
// Where the row is going, for as long as the current slide has not changed:
|
|
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.
|
|
68780
|
+
const writeHeading = headingElement => {
|
|
68781
|
+
const containerEl = containerRef.current;
|
|
68782
|
+
const area = headingElement ? readArea(headingElement) : null;
|
|
68783
|
+
// A window does not change slide, it rolls: it has nowhere to be heading.
|
|
68784
|
+
const heading = loop || area === null || area === containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE) ? null : area;
|
|
68785
|
+
if (heading === containerEl.getAttribute(SLIDE_HEADING_ATTRIBUTE)) {
|
|
68786
|
+
return;
|
|
68787
|
+
}
|
|
68788
|
+
if (heading === null) {
|
|
68789
|
+
containerEl.removeAttribute(SLIDE_HEADING_ATTRIBUTE);
|
|
68790
|
+
} else {
|
|
68791
|
+
containerEl.setAttribute(SLIDE_HEADING_ATTRIBUTE, heading);
|
|
68792
|
+
}
|
|
68793
|
+
paintWays();
|
|
68794
|
+
};
|
|
68447
68795
|
const markAnswered = area => {
|
|
68448
68796
|
const order = readMap().slideElements.map(readArea);
|
|
68449
68797
|
const rank = order.indexOf(area);
|
|
@@ -68609,6 +68957,7 @@ const SlideContainer = ({
|
|
|
68609
68957
|
offsetRef.current = "0% 0%";
|
|
68610
68958
|
track.style.setProperty("--slide-container-offset", "0% 0%");
|
|
68611
68959
|
containerRef.current.removeAttribute("data-slide-travelling");
|
|
68960
|
+
containerRef.current.removeAttribute("data-slide-row-taken");
|
|
68612
68961
|
sendRowToRest(currentElement);
|
|
68613
68962
|
renderNear(currentArea, {
|
|
68614
68963
|
alongside: true
|
|
@@ -68649,6 +68998,10 @@ const SlideContainer = ({
|
|
|
68649
68998
|
slideElements[0];
|
|
68650
68999
|
const currentArea = readArea(currentElement);
|
|
68651
69000
|
paintCurrentArea(currentArea);
|
|
69001
|
+
// In the commit the slide changes in, so nothing reads the slide left in between.
|
|
69002
|
+
if (!handMovingRef.current) {
|
|
69003
|
+
writeHeading(null);
|
|
69004
|
+
}
|
|
68652
69005
|
const realPlaceOf = area => placeOf.get(area) || {
|
|
68653
69006
|
x: 0,
|
|
68654
69007
|
y: 0
|
|
@@ -68679,8 +69032,26 @@ const SlideContainer = ({
|
|
|
68679
69032
|
// between the picture and the slide arriving, on the axis it walks.
|
|
68680
69033
|
let travelStep = null;
|
|
68681
69034
|
const drawnArea = stage ? stage.area : drawnAreaRef.current;
|
|
68682
|
-
const
|
|
68683
|
-
|
|
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;
|
|
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) {
|
|
68684
69055
|
// Where the slide being left IS — the place the stage gave it if a travel
|
|
68685
69056
|
// was already playing (a press landing mid-travel departs from where the
|
|
68686
69057
|
// eye is, not from the map), its own place otherwise.
|
|
@@ -68765,9 +69136,16 @@ const SlideContainer = ({
|
|
|
68765
69136
|
}
|
|
68766
69137
|
const track = trackRef.current;
|
|
68767
69138
|
containerRef.current.toggleAttribute("data-slide-travelling", Boolean(stage));
|
|
68768
|
-
|
|
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.
|
|
68769
69144
|
const rowPlace = onRow(currentPlace);
|
|
68770
|
-
const restPlace =
|
|
69145
|
+
const restPlace = containerRef.current.hasAttribute("data-slide-row-taken") ? {
|
|
69146
|
+
x: 0,
|
|
69147
|
+
y: 0
|
|
69148
|
+
} : restPlaceRef.current;
|
|
68771
69149
|
const offset = `${(restPlace.x - rowPlace.x) * 100}% ${(restPlace.y - rowPlace.y) * 100}%`;
|
|
68772
69150
|
// The travel that was ASKED for is still one box, whatever is left of it to
|
|
68773
69151
|
// cover: what is left takes what is left of the duration.
|
|
@@ -68790,6 +69168,9 @@ const SlideContainer = ({
|
|
|
68790
69168
|
const onScreenPx = trackOffsetPx(track, containerRef.current);
|
|
68791
69169
|
offsetOnScreen = `${onScreenPx.x}px ${onScreenPx.y}px`;
|
|
68792
69170
|
}
|
|
69171
|
+
if (overRow) {
|
|
69172
|
+
offsetOnScreen = rowStand.offset;
|
|
69173
|
+
}
|
|
68793
69174
|
const offsetBefore = offsetOnScreen ?? offsetRef.current;
|
|
68794
69175
|
offsetRef.current = offset;
|
|
68795
69176
|
track.style.setProperty("--slide-container-offset", offset);
|
|
@@ -68808,10 +69189,10 @@ const SlideContainer = ({
|
|
|
68808
69189
|
// short distance for a full duration. One box in `duration` is the pace;
|
|
68809
69190
|
// this only ever shortens it (a longer travel is not made slower, which
|
|
68810
69191
|
// would make a two-box move drag).
|
|
68811
|
-
const travelRatio = ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
69192
|
+
const travelRatio = overRow ? ratioOfOneBox(track, offsetBefore, offset) : ratioOfOneTravel(track, offsetBefore, offset, offsetTargetBefore);
|
|
68812
69193
|
// Already moving, so there is no ease-in to play: it would stall the track
|
|
68813
69194
|
// for an instant right where the eye is following it.
|
|
68814
|
-
const easing = travelInFlight ? "ease-out" : "ease";
|
|
69195
|
+
const easing = travelInFlight || overRow ? "ease-out" : "ease";
|
|
68815
69196
|
// Cancelled rather than layered: two animations on the same property
|
|
68816
69197
|
// would blend, and what one sees then is neither of the two moves.
|
|
68817
69198
|
trackAnimationRef.current?.cancel();
|
|
@@ -68819,15 +69200,21 @@ const SlideContainer = ({
|
|
|
68819
69200
|
translate: offsetBefore
|
|
68820
69201
|
}, {
|
|
68821
69202
|
translate: offset
|
|
68822
|
-
}],
|
|
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
|
+
{
|
|
68823
69208
|
duration: durationMs * travelRatio,
|
|
68824
|
-
easing
|
|
69209
|
+
easing,
|
|
69210
|
+
fill: "backwards"
|
|
68825
69211
|
});
|
|
68826
69212
|
// The trait travels with the slides, from a whole box away.
|
|
68827
|
-
const
|
|
69213
|
+
const stepProgress = travelStep ? travelStep.x || travelStep.y : 0;
|
|
69214
|
+
const progressFrom = overRow ? overRow.progress : stepProgress;
|
|
68828
69215
|
// The slide the picture leans on for the length of it is the one being
|
|
68829
69216
|
// LEFT: it is the second one in the frame until the travel is over.
|
|
68830
|
-
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, drawnArea);
|
|
69217
|
+
animateTravelProgress(progressFrom, durationMs * travelRatio, easing, overRow ? overRow.leaningArea : drawnArea);
|
|
68831
69218
|
// Presses still waiting behind this one: it is already late, so it is
|
|
68832
69219
|
// sent home at once rather than played out at the pace of someone who
|
|
68833
69220
|
// has stopped pressing. Someone pressing → four times is asking to be
|
|
@@ -68848,6 +69235,17 @@ const SlideContainer = ({
|
|
|
68848
69235
|
// thing it was standing for is over.
|
|
68849
69236
|
settleTravel();
|
|
68850
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
|
+
}
|
|
68851
69249
|
// A window waiting for its travel to be over (see goToArea's own loop
|
|
68852
69250
|
// branch): the animation says when, and says it about the move that just
|
|
68853
69251
|
// started rather than about a duration counted out beside it.
|
|
@@ -68911,9 +69309,9 @@ const SlideContainer = ({
|
|
|
68911
69309
|
sendRowToRest(currentElement);
|
|
68912
69310
|
}
|
|
68913
69311
|
// Said last, and after data-current has been written: what a travel would do
|
|
68914
|
-
// is read off the map from the slide
|
|
68915
|
-
//
|
|
68916
|
-
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();
|
|
68917
69315
|
// …and once everything this commit had to write is written, the box says so
|
|
68918
69316
|
// — a single announcement for a render that may have changed several of the
|
|
68919
69317
|
// facts at once.
|
|
@@ -68960,14 +69358,18 @@ const SlideContainer = ({
|
|
|
68960
69358
|
placeOf
|
|
68961
69359
|
} = readMap();
|
|
68962
69360
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current")) || slideElements[0];
|
|
68963
|
-
if (!area
|
|
69361
|
+
if (!area) {
|
|
68964
69362
|
return false;
|
|
68965
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;
|
|
68966
69368
|
if (forward === undefined) {
|
|
68967
69369
|
// Asked for by name (--navi-go-to-slide, --navi-back) rather than by a
|
|
68968
69370
|
// direction: which way it is is still a fact, read off the map, and it is
|
|
68969
69371
|
// what says which of the two locks below applies.
|
|
68970
|
-
const from = placeOf.get(
|
|
69372
|
+
const from = placeOf.get(areaLeft) || {
|
|
68971
69373
|
x: 0,
|
|
68972
69374
|
y: 0
|
|
68973
69375
|
};
|
|
@@ -68985,9 +69387,18 @@ const SlideContainer = ({
|
|
|
68985
69387
|
// one departure the forward hold does not apply to. Said here rather than
|
|
68986
69388
|
// by dropping the attribute, which stays the render's to write — a hold
|
|
68987
69389
|
// meant to stay (an explicit preventNavNext) is still there on the way back.
|
|
68988
|
-
if (forward ? !released &&
|
|
69390
|
+
if (area !== areaLeft && (forward ? !released && elementLeft.hasAttribute("data-prevent-nav-next") : elementLeft.hasAttribute("data-prevent-nav-previous"))) {
|
|
68989
69391
|
return false;
|
|
68990
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
|
+
}
|
|
68991
69402
|
// The focus moves here, while the event that asked for it is still in hand:
|
|
68992
69403
|
// who pressed what, and whether they were on the keyboard, is knowable now
|
|
68993
69404
|
// and gone a render later. The slide arriving is made reachable on the spot
|
|
@@ -69369,7 +69780,7 @@ const SlideContainer = ({
|
|
|
69369
69780
|
const move = (dx, dy, event, {
|
|
69370
69781
|
value,
|
|
69371
69782
|
released
|
|
69372
|
-
} = {}) => goToArea(areaTowards(dx, dy), {
|
|
69783
|
+
} = {}) => goToArea(areaTowards(dx, dy, readSlideOnScreen(containerRef.current)), {
|
|
69373
69784
|
forward: dx > 0 || dy > 0,
|
|
69374
69785
|
event,
|
|
69375
69786
|
value,
|
|
@@ -69447,11 +69858,13 @@ const SlideContainer = ({
|
|
|
69447
69858
|
// Where a hand has the row, in the terms of --slide-travel-progress: from the
|
|
69448
69859
|
// current slide while it stands next to it, and past that between the two it
|
|
69449
69860
|
// stands between, as far as the hand carries it.
|
|
69450
|
-
const paintNativeRow = (
|
|
69451
|
-
|
|
69861
|
+
const paintNativeRow = ({
|
|
69862
|
+
finger
|
|
69863
|
+
}) => {
|
|
69864
|
+
const containerEl = containerRef.current;
|
|
69865
|
+
if (trackAnimationRef.current?.playState === "running" || containerEl.hasAttribute("data-slide-row-taken")) {
|
|
69452
69866
|
return;
|
|
69453
69867
|
}
|
|
69454
|
-
const containerEl = containerRef.current;
|
|
69455
69868
|
const slideElements = Array.from(trackRef.current.children);
|
|
69456
69869
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69457
69870
|
if (!currentElement) {
|
|
@@ -69459,6 +69872,9 @@ const SlideContainer = ({
|
|
|
69459
69872
|
}
|
|
69460
69873
|
if (restsOn(containerEl, currentElement)) {
|
|
69461
69874
|
if (handMovingRef.current) {
|
|
69875
|
+
if (!finger) {
|
|
69876
|
+
writeHeading(null);
|
|
69877
|
+
}
|
|
69462
69878
|
paintTravelProgress();
|
|
69463
69879
|
}
|
|
69464
69880
|
return;
|
|
@@ -69479,6 +69895,12 @@ const SlideContainer = ({
|
|
|
69479
69895
|
}
|
|
69480
69896
|
element.style.setProperty("--slide-travel-progress", forward ? -fraction : fraction);
|
|
69481
69897
|
}
|
|
69898
|
+
// Nothing will say when a trackpad's fingers leave, and a long stroke would
|
|
69899
|
+
// keep the first tab lit for the whole trip: the nearest slide answers. A finger
|
|
69900
|
+
// has a lift to wait for (headNativeRow), and until then the slide held stands.
|
|
69901
|
+
if (!finger) {
|
|
69902
|
+
writeHeading(fraction >= 0.5 && toward ? toward : base);
|
|
69903
|
+
}
|
|
69482
69904
|
paintTravelToward(toward ? readArea(toward) : null);
|
|
69483
69905
|
// What the hand reaches ahead of the neighbours built at rest is asked for on
|
|
69484
69906
|
// the way: an empty slide goes by otherwise.
|
|
@@ -69486,6 +69908,28 @@ const SlideContainer = ({
|
|
|
69486
69908
|
alongside: true
|
|
69487
69909
|
});
|
|
69488
69910
|
};
|
|
69911
|
+
|
|
69912
|
+
// A finger has lifted and the row moves: on past the slide it stands after, or
|
|
69913
|
+
// back to it. Already standing on one, it has arrived, whichever way it came.
|
|
69914
|
+
const headNativeRow = movedForward => {
|
|
69915
|
+
if (!handMovingRef.current || trackAnimationRef.current?.playState === "running") {
|
|
69916
|
+
return;
|
|
69917
|
+
}
|
|
69918
|
+
const slideElements = Array.from(trackRef.current.children);
|
|
69919
|
+
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69920
|
+
if (!currentElement) {
|
|
69921
|
+
return;
|
|
69922
|
+
}
|
|
69923
|
+
const {
|
|
69924
|
+
base,
|
|
69925
|
+
toward,
|
|
69926
|
+
fraction,
|
|
69927
|
+
forward
|
|
69928
|
+
} = readRowStretch(containerRef.current, slideElements, currentElement);
|
|
69929
|
+
const goesOn = fraction > 0 && movedForward === forward;
|
|
69930
|
+
writeHeading(goesOn && toward ? toward : base);
|
|
69931
|
+
announceState();
|
|
69932
|
+
};
|
|
69489
69933
|
const landNativeRow = scrollEndEvent => {
|
|
69490
69934
|
if (!handMovingRef.current) {
|
|
69491
69935
|
return;
|
|
@@ -69500,13 +69944,15 @@ const SlideContainer = ({
|
|
|
69500
69944
|
return;
|
|
69501
69945
|
}
|
|
69502
69946
|
handMovingRef.current = false;
|
|
69503
|
-
paintTravelProgress();
|
|
69504
69947
|
const area = readArea(landedElement);
|
|
69505
69948
|
const currentArea = containerEl.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
69506
69949
|
if (area === currentArea) {
|
|
69950
|
+
writeHeading(null);
|
|
69951
|
+
paintTravelProgress();
|
|
69507
69952
|
settleTravel();
|
|
69508
69953
|
return;
|
|
69509
69954
|
}
|
|
69955
|
+
paintTravelProgress();
|
|
69510
69956
|
const event = scrollEndEvent ?? new Event("scrollend");
|
|
69511
69957
|
if (loop) {
|
|
69512
69958
|
// A window does not change slide, it rolls: back at rest in the render the
|
|
@@ -69537,6 +69983,8 @@ const SlideContainer = ({
|
|
|
69537
69983
|
}
|
|
69538
69984
|
// Refused: the row is put back in view of the slide that stays.
|
|
69539
69985
|
handLandedRef.current = null;
|
|
69986
|
+
writeHeading(null);
|
|
69987
|
+
announceState();
|
|
69540
69988
|
const currentElement = slideElements.find(slideElement => slideElement.hasAttribute("data-current"));
|
|
69541
69989
|
containerEl.scrollTo({
|
|
69542
69990
|
left: currentElement.offsetLeft,
|
|
@@ -69550,11 +69998,13 @@ const SlideContainer = ({
|
|
|
69550
69998
|
const nativeRowHandlersRef = useRef(null);
|
|
69551
69999
|
nativeRowHandlersRef.current = {
|
|
69552
70000
|
paintNativeRow,
|
|
70001
|
+
headNativeRow,
|
|
69553
70002
|
landNativeRow
|
|
69554
70003
|
};
|
|
69555
70004
|
useLayoutEffect(() => {
|
|
69556
70005
|
return watchRowScroll(containerRef.current, {
|
|
69557
|
-
onScroll:
|
|
70006
|
+
onScroll: move => nativeRowHandlersRef.current.paintNativeRow(move),
|
|
70007
|
+
onLeftToSnap: forward => nativeRowHandlersRef.current.headNativeRow(forward),
|
|
69558
70008
|
onLand: event => nativeRowHandlersRef.current.landNativeRow(event)
|
|
69559
70009
|
});
|
|
69560
70010
|
}, []);
|
|
@@ -69632,13 +70082,19 @@ const SlideContainer = ({
|
|
|
69632
70082
|
// ([data-slide-ways~="right"]), or through useSlideContainer, which watches
|
|
69633
70083
|
// them. Nothing is told, everything is read: it is what lets them sit
|
|
69634
70084
|
// anywhere, and what lets a container nobody drives still be followed.
|
|
69635
|
-
const paintWays =
|
|
70085
|
+
const paintWays = () => {
|
|
69636
70086
|
const ways = [];
|
|
69637
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);
|
|
69638
70094
|
// Whether the slide being left says no, this way. The one gate again, read
|
|
69639
70095
|
// exactly as goToArea reads it at the moment of the travel — so what is
|
|
69640
70096
|
// published here and what would happen cannot disagree.
|
|
69641
|
-
const holdsTowards = forward =>
|
|
70097
|
+
const holdsTowards = forward => elementOnScreen?.hasAttribute(forward ? "data-prevent-nav-next" : "data-prevent-nav-previous");
|
|
69642
70098
|
const say = (name, offered, forward) => {
|
|
69643
70099
|
if (!offered) {
|
|
69644
70100
|
return;
|
|
@@ -69655,18 +70111,14 @@ const SlideContainer = ({
|
|
|
69655
70111
|
// would be saying it about every row on the page.
|
|
69656
70112
|
continue;
|
|
69657
70113
|
}
|
|
69658
|
-
say(direction, Boolean(areaTowards(dx, dy)), dx > 0 || dy > 0);
|
|
70114
|
+
say(direction, Boolean(areaTowards(dx, dy, areaOnScreen)), dx > 0 || dy > 0);
|
|
69659
70115
|
}
|
|
69660
70116
|
// The two ends are ways out like the others — "all the way that way", said
|
|
69661
70117
|
// by the map's own order rather than by a direction (see goToEnd, and
|
|
69662
70118
|
// SlideContainer.First / .Last). Offered while one is not already standing
|
|
69663
70119
|
// there, and held by the same lock the direction they lie in would be.
|
|
69664
|
-
|
|
69665
|
-
|
|
69666
|
-
} = readMap();
|
|
69667
|
-
const currentArea = currentElement ? readArea(currentElement) : undefined;
|
|
69668
|
-
say("first", order.length > 0 && order[0] !== currentArea, false);
|
|
69669
|
-
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);
|
|
69670
70122
|
for (const element of travelPainters()) {
|
|
69671
70123
|
if (ways.length) {
|
|
69672
70124
|
element.setAttribute(SLIDE_WAYS_ATTRIBUTE, ways.join(" "));
|
|
@@ -69759,7 +70211,8 @@ const SlideContainer = ({
|
|
|
69759
70211
|
"--slide-travel-progress": 0
|
|
69760
70212
|
}], {
|
|
69761
70213
|
duration: durationMs,
|
|
69762
|
-
easing
|
|
70214
|
+
easing,
|
|
70215
|
+
fill: "backwards"
|
|
69763
70216
|
}));
|
|
69764
70217
|
progressAnimationsRef.current[0].finished.then(() => {
|
|
69765
70218
|
progressAnimationsRef.current = [];
|
|
@@ -105704,7 +106157,9 @@ const StepList = ({
|
|
|
105704
106157
|
const read = () => {
|
|
105705
106158
|
const currentArea = containerElement.getAttribute(SLIDE_CURRENT_ATTRIBUTE);
|
|
105706
106159
|
const towardArea = containerElement.getAttribute(SLIDE_TOWARD_ATTRIBUTE);
|
|
105707
|
-
|
|
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));
|
|
105708
106163
|
const currentIdx = currentArea === null ? -1 : indexOf(currentArea);
|
|
105709
106164
|
if (currentIdx === -1) {
|
|
105710
106165
|
// A slide no step names (a confirmation screen): the halo is not
|