@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/jsenv_navi.js
CHANGED
|
@@ -4302,9 +4302,10 @@ const createAction = (callback, rootOptions = {}) => {
|
|
|
4302
4302
|
}
|
|
4303
4303
|
}
|
|
4304
4304
|
runningStateSignal.value = RUNNING;
|
|
4305
|
-
|
|
4306
|
-
|
|
4307
|
-
|
|
4305
|
+
// What fills the instance says what it holds: an answer only a
|
|
4306
|
+
// prerun asked for is one nothing on screen has read yet, and the
|
|
4307
|
+
// run that asks for it next is promoted onto it (see updateActions).
|
|
4308
|
+
isPrerunSignal.value = isPrerun;
|
|
4308
4309
|
});
|
|
4309
4310
|
|
|
4310
4311
|
const args = [];
|
|
@@ -5106,10 +5107,16 @@ const actionRunEffect = (
|
|
|
5106
5107
|
runUnwatched(() =>
|
|
5107
5108
|
actionTarget.run({ reason: "params restored from falsy state" }),
|
|
5108
5109
|
);
|
|
5109
|
-
} else if (
|
|
5110
|
+
} else if (
|
|
5111
|
+
getActionPrivateProperties(actionTarget).keepsAnswers ||
|
|
5112
|
+
actionTarget.isPrerunSignal.peek()
|
|
5113
|
+
) {
|
|
5110
5114
|
// The lookup that moved the binding here already reset an answer
|
|
5111
5115
|
// older than its `keep`: what the instance holds is still good, and
|
|
5112
|
-
// a prerun still out is joined rather than asked again.
|
|
5116
|
+
// a prerun still out is joined rather than asked again. So is an
|
|
5117
|
+
// answer only a prerun asked for, kept or not: asked ahead for this
|
|
5118
|
+
// very arrival (a <RouteTravel prefetch> neighbour), nothing has read
|
|
5119
|
+
// it yet, and the run promotes it.
|
|
5113
5120
|
runUnwatched(() => actionTarget.run({ reason: "params modified" }));
|
|
5114
5121
|
} else {
|
|
5115
5122
|
runUnwatched(() => actionTarget.rerun({ reason: "params modified" }));
|
|
@@ -8425,17 +8432,35 @@ const route = (pattern, options = {}) => {
|
|
|
8425
8432
|
return `route "${cleanPattern}"`;
|
|
8426
8433
|
},
|
|
8427
8434
|
};
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
|
|
8435
|
+
/**
|
|
8436
|
+
* Asks ahead of the arrival for what this route's actions will ask: the
|
|
8437
|
+
* instances the arrival promotes to runs (see prerunAhead in route_action.js).
|
|
8438
|
+
*
|
|
8439
|
+
* Without params, only what asks nothing of the address: the page's code, a
|
|
8440
|
+
* read without params — what a link preloads, since it has no state to read
|
|
8441
|
+
* the params of. Given params, `{}` included, the address `buildUrl(params)`
|
|
8442
|
+
* leads to: every route action of this route reads its params as if the
|
|
8443
|
+
* route matched there (see statesAt).
|
|
8444
|
+
*
|
|
8445
|
+
* @param {object} [params]
|
|
8446
|
+
*/
|
|
8447
|
+
route.preload = (params) => {
|
|
8433
8448
|
const preloadSet = routePreloadMap.get(route);
|
|
8434
|
-
if (
|
|
8449
|
+
if (preloadSet) {
|
|
8450
|
+
for (const preload of preloadSet) {
|
|
8451
|
+
preload();
|
|
8452
|
+
}
|
|
8453
|
+
}
|
|
8454
|
+
if (params === undefined) {
|
|
8435
8455
|
return;
|
|
8436
8456
|
}
|
|
8437
|
-
|
|
8438
|
-
|
|
8457
|
+
const statePreloadSet = routeStatePreloadMap.get(route);
|
|
8458
|
+
if (!statePreloadSet) {
|
|
8459
|
+
return;
|
|
8460
|
+
}
|
|
8461
|
+
const values = statesAt(route, params);
|
|
8462
|
+
for (const preload of statePreloadSet) {
|
|
8463
|
+
preload(values);
|
|
8439
8464
|
}
|
|
8440
8465
|
};
|
|
8441
8466
|
Object.preventExtensions(route);
|
|
@@ -9034,6 +9059,42 @@ const preloadState = (signal, value) => {
|
|
|
9034
9059
|
}
|
|
9035
9060
|
}
|
|
9036
9061
|
};
|
|
9062
|
+
// What the states of a route will hold once the address is `route.buildUrl(params)`,
|
|
9063
|
+
// as the routing pass will write them (see sync_routes_with_url): a param given is
|
|
9064
|
+
// what its state holds there; a weak one not given is back to its default, a url
|
|
9065
|
+
// built for another visit never carries it (readSignalForUrlBuild); any other one
|
|
9066
|
+
// is carried over by that url and keeps what it holds, so it is left out.
|
|
9067
|
+
const statesAt = (route, params) => {
|
|
9068
|
+
const values = new Map();
|
|
9069
|
+
for (const connection of getRoutePrivateProperties(route).routePattern
|
|
9070
|
+
.connections) {
|
|
9071
|
+
const { paramName, signal } = connection;
|
|
9072
|
+
if (!signal) {
|
|
9073
|
+
continue;
|
|
9074
|
+
}
|
|
9075
|
+
if (!(paramName in params)) {
|
|
9076
|
+
if (connection.weak) {
|
|
9077
|
+
values.set(signal, connection.getDefaultValue());
|
|
9078
|
+
}
|
|
9079
|
+
continue;
|
|
9080
|
+
}
|
|
9081
|
+
const value = params[paramName];
|
|
9082
|
+
if (value !== undefined) {
|
|
9083
|
+
values.set(signal, value);
|
|
9084
|
+
continue;
|
|
9085
|
+
}
|
|
9086
|
+
// An address silent about a persisted search param leaves it alone.
|
|
9087
|
+
if (
|
|
9088
|
+
connection.persists &&
|
|
9089
|
+
connection.paramType === "query" &&
|
|
9090
|
+
connection.history !== "push"
|
|
9091
|
+
) {
|
|
9092
|
+
continue;
|
|
9093
|
+
}
|
|
9094
|
+
values.set(signal, connection.getDefaultValue());
|
|
9095
|
+
}
|
|
9096
|
+
return values;
|
|
9097
|
+
};
|
|
9037
9098
|
const setupRoutes = (routes) => {
|
|
9038
9099
|
if (setupRoutesCalled) {
|
|
9039
9100
|
throw new Error(
|
|
@@ -9562,6 +9623,35 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
9562
9623
|
return active;
|
|
9563
9624
|
});
|
|
9564
9625
|
|
|
9626
|
+
/**
|
|
9627
|
+
* Calls back once every route action asking something for the page on screen
|
|
9628
|
+
* has answered, none running and none about to: what is asked on behalf of
|
|
9629
|
+
* another page waits for it, and never competes with the page's own reads.
|
|
9630
|
+
*
|
|
9631
|
+
* @param {() => void} callback
|
|
9632
|
+
* @returns {() => void} cancel
|
|
9633
|
+
*/
|
|
9634
|
+
const whenPageReadsAnswered = (callback) => {
|
|
9635
|
+
let cancelled = false;
|
|
9636
|
+
const stop = effect(function () {
|
|
9637
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
9638
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
9639
|
+
// IDLE: asking, and run by an effect of the same batch that has not run yet.
|
|
9640
|
+
if (runningState === RUNNING || runningState === IDLE) {
|
|
9641
|
+
return;
|
|
9642
|
+
}
|
|
9643
|
+
}
|
|
9644
|
+
this.dispose();
|
|
9645
|
+
if (!cancelled) {
|
|
9646
|
+
untracked(callback);
|
|
9647
|
+
}
|
|
9648
|
+
});
|
|
9649
|
+
return () => {
|
|
9650
|
+
cancelled = true;
|
|
9651
|
+
stop();
|
|
9652
|
+
};
|
|
9653
|
+
};
|
|
9654
|
+
|
|
9565
9655
|
/**
|
|
9566
9656
|
* Whether a route action of the page on screen waits for app state to ask: its
|
|
9567
9657
|
* route matches and its `when` says no. Params that say no are not waiting:
|
|
@@ -9642,7 +9732,9 @@ const activeRouteReadsSignal = computed(() => {
|
|
|
9642
9732
|
* come from the address is not prefetched by a link — a link's prefetch has
|
|
9643
9733
|
* no address. It is by the press that opens a popup bound to a state these
|
|
9644
9734
|
* params read: that address is the current one with the state set to the
|
|
9645
|
-
* popup's value (see `prefetch` on Button).
|
|
9735
|
+
* popup's value (see `prefetch` on Button). And by `route.preload(params)`,
|
|
9736
|
+
* which names the address: what a `<RouteTravel prefetch>` asks of the pages
|
|
9737
|
+
* on each side of the one on screen.
|
|
9646
9738
|
* @param {object} [options]
|
|
9647
9739
|
* @param {() => any} [options.when] - a condition on app state the action
|
|
9648
9740
|
* asks under, read off signals like the params: falsy, it runs nothing.
|
|
@@ -9723,7 +9815,7 @@ const routeAction = (
|
|
|
9723
9815
|
for (const route of routes) {
|
|
9724
9816
|
if (paramsEffect) {
|
|
9725
9817
|
registerRouteStatePreload(route, (values) => {
|
|
9726
|
-
prefetchAsIf(action, readParams, values);
|
|
9818
|
+
prefetchAsIf(action, readParams, readParamsAsked, values);
|
|
9727
9819
|
});
|
|
9728
9820
|
} else {
|
|
9729
9821
|
registerRoutePreload(route, () => {
|
|
@@ -9739,15 +9831,17 @@ const routeAction = (
|
|
|
9739
9831
|
};
|
|
9740
9832
|
|
|
9741
9833
|
// 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
|
-
|
|
9834
|
+
// (see readStateAsIf), prerun only when they differ from what the action asks
|
|
9835
|
+
// now: params the states leave alone are the ones it already runs on — or is
|
|
9836
|
+
// meant not to, and a prerun would retry a FAILED one behind the screen's back.
|
|
9837
|
+
// An action whose route does not match asks nothing: whatever its params read,
|
|
9838
|
+
// the arrival runs it on them.
|
|
9839
|
+
const prefetchAsIf = (action, readParams, readParamsAsked, values) => {
|
|
9746
9840
|
const params = readStateAsIf(values, readParams);
|
|
9747
9841
|
if (!params) {
|
|
9748
9842
|
return;
|
|
9749
9843
|
}
|
|
9750
|
-
if (compareTwoJsValues(params, untracked(
|
|
9844
|
+
if (compareTwoJsValues(params, untracked(readParamsAsked))) {
|
|
9751
9845
|
return;
|
|
9752
9846
|
}
|
|
9753
9847
|
prerunAhead(action.bindParams(params));
|
|
@@ -28021,6 +28115,16 @@ const createResourceLifecycleManager = () => {
|
|
|
28021
28115
|
}
|
|
28022
28116
|
}
|
|
28023
28117
|
|
|
28118
|
+
// A head start the write made stale is dropped, not asked again: nothing
|
|
28119
|
+
// on screen reads it, and its owner, finding it idle, asks on arriving
|
|
28120
|
+
// instead of promoting what left before the write.
|
|
28121
|
+
for (const action of actionsToRerun) {
|
|
28122
|
+
if (action.isPrerunSignal.peek()) {
|
|
28123
|
+
actionsToRerun.delete(action);
|
|
28124
|
+
actionsToReset.add(action);
|
|
28125
|
+
}
|
|
28126
|
+
}
|
|
28127
|
+
|
|
28024
28128
|
return {
|
|
28025
28129
|
actionsToRerun,
|
|
28026
28130
|
actionsToReset,
|
|
@@ -28108,9 +28212,8 @@ const createResourceLifecycleManager = () => {
|
|
|
28108
28212
|
// A read to ask again after a write: answered before it landed, or still out —
|
|
28109
28213
|
// an answer that left before the write is as stale as a completed one, and
|
|
28110
28214
|
// would replace what the write put in the store (a rerun aborts it and asks
|
|
28111
|
-
// again). A head start is its owner's to ask.
|
|
28215
|
+
// again). A head start is its owner's to ask (findEffectOnActions resets it).
|
|
28112
28216
|
const isAnsweredOrAsking = (action, triggeringAction) =>
|
|
28113
|
-
!action.isPrerun &&
|
|
28114
28217
|
(action.completed || action.runningState === RUNNING) &&
|
|
28115
28218
|
action !== triggeringAction;
|
|
28116
28219
|
// A write named in `writes` says for itself which reads of its own resource it
|
|
@@ -40652,6 +40755,7 @@ const ROW_READING = {
|
|
|
40652
40755
|
* travelByDrag?: boolean,
|
|
40653
40756
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
40654
40757
|
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
40758
|
+
* prefetch?: boolean,
|
|
40655
40759
|
* }>}
|
|
40656
40760
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
40657
40761
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -40675,6 +40779,12 @@ const ROW_READING = {
|
|
|
40675
40779
|
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
40676
40780
|
* the height of the row, as the page is. Its route does not match while it stands
|
|
40677
40781
|
* there: what it reads is not asked for.
|
|
40782
|
+
* @param {boolean} [props.prefetch=false] - whether the pages on each side of the one
|
|
40783
|
+
* on screen have their reads asked for ahead of the hand: at rest, once the page on
|
|
40784
|
+
* screen has its answers and has painted them, the next page first, each with
|
|
40785
|
+
* `route.preload(params)`. A swipe then builds a page whose answers are already
|
|
40786
|
+
* there, with no wait to show. Off by default: every page one stops on costs the
|
|
40787
|
+
* reads of two more.
|
|
40678
40788
|
*
|
|
40679
40789
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
40680
40790
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -40691,6 +40801,7 @@ const RouteTravel = ({
|
|
|
40691
40801
|
routeTransition: "none"
|
|
40692
40802
|
}),
|
|
40693
40803
|
renderNeighbour,
|
|
40804
|
+
prefetch = false,
|
|
40694
40805
|
className,
|
|
40695
40806
|
children,
|
|
40696
40807
|
...rest
|
|
@@ -40700,7 +40811,8 @@ const RouteTravel = ({
|
|
|
40700
40811
|
const liveRef = useRef();
|
|
40701
40812
|
const picturesRef = useRef();
|
|
40702
40813
|
const windowed = Boolean(routesProp);
|
|
40703
|
-
|
|
40814
|
+
// Counts the movements ended: endMovement renders through it.
|
|
40815
|
+
const [restCount, render] = useReducer(count => count + 1, 0);
|
|
40704
40816
|
const pagesFromChildren = useMemo(() => collectRoutePages(children), [children]);
|
|
40705
40817
|
const pagesFromProp = useMemo(() => routesProp && routesProp.map(normalizePage), [routesProp]);
|
|
40706
40818
|
// Held while the row moves: a window recentred under a hand would move the very
|
|
@@ -41274,6 +41386,45 @@ const RouteTravel = ({
|
|
|
41274
41386
|
}
|
|
41275
41387
|
}, [currentIndex]);
|
|
41276
41388
|
|
|
41389
|
+
// What a hand can bring is asked for at rest, the next page first: never before
|
|
41390
|
+
// the page on screen has its answers and has painted them. A row in movement is
|
|
41391
|
+
// not at rest; the render that ends the movement asks again. Read as pages, not as
|
|
41392
|
+
// entries: the pages of <Route> children are collected again at every render.
|
|
41393
|
+
const neighboursRef = useRef([null, null]);
|
|
41394
|
+
const nextPage = currentIndex === -1 ? null : pages[currentIndex + 1] || null;
|
|
41395
|
+
const previousPage = currentIndex === -1 ? null : pages[currentIndex - 1] || null;
|
|
41396
|
+
if (!samePage(neighboursRef.current[0], nextPage) || !samePage(neighboursRef.current[1], previousPage)) {
|
|
41397
|
+
neighboursRef.current = [nextPage, previousPage];
|
|
41398
|
+
}
|
|
41399
|
+
const neighbours = neighboursRef.current;
|
|
41400
|
+
useLayoutEffect(() => {
|
|
41401
|
+
if (!prefetch || movementRef.current) {
|
|
41402
|
+
return undefined;
|
|
41403
|
+
}
|
|
41404
|
+
if (!neighbours[0] && !neighbours[1]) {
|
|
41405
|
+
return undefined;
|
|
41406
|
+
}
|
|
41407
|
+
let cancelPaint = null;
|
|
41408
|
+
const cancelWait = whenPageReadsAnswered(() => {
|
|
41409
|
+
cancelPaint = afterPaint(() => {
|
|
41410
|
+
if (movementRef.current) {
|
|
41411
|
+
return;
|
|
41412
|
+
}
|
|
41413
|
+
for (const page of neighbours) {
|
|
41414
|
+
if (page) {
|
|
41415
|
+
page.route.preload(page.params || {});
|
|
41416
|
+
}
|
|
41417
|
+
}
|
|
41418
|
+
});
|
|
41419
|
+
});
|
|
41420
|
+
return () => {
|
|
41421
|
+
cancelWait();
|
|
41422
|
+
if (cancelPaint) {
|
|
41423
|
+
cancelPaint();
|
|
41424
|
+
}
|
|
41425
|
+
};
|
|
41426
|
+
}, [prefetch, neighbours, restCount]);
|
|
41427
|
+
|
|
41277
41428
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
41278
41429
|
// any scroll event: a finger landing is the last word before that.
|
|
41279
41430
|
const paintsNeighbours = Boolean(renderNeighbour);
|