@jsenv/navi 0.30.8 → 0.30.10
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 +168 -21
- package/dist/dev/jsenv_navi.js.map +5 -5
- package/dist/jsenv_navi.js +168 -21
- package/dist/jsenv_navi.js.map +5 -5
- package/docs/drag_to_travel.md +3 -1
- package/docs/dynamic_import.md +1 -0
- package/docs/navigation.md +20 -0
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -8425,17 +8425,35 @@ const route = (pattern, options = {}) => {
|
|
|
8425
8425
|
return `route "${cleanPattern}"`;
|
|
8426
8426
|
},
|
|
8427
8427
|
};
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8428
|
+
/**
|
|
8429
|
+
* Asks ahead of the arrival for what this route's actions will ask: the
|
|
8430
|
+
* instances the arrival promotes to runs (see prerunAhead in route_action.js).
|
|
8431
|
+
*
|
|
8432
|
+
* Without params, only what asks nothing of the address: the page's code, a
|
|
8433
|
+
* read without params — what a link preloads, since it has no state to read
|
|
8434
|
+
* the params of. Given params, `{}` included, the address `buildUrl(params)`
|
|
8435
|
+
* leads to: every route action of this route reads its params as if the
|
|
8436
|
+
* route matched there (see statesAt).
|
|
8437
|
+
*
|
|
8438
|
+
* @param {object} [params]
|
|
8439
|
+
*/
|
|
8440
|
+
route.preload = (params) => {
|
|
8433
8441
|
const preloadSet = routePreloadMap.get(route);
|
|
8434
|
-
if (
|
|
8442
|
+
if (preloadSet) {
|
|
8443
|
+
for (const preload of preloadSet) {
|
|
8444
|
+
preload();
|
|
8445
|
+
}
|
|
8446
|
+
}
|
|
8447
|
+
if (params === undefined) {
|
|
8435
8448
|
return;
|
|
8436
8449
|
}
|
|
8437
|
-
|
|
8438
|
-
|
|
8450
|
+
const statePreloadSet = routeStatePreloadMap.get(route);
|
|
8451
|
+
if (!statePreloadSet) {
|
|
8452
|
+
return;
|
|
8453
|
+
}
|
|
8454
|
+
const values = statesAt(route, params);
|
|
8455
|
+
for (const preload of statePreloadSet) {
|
|
8456
|
+
preload(values);
|
|
8439
8457
|
}
|
|
8440
8458
|
};
|
|
8441
8459
|
Object.preventExtensions(route);
|
|
@@ -9034,6 +9052,42 @@ const preloadState = (signal, value) => {
|
|
|
9034
9052
|
}
|
|
9035
9053
|
}
|
|
9036
9054
|
};
|
|
9055
|
+
// What the states of a route will hold once the address is `route.buildUrl(params)`,
|
|
9056
|
+
// as the routing pass will write them (see sync_routes_with_url): a param given is
|
|
9057
|
+
// what its state holds there; a weak one not given is back to its default, a url
|
|
9058
|
+
// built for another visit never carries it (readSignalForUrlBuild); any other one
|
|
9059
|
+
// is carried over by that url and keeps what it holds, so it is left out.
|
|
9060
|
+
const statesAt = (route, params) => {
|
|
9061
|
+
const values = new Map();
|
|
9062
|
+
for (const connection of getRoutePrivateProperties(route).routePattern
|
|
9063
|
+
.connections) {
|
|
9064
|
+
const { paramName, signal } = connection;
|
|
9065
|
+
if (!signal) {
|
|
9066
|
+
continue;
|
|
9067
|
+
}
|
|
9068
|
+
if (!(paramName in params)) {
|
|
9069
|
+
if (connection.weak) {
|
|
9070
|
+
values.set(signal, connection.getDefaultValue());
|
|
9071
|
+
}
|
|
9072
|
+
continue;
|
|
9073
|
+
}
|
|
9074
|
+
const value = params[paramName];
|
|
9075
|
+
if (value !== undefined) {
|
|
9076
|
+
values.set(signal, value);
|
|
9077
|
+
continue;
|
|
9078
|
+
}
|
|
9079
|
+
// An address silent about a persisted search param leaves it alone.
|
|
9080
|
+
if (
|
|
9081
|
+
connection.persists &&
|
|
9082
|
+
connection.paramType === "query" &&
|
|
9083
|
+
connection.history !== "push"
|
|
9084
|
+
) {
|
|
9085
|
+
continue;
|
|
9086
|
+
}
|
|
9087
|
+
values.set(signal, connection.getDefaultValue());
|
|
9088
|
+
}
|
|
9089
|
+
return values;
|
|
9090
|
+
};
|
|
9037
9091
|
const setupRoutes = (routes) => {
|
|
9038
9092
|
if (setupRoutesCalled) {
|
|
9039
9093
|
throw new Error(
|
|
@@ -9562,6 +9616,35 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
9562
9616
|
return active;
|
|
9563
9617
|
});
|
|
9564
9618
|
|
|
9619
|
+
/**
|
|
9620
|
+
* Calls back once every route action asking something for the page on screen
|
|
9621
|
+
* has answered, none running and none about to: what is asked on behalf of
|
|
9622
|
+
* another page waits for it, and never competes with the page's own reads.
|
|
9623
|
+
*
|
|
9624
|
+
* @param {() => void} callback
|
|
9625
|
+
* @returns {() => void} cancel
|
|
9626
|
+
*/
|
|
9627
|
+
const whenPageReadsAnswered = (callback) => {
|
|
9628
|
+
let cancelled = false;
|
|
9629
|
+
const stop = effect(function () {
|
|
9630
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
9631
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
9632
|
+
// IDLE: asking, and run by an effect of the same batch that has not run yet.
|
|
9633
|
+
if (runningState === RUNNING || runningState === IDLE) {
|
|
9634
|
+
return;
|
|
9635
|
+
}
|
|
9636
|
+
}
|
|
9637
|
+
this.dispose();
|
|
9638
|
+
if (!cancelled) {
|
|
9639
|
+
untracked(callback);
|
|
9640
|
+
}
|
|
9641
|
+
});
|
|
9642
|
+
return () => {
|
|
9643
|
+
cancelled = true;
|
|
9644
|
+
stop();
|
|
9645
|
+
};
|
|
9646
|
+
};
|
|
9647
|
+
|
|
9565
9648
|
/**
|
|
9566
9649
|
* Whether a route action of the page on screen waits for app state to ask: its
|
|
9567
9650
|
* route matches and its `when` says no. Params that say no are not waiting:
|
|
@@ -9642,7 +9725,9 @@ const activeRouteReadsSignal = computed(() => {
|
|
|
9642
9725
|
* come from the address is not prefetched by a link — a link's prefetch has
|
|
9643
9726
|
* no address. It is by the press that opens a popup bound to a state these
|
|
9644
9727
|
* params read: that address is the current one with the state set to the
|
|
9645
|
-
* popup's value (see `prefetch` on Button).
|
|
9728
|
+
* popup's value (see `prefetch` on Button). And by `route.preload(params)`,
|
|
9729
|
+
* which names the address: what a `<RouteTravel prefetch>` asks of the pages
|
|
9730
|
+
* on each side of the one on screen.
|
|
9646
9731
|
* @param {object} [options]
|
|
9647
9732
|
* @param {() => any} [options.when] - a condition on app state the action
|
|
9648
9733
|
* asks under, read off signals like the params: falsy, it runs nothing.
|
|
@@ -9723,7 +9808,7 @@ const routeAction = (
|
|
|
9723
9808
|
for (const route of routes) {
|
|
9724
9809
|
if (paramsEffect) {
|
|
9725
9810
|
registerRouteStatePreload(route, (values) => {
|
|
9726
|
-
prefetchAsIf(action, readParams, values);
|
|
9811
|
+
prefetchAsIf(action, readParams, readParamsAsked, values);
|
|
9727
9812
|
});
|
|
9728
9813
|
} else {
|
|
9729
9814
|
registerRoutePreload(route, () => {
|
|
@@ -9739,15 +9824,17 @@ const routeAction = (
|
|
|
9739
9824
|
};
|
|
9740
9825
|
|
|
9741
9826
|
// The params as they will read once the states hold what they are about to
|
|
9742
|
-
// (see readStateAsIf), prerun only when
|
|
9743
|
-
//
|
|
9744
|
-
// and a prerun would retry a FAILED one behind the screen's back.
|
|
9745
|
-
|
|
9827
|
+
// (see readStateAsIf), prerun only when they differ from what the action asks
|
|
9828
|
+
// now: params the states leave alone are the ones it already runs on — or is
|
|
9829
|
+
// meant not to, and a prerun would retry a FAILED one behind the screen's back.
|
|
9830
|
+
// An action whose route does not match asks nothing: whatever its params read,
|
|
9831
|
+
// the arrival runs it on them.
|
|
9832
|
+
const prefetchAsIf = (action, readParams, readParamsAsked, values) => {
|
|
9746
9833
|
const params = readStateAsIf(values, readParams);
|
|
9747
9834
|
if (!params) {
|
|
9748
9835
|
return;
|
|
9749
9836
|
}
|
|
9750
|
-
if (compareTwoJsValues(params, untracked(
|
|
9837
|
+
if (compareTwoJsValues(params, untracked(readParamsAsked))) {
|
|
9751
9838
|
return;
|
|
9752
9839
|
}
|
|
9753
9840
|
prerunAhead(action.bindParams(params));
|
|
@@ -40450,9 +40537,11 @@ const readRowStretch = (scroller, pages, page, { sideways } = {}) => {
|
|
|
40450
40537
|
/**
|
|
40451
40538
|
* @param {Element} scroller
|
|
40452
40539
|
* @param {object} options
|
|
40453
|
-
* @param {(move: {finger: boolean}) => void} options.onScroll - the
|
|
40454
|
-
* `finger`: a touch has come since the row last stopped, so a lift will
|
|
40455
|
-
* it goes (`onLeftToSnap`). Without one (a trackpad, a wheel) nothing
|
|
40540
|
+
* @param {(move: {finger: boolean, lifted: boolean}) => void} options.onScroll - the
|
|
40541
|
+
* row moved. `finger`: a touch has come since the row last stopped, so a lift will
|
|
40542
|
+
* say where it goes (`onLeftToSnap`). Without one (a trackpad, a wheel) nothing
|
|
40543
|
+
* will. `lifted`: that finger has lifted and none has landed since: no hand moves
|
|
40544
|
+
* the row, the snap the lift left running does.
|
|
40456
40545
|
* @param {(event: Event|null) => void} options.onLand - the row may have stopped.
|
|
40457
40546
|
* Read `pageAtRest` to know whether it stopped on a page. Heard more than once
|
|
40458
40547
|
* for one landing, so what it does must find nothing to do the second time.
|
|
@@ -40483,7 +40572,10 @@ const watchRowScroll = (
|
|
|
40483
40572
|
stoppedTimeout = setTimeout(landOnSilence, STOPPED_SILENCE);
|
|
40484
40573
|
};
|
|
40485
40574
|
const onScrollEvent = () => {
|
|
40486
|
-
onScroll({
|
|
40575
|
+
onScroll({
|
|
40576
|
+
finger: touchedSinceStop,
|
|
40577
|
+
lifted: touchedSinceStop && !touching,
|
|
40578
|
+
});
|
|
40487
40579
|
armSilence();
|
|
40488
40580
|
if (!liftedAt) {
|
|
40489
40581
|
return;
|
|
@@ -40647,6 +40739,7 @@ const ROW_READING = {
|
|
|
40647
40739
|
* travelByDrag?: boolean,
|
|
40648
40740
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
40649
40741
|
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
40742
|
+
* prefetch?: boolean,
|
|
40650
40743
|
* }>}
|
|
40651
40744
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
40652
40745
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -40670,6 +40763,12 @@ const ROW_READING = {
|
|
|
40670
40763
|
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
40671
40764
|
* the height of the row, as the page is. Its route does not match while it stands
|
|
40672
40765
|
* there: what it reads is not asked for.
|
|
40766
|
+
* @param {boolean} [props.prefetch=false] - whether the pages on each side of the one
|
|
40767
|
+
* on screen have their reads asked for ahead of the hand: at rest, once the page on
|
|
40768
|
+
* screen has its answers and has painted them, the next page first, each with
|
|
40769
|
+
* `route.preload(params)`. A swipe then builds a page whose answers are already
|
|
40770
|
+
* there, with no wait to show. Off by default: every page one stops on costs the
|
|
40771
|
+
* reads of two more.
|
|
40673
40772
|
*
|
|
40674
40773
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
40675
40774
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -40686,6 +40785,7 @@ const RouteTravel = ({
|
|
|
40686
40785
|
routeTransition: "none"
|
|
40687
40786
|
}),
|
|
40688
40787
|
renderNeighbour,
|
|
40788
|
+
prefetch = false,
|
|
40689
40789
|
className,
|
|
40690
40790
|
children,
|
|
40691
40791
|
...rest
|
|
@@ -40695,7 +40795,8 @@ const RouteTravel = ({
|
|
|
40695
40795
|
const liveRef = useRef();
|
|
40696
40796
|
const picturesRef = useRef();
|
|
40697
40797
|
const windowed = Boolean(routesProp);
|
|
40698
|
-
|
|
40798
|
+
// Counts the movements ended: endMovement renders through it.
|
|
40799
|
+
const [restCount, render] = useReducer(count => count + 1, 0);
|
|
40699
40800
|
const pagesFromChildren = useMemo(() => collectRoutePages(children), [children]);
|
|
40700
40801
|
const pagesFromProp = useMemo(() => routesProp && routesProp.map(normalizePage), [routesProp]);
|
|
40701
40802
|
// Held while the row moves: a window recentred under a hand would move the very
|
|
@@ -41067,7 +41168,8 @@ const RouteTravel = ({
|
|
|
41067
41168
|
const row = rowRef.current;
|
|
41068
41169
|
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
41069
41170
|
const onScroll = ({
|
|
41070
|
-
finger
|
|
41171
|
+
finger,
|
|
41172
|
+
lifted
|
|
41071
41173
|
}) => {
|
|
41072
41174
|
const {
|
|
41073
41175
|
pages,
|
|
@@ -41102,6 +41204,12 @@ const RouteTravel = ({
|
|
|
41102
41204
|
} = readRowFrame(row, panes);
|
|
41103
41205
|
followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
|
|
41104
41206
|
}
|
|
41207
|
+
// The pull is read from the pane of the address. Once the finger has lifted,
|
|
41208
|
+
// the address is where the snap goes (onLeftToSnap), and what still lies
|
|
41209
|
+
// between is the snap playing out, not a hand pulling towards the neighbour.
|
|
41210
|
+
if (lifted) {
|
|
41211
|
+
return;
|
|
41212
|
+
}
|
|
41105
41213
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
41106
41214
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
41107
41215
|
return;
|
|
@@ -41262,6 +41370,45 @@ const RouteTravel = ({
|
|
|
41262
41370
|
}
|
|
41263
41371
|
}, [currentIndex]);
|
|
41264
41372
|
|
|
41373
|
+
// What a hand can bring is asked for at rest, the next page first: never before
|
|
41374
|
+
// the page on screen has its answers and has painted them. A row in movement is
|
|
41375
|
+
// not at rest; the render that ends the movement asks again. Read as pages, not as
|
|
41376
|
+
// entries: the pages of <Route> children are collected again at every render.
|
|
41377
|
+
const neighboursRef = useRef([null, null]);
|
|
41378
|
+
const nextPage = currentIndex === -1 ? null : pages[currentIndex + 1] || null;
|
|
41379
|
+
const previousPage = currentIndex === -1 ? null : pages[currentIndex - 1] || null;
|
|
41380
|
+
if (!samePage(neighboursRef.current[0], nextPage) || !samePage(neighboursRef.current[1], previousPage)) {
|
|
41381
|
+
neighboursRef.current = [nextPage, previousPage];
|
|
41382
|
+
}
|
|
41383
|
+
const neighbours = neighboursRef.current;
|
|
41384
|
+
useLayoutEffect(() => {
|
|
41385
|
+
if (!prefetch || movementRef.current) {
|
|
41386
|
+
return undefined;
|
|
41387
|
+
}
|
|
41388
|
+
if (!neighbours[0] && !neighbours[1]) {
|
|
41389
|
+
return undefined;
|
|
41390
|
+
}
|
|
41391
|
+
let cancelPaint = null;
|
|
41392
|
+
const cancelWait = whenPageReadsAnswered(() => {
|
|
41393
|
+
cancelPaint = afterPaint(() => {
|
|
41394
|
+
if (movementRef.current) {
|
|
41395
|
+
return;
|
|
41396
|
+
}
|
|
41397
|
+
for (const page of neighbours) {
|
|
41398
|
+
if (page) {
|
|
41399
|
+
page.route.preload(page.params || {});
|
|
41400
|
+
}
|
|
41401
|
+
}
|
|
41402
|
+
});
|
|
41403
|
+
});
|
|
41404
|
+
return () => {
|
|
41405
|
+
cancelWait();
|
|
41406
|
+
if (cancelPaint) {
|
|
41407
|
+
cancelPaint();
|
|
41408
|
+
}
|
|
41409
|
+
};
|
|
41410
|
+
}, [prefetch, neighbours, restCount]);
|
|
41411
|
+
|
|
41265
41412
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
41266
41413
|
// any scroll event: a finger landing is the last word before that.
|
|
41267
41414
|
const paintsNeighbours = Boolean(renderNeighbour);
|