@jsenv/navi 0.30.9 → 0.30.11
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 +174 -23
- package/dist/dev/jsenv_navi.js.map +7 -7
- package/dist/jsenv_navi.js +174 -23
- package/dist/jsenv_navi.js.map +7 -7
- package/docs/actions.md +5 -2
- 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/dev/jsenv_navi.js
CHANGED
|
@@ -4562,9 +4562,10 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4562
4562
|
}
|
|
4563
4563
|
}
|
|
4564
4564
|
runningStateSignal.value = RUNNING;
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
|
|
4565
|
+
// What fills the instance says what it holds: an answer only a
|
|
4566
|
+
// prerun asked for is one nothing on screen has read yet, and the
|
|
4567
|
+
// run that asks for it next is promoted onto it (see updateActions).
|
|
4568
|
+
isPrerunSignal.value = isPrerun;
|
|
4568
4569
|
});
|
|
4569
4570
|
|
|
4570
4571
|
const args = [];
|
|
@@ -5375,10 +5376,16 @@ const actionRunEffect = (
|
|
|
5375
5376
|
runUnwatched(() =>
|
|
5376
5377
|
actionTarget.run({ reason: "params restored from falsy state" }),
|
|
5377
5378
|
);
|
|
5378
|
-
} else if (
|
|
5379
|
+
} else if (
|
|
5380
|
+
getActionPrivateProperties(actionTarget).keepsAnswers ||
|
|
5381
|
+
actionTarget.isPrerunSignal.peek()
|
|
5382
|
+
) {
|
|
5379
5383
|
// The lookup that moved the binding here already reset an answer
|
|
5380
5384
|
// older than its `keep`: what the instance holds is still good, and
|
|
5381
|
-
// a prerun still out is joined rather than asked again.
|
|
5385
|
+
// a prerun still out is joined rather than asked again. So is an
|
|
5386
|
+
// answer only a prerun asked for, kept or not: asked ahead for this
|
|
5387
|
+
// very arrival (a <RouteTravel prefetch> neighbour), nothing has read
|
|
5388
|
+
// it yet, and the run promotes it.
|
|
5382
5389
|
runUnwatched(() => actionTarget.run({ reason: "params modified" }));
|
|
5383
5390
|
} else {
|
|
5384
5391
|
runUnwatched(() => actionTarget.rerun({ reason: "params modified" }));
|
|
@@ -8787,17 +8794,35 @@ const route = (pattern, options = {}) => {
|
|
|
8787
8794
|
return `route "${cleanPattern}"`;
|
|
8788
8795
|
},
|
|
8789
8796
|
};
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8797
|
+
/**
|
|
8798
|
+
* Asks ahead of the arrival for what this route's actions will ask: the
|
|
8799
|
+
* instances the arrival promotes to runs (see prerunAhead in route_action.js).
|
|
8800
|
+
*
|
|
8801
|
+
* Without params, only what asks nothing of the address: the page's code, a
|
|
8802
|
+
* read without params — what a link preloads, since it has no state to read
|
|
8803
|
+
* the params of. Given params, `{}` included, the address `buildUrl(params)`
|
|
8804
|
+
* leads to: every route action of this route reads its params as if the
|
|
8805
|
+
* route matched there (see statesAt).
|
|
8806
|
+
*
|
|
8807
|
+
* @param {object} [params]
|
|
8808
|
+
*/
|
|
8809
|
+
route.preload = (params) => {
|
|
8795
8810
|
const preloadSet = routePreloadMap.get(route);
|
|
8796
|
-
if (
|
|
8811
|
+
if (preloadSet) {
|
|
8812
|
+
for (const preload of preloadSet) {
|
|
8813
|
+
preload();
|
|
8814
|
+
}
|
|
8815
|
+
}
|
|
8816
|
+
if (params === undefined) {
|
|
8797
8817
|
return;
|
|
8798
8818
|
}
|
|
8799
|
-
|
|
8800
|
-
|
|
8819
|
+
const statePreloadSet = routeStatePreloadMap.get(route);
|
|
8820
|
+
if (!statePreloadSet) {
|
|
8821
|
+
return;
|
|
8822
|
+
}
|
|
8823
|
+
const values = statesAt(route, params);
|
|
8824
|
+
for (const preload of statePreloadSet) {
|
|
8825
|
+
preload(values);
|
|
8801
8826
|
}
|
|
8802
8827
|
};
|
|
8803
8828
|
Object.preventExtensions(route);
|
|
@@ -9409,6 +9434,42 @@ const preloadState = (signal, value) => {
|
|
|
9409
9434
|
}
|
|
9410
9435
|
}
|
|
9411
9436
|
};
|
|
9437
|
+
// What the states of a route will hold once the address is `route.buildUrl(params)`,
|
|
9438
|
+
// as the routing pass will write them (see sync_routes_with_url): a param given is
|
|
9439
|
+
// what its state holds there; a weak one not given is back to its default, a url
|
|
9440
|
+
// built for another visit never carries it (readSignalForUrlBuild); any other one
|
|
9441
|
+
// is carried over by that url and keeps what it holds, so it is left out.
|
|
9442
|
+
const statesAt = (route, params) => {
|
|
9443
|
+
const values = new Map();
|
|
9444
|
+
for (const connection of getRoutePrivateProperties(route).routePattern
|
|
9445
|
+
.connections) {
|
|
9446
|
+
const { paramName, signal } = connection;
|
|
9447
|
+
if (!signal) {
|
|
9448
|
+
continue;
|
|
9449
|
+
}
|
|
9450
|
+
if (!(paramName in params)) {
|
|
9451
|
+
if (connection.weak) {
|
|
9452
|
+
values.set(signal, connection.getDefaultValue());
|
|
9453
|
+
}
|
|
9454
|
+
continue;
|
|
9455
|
+
}
|
|
9456
|
+
const value = params[paramName];
|
|
9457
|
+
if (value !== undefined) {
|
|
9458
|
+
values.set(signal, value);
|
|
9459
|
+
continue;
|
|
9460
|
+
}
|
|
9461
|
+
// An address silent about a persisted search param leaves it alone.
|
|
9462
|
+
if (
|
|
9463
|
+
connection.persists &&
|
|
9464
|
+
connection.paramType === "query" &&
|
|
9465
|
+
connection.history !== "push"
|
|
9466
|
+
) {
|
|
9467
|
+
continue;
|
|
9468
|
+
}
|
|
9469
|
+
values.set(signal, connection.getDefaultValue());
|
|
9470
|
+
}
|
|
9471
|
+
return values;
|
|
9472
|
+
};
|
|
9412
9473
|
const setupRoutes = (routes) => {
|
|
9413
9474
|
if (setupRoutesCalled) {
|
|
9414
9475
|
throw new Error(
|
|
@@ -9956,6 +10017,35 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
9956
10017
|
return active;
|
|
9957
10018
|
});
|
|
9958
10019
|
|
|
10020
|
+
/**
|
|
10021
|
+
* Calls back once every route action asking something for the page on screen
|
|
10022
|
+
* has answered, none running and none about to: what is asked on behalf of
|
|
10023
|
+
* another page waits for it, and never competes with the page's own reads.
|
|
10024
|
+
*
|
|
10025
|
+
* @param {() => void} callback
|
|
10026
|
+
* @returns {() => void} cancel
|
|
10027
|
+
*/
|
|
10028
|
+
const whenPageReadsAnswered = (callback) => {
|
|
10029
|
+
let cancelled = false;
|
|
10030
|
+
const stop = effect(function () {
|
|
10031
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
10032
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
10033
|
+
// IDLE: asking, and run by an effect of the same batch that has not run yet.
|
|
10034
|
+
if (runningState === RUNNING || runningState === IDLE) {
|
|
10035
|
+
return;
|
|
10036
|
+
}
|
|
10037
|
+
}
|
|
10038
|
+
this.dispose();
|
|
10039
|
+
if (!cancelled) {
|
|
10040
|
+
untracked(callback);
|
|
10041
|
+
}
|
|
10042
|
+
});
|
|
10043
|
+
return () => {
|
|
10044
|
+
cancelled = true;
|
|
10045
|
+
stop();
|
|
10046
|
+
};
|
|
10047
|
+
};
|
|
10048
|
+
|
|
9959
10049
|
/**
|
|
9960
10050
|
* Whether a route action of the page on screen waits for app state to ask: its
|
|
9961
10051
|
* route matches and its `when` says no. Params that say no are not waiting:
|
|
@@ -10044,7 +10134,9 @@ const ROUTE_ACTION_BINDING_OPTIONS = new Set([
|
|
|
10044
10134
|
* come from the address is not prefetched by a link — a link's prefetch has
|
|
10045
10135
|
* no address. It is by the press that opens a popup bound to a state these
|
|
10046
10136
|
* params read: that address is the current one with the state set to the
|
|
10047
|
-
* popup's value (see `prefetch` on Button).
|
|
10137
|
+
* popup's value (see `prefetch` on Button). And by `route.preload(params)`,
|
|
10138
|
+
* which names the address: what a `<RouteTravel prefetch>` asks of the pages
|
|
10139
|
+
* on each side of the one on screen.
|
|
10048
10140
|
* @param {object} [options]
|
|
10049
10141
|
* @param {() => any} [options.when] - a condition on app state the action
|
|
10050
10142
|
* asks under, read off signals like the params: falsy, it runs nothing.
|
|
@@ -10128,7 +10220,7 @@ const routeAction = (
|
|
|
10128
10220
|
for (const route of routes) {
|
|
10129
10221
|
if (paramsEffect) {
|
|
10130
10222
|
registerRouteStatePreload(route, (values) => {
|
|
10131
|
-
prefetchAsIf(action, readParams, values);
|
|
10223
|
+
prefetchAsIf(action, readParams, readParamsAsked, values);
|
|
10132
10224
|
});
|
|
10133
10225
|
} else {
|
|
10134
10226
|
registerRoutePreload(route, () => {
|
|
@@ -10144,15 +10236,17 @@ const routeAction = (
|
|
|
10144
10236
|
};
|
|
10145
10237
|
|
|
10146
10238
|
// The params as they will read once the states hold what they are about to
|
|
10147
|
-
// (see readStateAsIf), prerun only when
|
|
10148
|
-
//
|
|
10149
|
-
// and a prerun would retry a FAILED one behind the screen's back.
|
|
10150
|
-
|
|
10239
|
+
// (see readStateAsIf), prerun only when they differ from what the action asks
|
|
10240
|
+
// now: params the states leave alone are the ones it already runs on — or is
|
|
10241
|
+
// meant not to, and a prerun would retry a FAILED one behind the screen's back.
|
|
10242
|
+
// An action whose route does not match asks nothing: whatever its params read,
|
|
10243
|
+
// the arrival runs it on them.
|
|
10244
|
+
const prefetchAsIf = (action, readParams, readParamsAsked, values) => {
|
|
10151
10245
|
const params = readStateAsIf(values, readParams);
|
|
10152
10246
|
if (!params) {
|
|
10153
10247
|
return;
|
|
10154
10248
|
}
|
|
10155
|
-
if (compareTwoJsValues(params, untracked(
|
|
10249
|
+
if (compareTwoJsValues(params, untracked(readParamsAsked))) {
|
|
10156
10250
|
return;
|
|
10157
10251
|
}
|
|
10158
10252
|
prerunAhead(action.bindParams(params));
|
|
@@ -29086,6 +29180,16 @@ const createResourceLifecycleManager = () => {
|
|
|
29086
29180
|
}
|
|
29087
29181
|
}
|
|
29088
29182
|
|
|
29183
|
+
// A head start the write made stale is dropped, not asked again: nothing
|
|
29184
|
+
// on screen reads it, and its owner, finding it idle, asks on arriving
|
|
29185
|
+
// instead of promoting what left before the write.
|
|
29186
|
+
for (const action of actionsToRerun) {
|
|
29187
|
+
if (action.isPrerunSignal.peek()) {
|
|
29188
|
+
actionsToRerun.delete(action);
|
|
29189
|
+
actionsToReset.add(action);
|
|
29190
|
+
}
|
|
29191
|
+
}
|
|
29192
|
+
|
|
29089
29193
|
return {
|
|
29090
29194
|
actionsToRerun,
|
|
29091
29195
|
actionsToReset,
|
|
@@ -29173,9 +29277,8 @@ const createResourceLifecycleManager = () => {
|
|
|
29173
29277
|
// A read to ask again after a write: answered before it landed, or still out —
|
|
29174
29278
|
// an answer that left before the write is as stale as a completed one, and
|
|
29175
29279
|
// would replace what the write put in the store (a rerun aborts it and asks
|
|
29176
|
-
// again). A head start is its owner's to ask.
|
|
29280
|
+
// again). A head start is its owner's to ask (findEffectOnActions resets it).
|
|
29177
29281
|
const isAnsweredOrAsking = (action, triggeringAction) =>
|
|
29178
|
-
!action.isPrerun &&
|
|
29179
29282
|
(action.completed || action.runningState === RUNNING) &&
|
|
29180
29283
|
action !== triggeringAction;
|
|
29181
29284
|
// A write named in `writes` says for itself which reads of its own resource it
|
|
@@ -41943,6 +42046,7 @@ const ROW_READING = {
|
|
|
41943
42046
|
* travelByDrag?: boolean,
|
|
41944
42047
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
41945
42048
|
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
42049
|
+
* prefetch?: boolean,
|
|
41946
42050
|
* }>}
|
|
41947
42051
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
41948
42052
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -41966,6 +42070,12 @@ const ROW_READING = {
|
|
|
41966
42070
|
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
41967
42071
|
* the height of the row, as the page is. Its route does not match while it stands
|
|
41968
42072
|
* there: what it reads is not asked for.
|
|
42073
|
+
* @param {boolean} [props.prefetch=false] - whether the pages on each side of the one
|
|
42074
|
+
* on screen have their reads asked for ahead of the hand: at rest, once the page on
|
|
42075
|
+
* screen has its answers and has painted them, the next page first, each with
|
|
42076
|
+
* `route.preload(params)`. A swipe then builds a page whose answers are already
|
|
42077
|
+
* there, with no wait to show. Off by default: every page one stops on costs the
|
|
42078
|
+
* reads of two more.
|
|
41969
42079
|
*
|
|
41970
42080
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
41971
42081
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -41982,6 +42092,7 @@ const RouteTravel = ({
|
|
|
41982
42092
|
routeTransition: "none"
|
|
41983
42093
|
}),
|
|
41984
42094
|
renderNeighbour,
|
|
42095
|
+
prefetch = false,
|
|
41985
42096
|
className,
|
|
41986
42097
|
children,
|
|
41987
42098
|
...rest
|
|
@@ -41991,7 +42102,8 @@ const RouteTravel = ({
|
|
|
41991
42102
|
const liveRef = useRef();
|
|
41992
42103
|
const picturesRef = useRef();
|
|
41993
42104
|
const windowed = Boolean(routesProp);
|
|
41994
|
-
|
|
42105
|
+
// Counts the movements ended: endMovement renders through it.
|
|
42106
|
+
const [restCount, render] = useReducer(count => count + 1, 0);
|
|
41995
42107
|
const pagesFromChildren = useMemo(() => collectRoutePages(children), [children]);
|
|
41996
42108
|
const pagesFromProp = useMemo(() => routesProp && routesProp.map(normalizePage), [routesProp]);
|
|
41997
42109
|
// Held while the row moves: a window recentred under a hand would move the very
|
|
@@ -42565,6 +42677,45 @@ const RouteTravel = ({
|
|
|
42565
42677
|
}
|
|
42566
42678
|
}, [currentIndex]);
|
|
42567
42679
|
|
|
42680
|
+
// What a hand can bring is asked for at rest, the next page first: never before
|
|
42681
|
+
// the page on screen has its answers and has painted them. A row in movement is
|
|
42682
|
+
// not at rest; the render that ends the movement asks again. Read as pages, not as
|
|
42683
|
+
// entries: the pages of <Route> children are collected again at every render.
|
|
42684
|
+
const neighboursRef = useRef([null, null]);
|
|
42685
|
+
const nextPage = currentIndex === -1 ? null : pages[currentIndex + 1] || null;
|
|
42686
|
+
const previousPage = currentIndex === -1 ? null : pages[currentIndex - 1] || null;
|
|
42687
|
+
if (!samePage(neighboursRef.current[0], nextPage) || !samePage(neighboursRef.current[1], previousPage)) {
|
|
42688
|
+
neighboursRef.current = [nextPage, previousPage];
|
|
42689
|
+
}
|
|
42690
|
+
const neighbours = neighboursRef.current;
|
|
42691
|
+
useLayoutEffect(() => {
|
|
42692
|
+
if (!prefetch || movementRef.current) {
|
|
42693
|
+
return undefined;
|
|
42694
|
+
}
|
|
42695
|
+
if (!neighbours[0] && !neighbours[1]) {
|
|
42696
|
+
return undefined;
|
|
42697
|
+
}
|
|
42698
|
+
let cancelPaint = null;
|
|
42699
|
+
const cancelWait = whenPageReadsAnswered(() => {
|
|
42700
|
+
cancelPaint = afterPaint(() => {
|
|
42701
|
+
if (movementRef.current) {
|
|
42702
|
+
return;
|
|
42703
|
+
}
|
|
42704
|
+
for (const page of neighbours) {
|
|
42705
|
+
if (page) {
|
|
42706
|
+
page.route.preload(page.params || {});
|
|
42707
|
+
}
|
|
42708
|
+
}
|
|
42709
|
+
});
|
|
42710
|
+
});
|
|
42711
|
+
return () => {
|
|
42712
|
+
cancelWait();
|
|
42713
|
+
if (cancelPaint) {
|
|
42714
|
+
cancelPaint();
|
|
42715
|
+
}
|
|
42716
|
+
};
|
|
42717
|
+
}, [prefetch, neighbours, restCount]);
|
|
42718
|
+
|
|
42568
42719
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
42569
42720
|
// any scroll event: a finger landing is the last word before that.
|
|
42570
42721
|
const paintsNeighbours = Boolean(renderNeighbour);
|