@jsenv/navi 0.30.9 → 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 +151 -16
- package/dist/dev/jsenv_navi.js.map +4 -4
- package/dist/jsenv_navi.js +151 -16
- package/dist/jsenv_navi.js.map +4 -4
- 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));
|
|
@@ -40652,6 +40739,7 @@ const ROW_READING = {
|
|
|
40652
40739
|
* travelByDrag?: boolean,
|
|
40653
40740
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
40654
40741
|
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
40742
|
+
* prefetch?: boolean,
|
|
40655
40743
|
* }>}
|
|
40656
40744
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
40657
40745
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -40675,6 +40763,12 @@ const ROW_READING = {
|
|
|
40675
40763
|
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
40676
40764
|
* the height of the row, as the page is. Its route does not match while it stands
|
|
40677
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.
|
|
40678
40772
|
*
|
|
40679
40773
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
40680
40774
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -40691,6 +40785,7 @@ const RouteTravel = ({
|
|
|
40691
40785
|
routeTransition: "none"
|
|
40692
40786
|
}),
|
|
40693
40787
|
renderNeighbour,
|
|
40788
|
+
prefetch = false,
|
|
40694
40789
|
className,
|
|
40695
40790
|
children,
|
|
40696
40791
|
...rest
|
|
@@ -40700,7 +40795,8 @@ const RouteTravel = ({
|
|
|
40700
40795
|
const liveRef = useRef();
|
|
40701
40796
|
const picturesRef = useRef();
|
|
40702
40797
|
const windowed = Boolean(routesProp);
|
|
40703
|
-
|
|
40798
|
+
// Counts the movements ended: endMovement renders through it.
|
|
40799
|
+
const [restCount, render] = useReducer(count => count + 1, 0);
|
|
40704
40800
|
const pagesFromChildren = useMemo(() => collectRoutePages(children), [children]);
|
|
40705
40801
|
const pagesFromProp = useMemo(() => routesProp && routesProp.map(normalizePage), [routesProp]);
|
|
40706
40802
|
// Held while the row moves: a window recentred under a hand would move the very
|
|
@@ -41274,6 +41370,45 @@ const RouteTravel = ({
|
|
|
41274
41370
|
}
|
|
41275
41371
|
}, [currentIndex]);
|
|
41276
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
|
+
|
|
41277
41412
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
41278
41413
|
// any scroll event: a finger landing is the last word before that.
|
|
41279
41414
|
const paintsNeighbours = Boolean(renderNeighbour);
|