@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/dev/jsenv_navi.js
CHANGED
|
@@ -8787,17 +8787,35 @@ const route = (pattern, options = {}) => {
|
|
|
8787
8787
|
return `route "${cleanPattern}"`;
|
|
8788
8788
|
},
|
|
8789
8789
|
};
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8790
|
+
/**
|
|
8791
|
+
* Asks ahead of the arrival for what this route's actions will ask: the
|
|
8792
|
+
* instances the arrival promotes to runs (see prerunAhead in route_action.js).
|
|
8793
|
+
*
|
|
8794
|
+
* Without params, only what asks nothing of the address: the page's code, a
|
|
8795
|
+
* read without params — what a link preloads, since it has no state to read
|
|
8796
|
+
* the params of. Given params, `{}` included, the address `buildUrl(params)`
|
|
8797
|
+
* leads to: every route action of this route reads its params as if the
|
|
8798
|
+
* route matched there (see statesAt).
|
|
8799
|
+
*
|
|
8800
|
+
* @param {object} [params]
|
|
8801
|
+
*/
|
|
8802
|
+
route.preload = (params) => {
|
|
8795
8803
|
const preloadSet = routePreloadMap.get(route);
|
|
8796
|
-
if (
|
|
8804
|
+
if (preloadSet) {
|
|
8805
|
+
for (const preload of preloadSet) {
|
|
8806
|
+
preload();
|
|
8807
|
+
}
|
|
8808
|
+
}
|
|
8809
|
+
if (params === undefined) {
|
|
8797
8810
|
return;
|
|
8798
8811
|
}
|
|
8799
|
-
|
|
8800
|
-
|
|
8812
|
+
const statePreloadSet = routeStatePreloadMap.get(route);
|
|
8813
|
+
if (!statePreloadSet) {
|
|
8814
|
+
return;
|
|
8815
|
+
}
|
|
8816
|
+
const values = statesAt(route, params);
|
|
8817
|
+
for (const preload of statePreloadSet) {
|
|
8818
|
+
preload(values);
|
|
8801
8819
|
}
|
|
8802
8820
|
};
|
|
8803
8821
|
Object.preventExtensions(route);
|
|
@@ -9409,6 +9427,42 @@ const preloadState = (signal, value) => {
|
|
|
9409
9427
|
}
|
|
9410
9428
|
}
|
|
9411
9429
|
};
|
|
9430
|
+
// What the states of a route will hold once the address is `route.buildUrl(params)`,
|
|
9431
|
+
// as the routing pass will write them (see sync_routes_with_url): a param given is
|
|
9432
|
+
// what its state holds there; a weak one not given is back to its default, a url
|
|
9433
|
+
// built for another visit never carries it (readSignalForUrlBuild); any other one
|
|
9434
|
+
// is carried over by that url and keeps what it holds, so it is left out.
|
|
9435
|
+
const statesAt = (route, params) => {
|
|
9436
|
+
const values = new Map();
|
|
9437
|
+
for (const connection of getRoutePrivateProperties(route).routePattern
|
|
9438
|
+
.connections) {
|
|
9439
|
+
const { paramName, signal } = connection;
|
|
9440
|
+
if (!signal) {
|
|
9441
|
+
continue;
|
|
9442
|
+
}
|
|
9443
|
+
if (!(paramName in params)) {
|
|
9444
|
+
if (connection.weak) {
|
|
9445
|
+
values.set(signal, connection.getDefaultValue());
|
|
9446
|
+
}
|
|
9447
|
+
continue;
|
|
9448
|
+
}
|
|
9449
|
+
const value = params[paramName];
|
|
9450
|
+
if (value !== undefined) {
|
|
9451
|
+
values.set(signal, value);
|
|
9452
|
+
continue;
|
|
9453
|
+
}
|
|
9454
|
+
// An address silent about a persisted search param leaves it alone.
|
|
9455
|
+
if (
|
|
9456
|
+
connection.persists &&
|
|
9457
|
+
connection.paramType === "query" &&
|
|
9458
|
+
connection.history !== "push"
|
|
9459
|
+
) {
|
|
9460
|
+
continue;
|
|
9461
|
+
}
|
|
9462
|
+
values.set(signal, connection.getDefaultValue());
|
|
9463
|
+
}
|
|
9464
|
+
return values;
|
|
9465
|
+
};
|
|
9412
9466
|
const setupRoutes = (routes) => {
|
|
9413
9467
|
if (setupRoutesCalled) {
|
|
9414
9468
|
throw new Error(
|
|
@@ -9956,6 +10010,35 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
9956
10010
|
return active;
|
|
9957
10011
|
});
|
|
9958
10012
|
|
|
10013
|
+
/**
|
|
10014
|
+
* Calls back once every route action asking something for the page on screen
|
|
10015
|
+
* has answered, none running and none about to: what is asked on behalf of
|
|
10016
|
+
* another page waits for it, and never competes with the page's own reads.
|
|
10017
|
+
*
|
|
10018
|
+
* @param {() => void} callback
|
|
10019
|
+
* @returns {() => void} cancel
|
|
10020
|
+
*/
|
|
10021
|
+
const whenPageReadsAnswered = (callback) => {
|
|
10022
|
+
let cancelled = false;
|
|
10023
|
+
const stop = effect(function () {
|
|
10024
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
10025
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
10026
|
+
// IDLE: asking, and run by an effect of the same batch that has not run yet.
|
|
10027
|
+
if (runningState === RUNNING || runningState === IDLE) {
|
|
10028
|
+
return;
|
|
10029
|
+
}
|
|
10030
|
+
}
|
|
10031
|
+
this.dispose();
|
|
10032
|
+
if (!cancelled) {
|
|
10033
|
+
untracked(callback);
|
|
10034
|
+
}
|
|
10035
|
+
});
|
|
10036
|
+
return () => {
|
|
10037
|
+
cancelled = true;
|
|
10038
|
+
stop();
|
|
10039
|
+
};
|
|
10040
|
+
};
|
|
10041
|
+
|
|
9959
10042
|
/**
|
|
9960
10043
|
* Whether a route action of the page on screen waits for app state to ask: its
|
|
9961
10044
|
* route matches and its `when` says no. Params that say no are not waiting:
|
|
@@ -10044,7 +10127,9 @@ const ROUTE_ACTION_BINDING_OPTIONS = new Set([
|
|
|
10044
10127
|
* come from the address is not prefetched by a link — a link's prefetch has
|
|
10045
10128
|
* no address. It is by the press that opens a popup bound to a state these
|
|
10046
10129
|
* params read: that address is the current one with the state set to the
|
|
10047
|
-
* popup's value (see `prefetch` on Button).
|
|
10130
|
+
* popup's value (see `prefetch` on Button). And by `route.preload(params)`,
|
|
10131
|
+
* which names the address: what a `<RouteTravel prefetch>` asks of the pages
|
|
10132
|
+
* on each side of the one on screen.
|
|
10048
10133
|
* @param {object} [options]
|
|
10049
10134
|
* @param {() => any} [options.when] - a condition on app state the action
|
|
10050
10135
|
* asks under, read off signals like the params: falsy, it runs nothing.
|
|
@@ -10128,7 +10213,7 @@ const routeAction = (
|
|
|
10128
10213
|
for (const route of routes) {
|
|
10129
10214
|
if (paramsEffect) {
|
|
10130
10215
|
registerRouteStatePreload(route, (values) => {
|
|
10131
|
-
prefetchAsIf(action, readParams, values);
|
|
10216
|
+
prefetchAsIf(action, readParams, readParamsAsked, values);
|
|
10132
10217
|
});
|
|
10133
10218
|
} else {
|
|
10134
10219
|
registerRoutePreload(route, () => {
|
|
@@ -10144,15 +10229,17 @@ const routeAction = (
|
|
|
10144
10229
|
};
|
|
10145
10230
|
|
|
10146
10231
|
// 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
|
-
|
|
10232
|
+
// (see readStateAsIf), prerun only when they differ from what the action asks
|
|
10233
|
+
// now: params the states leave alone are the ones it already runs on — or is
|
|
10234
|
+
// meant not to, and a prerun would retry a FAILED one behind the screen's back.
|
|
10235
|
+
// An action whose route does not match asks nothing: whatever its params read,
|
|
10236
|
+
// the arrival runs it on them.
|
|
10237
|
+
const prefetchAsIf = (action, readParams, readParamsAsked, values) => {
|
|
10151
10238
|
const params = readStateAsIf(values, readParams);
|
|
10152
10239
|
if (!params) {
|
|
10153
10240
|
return;
|
|
10154
10241
|
}
|
|
10155
|
-
if (compareTwoJsValues(params, untracked(
|
|
10242
|
+
if (compareTwoJsValues(params, untracked(readParamsAsked))) {
|
|
10156
10243
|
return;
|
|
10157
10244
|
}
|
|
10158
10245
|
prerunAhead(action.bindParams(params));
|
|
@@ -41741,9 +41828,11 @@ const readRowStretch = (scroller, pages, page, { sideways } = {}) => {
|
|
|
41741
41828
|
/**
|
|
41742
41829
|
* @param {Element} scroller
|
|
41743
41830
|
* @param {object} options
|
|
41744
|
-
* @param {(move: {finger: boolean}) => void} options.onScroll - the
|
|
41745
|
-
* `finger`: a touch has come since the row last stopped, so a lift will
|
|
41746
|
-
* it goes (`onLeftToSnap`). Without one (a trackpad, a wheel) nothing
|
|
41831
|
+
* @param {(move: {finger: boolean, lifted: boolean}) => void} options.onScroll - the
|
|
41832
|
+
* row moved. `finger`: a touch has come since the row last stopped, so a lift will
|
|
41833
|
+
* say where it goes (`onLeftToSnap`). Without one (a trackpad, a wheel) nothing
|
|
41834
|
+
* will. `lifted`: that finger has lifted and none has landed since: no hand moves
|
|
41835
|
+
* the row, the snap the lift left running does.
|
|
41747
41836
|
* @param {(event: Event|null) => void} options.onLand - the row may have stopped.
|
|
41748
41837
|
* Read `pageAtRest` to know whether it stopped on a page. Heard more than once
|
|
41749
41838
|
* for one landing, so what it does must find nothing to do the second time.
|
|
@@ -41774,7 +41863,10 @@ const watchRowScroll = (
|
|
|
41774
41863
|
stoppedTimeout = setTimeout(landOnSilence, STOPPED_SILENCE);
|
|
41775
41864
|
};
|
|
41776
41865
|
const onScrollEvent = () => {
|
|
41777
|
-
onScroll({
|
|
41866
|
+
onScroll({
|
|
41867
|
+
finger: touchedSinceStop,
|
|
41868
|
+
lifted: touchedSinceStop && !touching,
|
|
41869
|
+
});
|
|
41778
41870
|
armSilence();
|
|
41779
41871
|
if (!liftedAt) {
|
|
41780
41872
|
return;
|
|
@@ -41938,6 +42030,7 @@ const ROW_READING = {
|
|
|
41938
42030
|
* travelByDrag?: boolean,
|
|
41939
42031
|
* onTravel?: (detail: {route: object, params: object|undefined, cause: string}) => void|Promise<void>,
|
|
41940
42032
|
* renderNeighbour?: (page: {route: object, params: object|undefined}) => import("preact").ComponentChildren,
|
|
42033
|
+
* prefetch?: boolean,
|
|
41941
42034
|
* }>}
|
|
41942
42035
|
* @param {Array<object|{route: object, params?: object}>} [props.routes] - a window
|
|
41943
42036
|
* over something longer, in order. Read from the <Route> children by default,
|
|
@@ -41961,6 +42054,12 @@ const ROW_READING = {
|
|
|
41961
42054
|
* `{ route, params }`, or the entry of `routes` as it was given, and is a column
|
|
41962
42055
|
* the height of the row, as the page is. Its route does not match while it stands
|
|
41963
42056
|
* there: what it reads is not asked for.
|
|
42057
|
+
* @param {boolean} [props.prefetch=false] - whether the pages on each side of the one
|
|
42058
|
+
* on screen have their reads asked for ahead of the hand: at rest, once the page on
|
|
42059
|
+
* screen has its answers and has painted them, the next page first, each with
|
|
42060
|
+
* `route.preload(params)`. A swipe then builds a page whose answers are already
|
|
42061
|
+
* there, with no wait to show. Off by default: every page one stops on costs the
|
|
42062
|
+
* reads of two more.
|
|
41964
42063
|
*
|
|
41965
42064
|
* Every other prop lands on the box itself (`id`, `data-testid`, `className`, …).
|
|
41966
42065
|
* The box has to outlive every page of its row: around those <Route>s, or around
|
|
@@ -41977,6 +42076,7 @@ const RouteTravel = ({
|
|
|
41977
42076
|
routeTransition: "none"
|
|
41978
42077
|
}),
|
|
41979
42078
|
renderNeighbour,
|
|
42079
|
+
prefetch = false,
|
|
41980
42080
|
className,
|
|
41981
42081
|
children,
|
|
41982
42082
|
...rest
|
|
@@ -41986,7 +42086,8 @@ const RouteTravel = ({
|
|
|
41986
42086
|
const liveRef = useRef();
|
|
41987
42087
|
const picturesRef = useRef();
|
|
41988
42088
|
const windowed = Boolean(routesProp);
|
|
41989
|
-
|
|
42089
|
+
// Counts the movements ended: endMovement renders through it.
|
|
42090
|
+
const [restCount, render] = useReducer(count => count + 1, 0);
|
|
41990
42091
|
const pagesFromChildren = useMemo(() => collectRoutePages(children), [children]);
|
|
41991
42092
|
const pagesFromProp = useMemo(() => routesProp && routesProp.map(normalizePage), [routesProp]);
|
|
41992
42093
|
// Held while the row moves: a window recentred under a hand would move the very
|
|
@@ -42358,7 +42459,8 @@ const RouteTravel = ({
|
|
|
42358
42459
|
const row = rowRef.current;
|
|
42359
42460
|
const isClipped = () => row.getAttribute("data-route-row-taken") === "clip";
|
|
42360
42461
|
const onScroll = ({
|
|
42361
|
-
finger
|
|
42462
|
+
finger,
|
|
42463
|
+
lifted
|
|
42362
42464
|
}) => {
|
|
42363
42465
|
const {
|
|
42364
42466
|
pages,
|
|
@@ -42393,6 +42495,12 @@ const RouteTravel = ({
|
|
|
42393
42495
|
} = readRowFrame(row, panes);
|
|
42394
42496
|
followPageOnScreen(pages[panes.indexOf(fraction >= 0.5 && toward ? toward : base)], askedPageRef.current || pages[currentIndex]);
|
|
42395
42497
|
}
|
|
42498
|
+
// The pull is read from the pane of the address. Once the finger has lifted,
|
|
42499
|
+
// the address is where the snap goes (onLeftToSnap), and what still lies
|
|
42500
|
+
// between is the snap playing out, not a hand pulling towards the neighbour.
|
|
42501
|
+
if (lifted) {
|
|
42502
|
+
return;
|
|
42503
|
+
}
|
|
42396
42504
|
const pulled = row.scrollLeft - pane.offsetLeft;
|
|
42397
42505
|
if (Math.abs(pulled) < SET_OFF_PULL) {
|
|
42398
42506
|
return;
|
|
@@ -42553,6 +42661,45 @@ const RouteTravel = ({
|
|
|
42553
42661
|
}
|
|
42554
42662
|
}, [currentIndex]);
|
|
42555
42663
|
|
|
42664
|
+
// What a hand can bring is asked for at rest, the next page first: never before
|
|
42665
|
+
// the page on screen has its answers and has painted them. A row in movement is
|
|
42666
|
+
// not at rest; the render that ends the movement asks again. Read as pages, not as
|
|
42667
|
+
// entries: the pages of <Route> children are collected again at every render.
|
|
42668
|
+
const neighboursRef = useRef([null, null]);
|
|
42669
|
+
const nextPage = currentIndex === -1 ? null : pages[currentIndex + 1] || null;
|
|
42670
|
+
const previousPage = currentIndex === -1 ? null : pages[currentIndex - 1] || null;
|
|
42671
|
+
if (!samePage(neighboursRef.current[0], nextPage) || !samePage(neighboursRef.current[1], previousPage)) {
|
|
42672
|
+
neighboursRef.current = [nextPage, previousPage];
|
|
42673
|
+
}
|
|
42674
|
+
const neighbours = neighboursRef.current;
|
|
42675
|
+
useLayoutEffect(() => {
|
|
42676
|
+
if (!prefetch || movementRef.current) {
|
|
42677
|
+
return undefined;
|
|
42678
|
+
}
|
|
42679
|
+
if (!neighbours[0] && !neighbours[1]) {
|
|
42680
|
+
return undefined;
|
|
42681
|
+
}
|
|
42682
|
+
let cancelPaint = null;
|
|
42683
|
+
const cancelWait = whenPageReadsAnswered(() => {
|
|
42684
|
+
cancelPaint = afterPaint(() => {
|
|
42685
|
+
if (movementRef.current) {
|
|
42686
|
+
return;
|
|
42687
|
+
}
|
|
42688
|
+
for (const page of neighbours) {
|
|
42689
|
+
if (page) {
|
|
42690
|
+
page.route.preload(page.params || {});
|
|
42691
|
+
}
|
|
42692
|
+
}
|
|
42693
|
+
});
|
|
42694
|
+
});
|
|
42695
|
+
return () => {
|
|
42696
|
+
cancelWait();
|
|
42697
|
+
if (cancelPaint) {
|
|
42698
|
+
cancelPaint();
|
|
42699
|
+
}
|
|
42700
|
+
};
|
|
42701
|
+
}, [prefetch, neighbours, restCount]);
|
|
42702
|
+
|
|
42556
42703
|
// A neighbour shows from the first frame the compositor moves the row, before
|
|
42557
42704
|
// any scroll event: a finger landing is the last word before that.
|
|
42558
42705
|
const paintsNeighbours = Boolean(renderNeighbour);
|