@jsenv/navi 0.29.461 → 0.29.463
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 +152 -62
- package/dist/dev/jsenv_navi.js.map +4 -4
- package/dist/jsenv_navi.js +152 -62
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/navigation.md +20 -10
- package/package.json +1 -1
package/dist/jsenv_navi.js
CHANGED
|
@@ -3307,10 +3307,17 @@ const updateActions = ({
|
|
|
3307
3307
|
const staysFailedSet = new Set();
|
|
3308
3308
|
const staysCompletedSet = new Set();
|
|
3309
3309
|
|
|
3310
|
+
// Every decision below reads an action's state from its signals. The plain
|
|
3311
|
+
// properties copying them (action.runningState, action.isPrerun) catch up
|
|
3312
|
+
// only once the effect or batch in progress is over: a reset asked just
|
|
3313
|
+
// before this update in the same effect — a kept answer the lookup found
|
|
3314
|
+
// expired — still reads as completed there, and the run asked after it
|
|
3315
|
+
// would join an answer that is gone.
|
|
3316
|
+
|
|
3310
3317
|
// Step 1: Determine which actions will be reset
|
|
3311
3318
|
{
|
|
3312
3319
|
for (const actionToReset of resetSet) {
|
|
3313
|
-
if (actionToReset.
|
|
3320
|
+
if (actionToReset.runningStateSignal.peek() !== IDLE) {
|
|
3314
3321
|
willResetSet.add(actionToReset);
|
|
3315
3322
|
}
|
|
3316
3323
|
}
|
|
@@ -3324,10 +3331,11 @@ const updateActions = ({
|
|
|
3324
3331
|
) => {
|
|
3325
3332
|
const isPrerun = requestType === "prerun";
|
|
3326
3333
|
const isRerun = requestType === "rerun";
|
|
3334
|
+
const runningState = action.runningStateSignal.peek();
|
|
3327
3335
|
|
|
3328
3336
|
if (
|
|
3329
3337
|
isRerun &&
|
|
3330
|
-
|
|
3338
|
+
runningState === COMPLETED &&
|
|
3331
3339
|
!willResetSet.has(action) &&
|
|
3332
3340
|
isRerunHeldByNetworkPolicy(action)
|
|
3333
3341
|
) {
|
|
@@ -3338,10 +3346,7 @@ const updateActions = ({
|
|
|
3338
3346
|
);
|
|
3339
3347
|
return;
|
|
3340
3348
|
}
|
|
3341
|
-
if (
|
|
3342
|
-
action.runningState === RUNNING ||
|
|
3343
|
-
action.runningState === COMPLETED
|
|
3344
|
-
) {
|
|
3349
|
+
if (runningState === RUNNING || runningState === COMPLETED) {
|
|
3345
3350
|
// Action is already running/completed
|
|
3346
3351
|
// By default, we don't interfere with already active actions
|
|
3347
3352
|
// Unless it's a rerun or the action is also being reset
|
|
@@ -3357,6 +3362,12 @@ const updateActions = ({
|
|
|
3357
3362
|
// The request performs nothing, and still has to be answered: what
|
|
3358
3363
|
// the caller asked for is the answer, not the running.
|
|
3359
3364
|
willJoinSet.add(action);
|
|
3365
|
+
if (requestType === "run" && action.isPrerunSignal.peek()) {
|
|
3366
|
+
// A prerun's run, or its answer, becomes the one this run asked
|
|
3367
|
+
// for. A prerun that failed or was aborted carries nothing to
|
|
3368
|
+
// promote: it is run like any other, in the branches below.
|
|
3369
|
+
willPromoteSet.add(action);
|
|
3370
|
+
}
|
|
3360
3371
|
}
|
|
3361
3372
|
} else if (isPrerun) {
|
|
3362
3373
|
willPrerunSet.add(action);
|
|
@@ -3380,13 +3391,6 @@ const updateActions = ({
|
|
|
3380
3391
|
// rerun wins over run - skip run
|
|
3381
3392
|
continue;
|
|
3382
3393
|
}
|
|
3383
|
-
if (actionToRun.isPrerun && actionToRun.runningState !== IDLE) {
|
|
3384
|
-
// Special case: action was prerun but not yet requested to run
|
|
3385
|
-
// Just promote it to "run requested" without rerunning
|
|
3386
|
-
willPromoteSet.add(actionToRun);
|
|
3387
|
-
willJoinSet.add(actionToRun);
|
|
3388
|
-
continue;
|
|
3389
|
-
}
|
|
3390
3394
|
handleActionRequest(actionToRun, "run");
|
|
3391
3395
|
}
|
|
3392
3396
|
|
|
@@ -3411,9 +3415,10 @@ const updateActions = ({
|
|
|
3411
3415
|
}
|
|
3412
3416
|
}
|
|
3413
3417
|
for (const actionSettled of settledSet) {
|
|
3414
|
-
|
|
3418
|
+
const runningState = actionSettled.runningStateSignal.peek();
|
|
3419
|
+
if (willResetSet.has(actionSettled)) ; else if (runningState === ABORTED) {
|
|
3415
3420
|
staysAbortedSet.add(actionSettled);
|
|
3416
|
-
} else if (
|
|
3421
|
+
} else if (runningState === FAILED) {
|
|
3417
3422
|
staysFailedSet.add(actionSettled);
|
|
3418
3423
|
} else {
|
|
3419
3424
|
staysCompletedSet.add(actionSettled);
|
|
@@ -11429,6 +11434,26 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
11429
11434
|
return active;
|
|
11430
11435
|
});
|
|
11431
11436
|
|
|
11437
|
+
/**
|
|
11438
|
+
* Whether a route action of the page on screen asks nothing yet: its route
|
|
11439
|
+
* matches and its params (or `when`) say no. Gated on app state not there yet,
|
|
11440
|
+
* or never meant to ask on this page — nothing tells one from the other before
|
|
11441
|
+
* it asks. Read by the scroll restoration, which stays ready for it.
|
|
11442
|
+
*
|
|
11443
|
+
* @type {import("@preact/signals").ReadonlySignal<boolean>}
|
|
11444
|
+
*/
|
|
11445
|
+
const routeActionYetToAskSignal = computed(() => {
|
|
11446
|
+
for (const {
|
|
11447
|
+
routeMatchingSignal,
|
|
11448
|
+
askingSignal,
|
|
11449
|
+
} of routeActionEntriesSignal.value) {
|
|
11450
|
+
if (routeMatchingSignal.value && !askingSignal.value) {
|
|
11451
|
+
return true;
|
|
11452
|
+
}
|
|
11453
|
+
}
|
|
11454
|
+
return false;
|
|
11455
|
+
});
|
|
11456
|
+
|
|
11432
11457
|
let activeRouteReadsPrevious = new Set();
|
|
11433
11458
|
/**
|
|
11434
11459
|
* `activeRouteActionsSignal` without the page's code: what to rerun to read
|
|
@@ -11542,6 +11567,7 @@ const routeAction = (
|
|
|
11542
11567
|
...routeActionEntriesSignal.peek(),
|
|
11543
11568
|
{
|
|
11544
11569
|
routeAction: actionBoundToRoute,
|
|
11570
|
+
routeMatchingSignal,
|
|
11545
11571
|
askingSignal: computed(() => Boolean(readParamsAsked())),
|
|
11546
11572
|
},
|
|
11547
11573
|
];
|
|
@@ -11698,10 +11724,11 @@ const haveSameMembers = (setA, setB) => {
|
|
|
11698
11724
|
* moment it is put back, and a position beyond what has arrived is clamped.
|
|
11699
11725
|
* What navi knows a first render left out, it waits for: a `<Box
|
|
11700
11726
|
* mount="after-paint">` is built before the offset that shows it is written
|
|
11701
|
-
* (see mount_after_paint.jsx), and a reload waits for the route
|
|
11702
|
-
*
|
|
11703
|
-
*
|
|
11704
|
-
*
|
|
11727
|
+
* (see mount_after_paint.jsx), and a reload or a return waits for the route
|
|
11728
|
+
* actions the page has nothing to draw for — its code among them — including
|
|
11729
|
+
* one that starts asking after the position is written (see
|
|
11730
|
+
* putBackOnceAnswered). Anything else only the page knows, and it says so
|
|
11731
|
+
* (useHoldScrollRestoration). A wait can be long, so the reader reaching for
|
|
11705
11732
|
* the page during it cancels the position: moved afterwards, they would lose
|
|
11706
11733
|
* whatever they had started on.
|
|
11707
11734
|
*
|
|
@@ -11816,12 +11843,13 @@ const suspendScrollRecording = () => {
|
|
|
11816
11843
|
};
|
|
11817
11844
|
};
|
|
11818
11845
|
|
|
11819
|
-
// The position a reload or a return is about to put back, until it is written
|
|
11820
|
-
//
|
|
11821
|
-
//
|
|
11822
|
-
//
|
|
11823
|
-
//
|
|
11824
|
-
// are is theirs (see
|
|
11846
|
+
// The position a reload or a return is about to put back, until it is written
|
|
11847
|
+
// into a page that will ask nothing more (see stayForLateAsks). Where the
|
|
11848
|
+
// document lands is the last arrival's to say: an arrival decided meanwhile — a
|
|
11849
|
+
// page that sends the reader elsewhere as it mounts — replaces it (see
|
|
11850
|
+
// startAtTop), and must not be scrolled to where the return was going. So does
|
|
11851
|
+
// the reader reaching for the page: where they are is theirs (see
|
|
11852
|
+
// installScrollRestoration).
|
|
11825
11853
|
let pendingRestoration = null;
|
|
11826
11854
|
const startRestoration = (position) => {
|
|
11827
11855
|
const restoration = { position, due: false };
|
|
@@ -11839,11 +11867,13 @@ const putBack = (restoration) => {
|
|
|
11839
11867
|
// The page is not whole yet: the last release puts it back.
|
|
11840
11868
|
return;
|
|
11841
11869
|
}
|
|
11842
|
-
pendingRestoration = null;
|
|
11843
11870
|
if (documentHoldCount) {
|
|
11871
|
+
pendingRestoration = null;
|
|
11844
11872
|
return;
|
|
11845
11873
|
}
|
|
11846
11874
|
scrollTo(restoration.position);
|
|
11875
|
+
restoration.due = false;
|
|
11876
|
+
stayForLateAsks(restoration);
|
|
11847
11877
|
};
|
|
11848
11878
|
|
|
11849
11879
|
// A list scrolling the document that opens on a row puts the document where
|
|
@@ -11901,9 +11931,10 @@ const holdScrollRestoration = () => {
|
|
|
11901
11931
|
* `held` turns false — unless the reader has reached for the page in the
|
|
11902
11932
|
* meantime (a finger, a click, a wheel, a key), or gone elsewhere.
|
|
11903
11933
|
*
|
|
11904
|
-
*
|
|
11905
|
-
*
|
|
11906
|
-
* cannot see: a read the page starts itself, content whose height
|
|
11934
|
+
* The page's route actions are waited for without this — its code, a read
|
|
11935
|
+
* with no answer yet, one gated on app state that asks later. This is for
|
|
11936
|
+
* what navi cannot see: a read the page starts itself, content whose height
|
|
11937
|
+
* arrives late.
|
|
11907
11938
|
*
|
|
11908
11939
|
* ```jsx
|
|
11909
11940
|
* const [games] = useAsyncData(GAMES, { loading: true, run: true });
|
|
@@ -11991,38 +12022,96 @@ const restoreScrollPositionOnLoad = (position) => {
|
|
|
11991
12022
|
const restoration = startRestoration(position);
|
|
11992
12023
|
const stopListening = observeRouteRender(() => {
|
|
11993
12024
|
stopListening();
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12025
|
+
putBackOnceAnswered(restoration);
|
|
12026
|
+
});
|
|
12027
|
+
};
|
|
12028
|
+
|
|
12029
|
+
// Reads the actions to wait for at once: called from the commit of a render,
|
|
12030
|
+
// the ones it had nothing to draw for are those still running then. An answer
|
|
12031
|
+
// can start another read (params made of that answer), so the actions asking
|
|
12032
|
+
// are read again before the position is written.
|
|
12033
|
+
//
|
|
12034
|
+
// The same for a reload and a return: a return to a page this document has
|
|
12035
|
+
// never drawn, or whose read was reset since, finds no answer either.
|
|
12036
|
+
const putBackOnceAnswered = (restoration) => {
|
|
12037
|
+
const unanswered = readUnansweredRouteActions();
|
|
12038
|
+
if (unanswered.length === 0) {
|
|
12039
|
+
queueMicrotask(() => {
|
|
12040
|
+
putBack(restoration);
|
|
12041
|
+
});
|
|
12042
|
+
return;
|
|
12043
|
+
}
|
|
12044
|
+
// A reader drawing its own wait subscribes in an effect, after the first
|
|
12045
|
+
// paint (see afterPaint): an answer that came before is drawn only then.
|
|
12046
|
+
let waitCount = 2;
|
|
12047
|
+
const waitOver = () => {
|
|
12048
|
+
waitCount--;
|
|
12049
|
+
if (waitCount === 0) {
|
|
12050
|
+
whenPageRendered(() => {
|
|
12051
|
+
putBackOnceAnswered(restoration);
|
|
12006
12052
|
});
|
|
12007
|
-
return;
|
|
12008
12053
|
}
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
|
|
12015
|
-
|
|
12016
|
-
|
|
12017
|
-
|
|
12054
|
+
};
|
|
12055
|
+
afterPaint(waitOver);
|
|
12056
|
+
whenSettled(unanswered, (answered) => {
|
|
12057
|
+
if (answered) {
|
|
12058
|
+
waitOver();
|
|
12059
|
+
}
|
|
12060
|
+
});
|
|
12061
|
+
};
|
|
12062
|
+
|
|
12063
|
+
const readUnansweredRouteActions = () => {
|
|
12064
|
+
const unanswered = [];
|
|
12065
|
+
for (const routeAction of activeRouteActionsSignal.peek()) {
|
|
12066
|
+
if (
|
|
12067
|
+
routeAction.runningStateSignal.peek() === RUNNING &&
|
|
12068
|
+
routeAction.dataSignal.peek() === undefined
|
|
12069
|
+
) {
|
|
12070
|
+
unanswered.push(routeAction);
|
|
12071
|
+
}
|
|
12072
|
+
}
|
|
12073
|
+
return unanswered;
|
|
12074
|
+
};
|
|
12075
|
+
|
|
12076
|
+
// A page can start asking after the position is written: an action gated on
|
|
12077
|
+
// app state not there yet (`when`, or params reading an app-wide read), and
|
|
12078
|
+
// nothing tells "not yet" from "never" before it asks (see
|
|
12079
|
+
// routeActionYetToAskSignal). So a written position stays pending while a
|
|
12080
|
+
// route action of the page has not asked: the next one that starts without an
|
|
12081
|
+
// answer is waited for like the first ones, and the position put back once it
|
|
12082
|
+
// is drawn. Not told when the restoration is dropped (pendingRestoration is no
|
|
12083
|
+
// signal): it notices at its next run.
|
|
12084
|
+
const stayForLateAsks = (restoration) => {
|
|
12085
|
+
effect(function () {
|
|
12086
|
+
let running = false;
|
|
12087
|
+
let starting = false;
|
|
12088
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
12089
|
+
if (routeAction.dataSignal.value !== undefined) {
|
|
12090
|
+
continue;
|
|
12018
12091
|
}
|
|
12019
|
-
|
|
12020
|
-
|
|
12021
|
-
|
|
12022
|
-
if (
|
|
12023
|
-
|
|
12092
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
12093
|
+
if (runningState === RUNNING) {
|
|
12094
|
+
running = true;
|
|
12095
|
+
} else if (runningState === IDLE) {
|
|
12096
|
+
// Asking, and run by an effect of the same batch that has not run yet.
|
|
12097
|
+
starting = true;
|
|
12024
12098
|
}
|
|
12025
|
-
}
|
|
12099
|
+
}
|
|
12100
|
+
const yetToAsk = routeActionYetToAskSignal.value;
|
|
12101
|
+
if (pendingRestoration !== restoration) {
|
|
12102
|
+
this.dispose();
|
|
12103
|
+
return;
|
|
12104
|
+
}
|
|
12105
|
+
if (running) {
|
|
12106
|
+
this.dispose();
|
|
12107
|
+
putBackOnceAnswered(restoration);
|
|
12108
|
+
return;
|
|
12109
|
+
}
|
|
12110
|
+
if (starting || yetToAsk) {
|
|
12111
|
+
return;
|
|
12112
|
+
}
|
|
12113
|
+
this.dispose();
|
|
12114
|
+
pendingRestoration = null;
|
|
12026
12115
|
});
|
|
12027
12116
|
};
|
|
12028
12117
|
|
|
@@ -12074,10 +12163,11 @@ const restoreScrollPosition = (url) => {
|
|
|
12074
12163
|
};
|
|
12075
12164
|
|
|
12076
12165
|
// A traversal back to `url`. The offset is written once the page returned to is
|
|
12077
|
-
// rendered: written sooner, the document
|
|
12078
|
-
//
|
|
12079
|
-
//
|
|
12080
|
-
//
|
|
12166
|
+
// rendered and answered (see putBackOnceAnswered): written sooner, the document
|
|
12167
|
+
// still holds the page being left, or a stand-in for the one arriving, and
|
|
12168
|
+
// clamps it to that height. It is read at once all the same — a page left
|
|
12169
|
+
// taller than the one arriving is clamped by the render itself, and that clamp
|
|
12170
|
+
// is a scroll recorded under the url already returned to.
|
|
12081
12171
|
const restoreScrollPositionOnReturn = (url) => {
|
|
12082
12172
|
pendingRestoration = null;
|
|
12083
12173
|
const href = new URL(url, window.location.href).href;
|
|
@@ -12088,7 +12178,7 @@ const restoreScrollPositionOnReturn = (url) => {
|
|
|
12088
12178
|
}
|
|
12089
12179
|
const restoration = startRestoration(position);
|
|
12090
12180
|
whenPageRendered(() => {
|
|
12091
|
-
|
|
12181
|
+
putBackOnceAnswered(restoration);
|
|
12092
12182
|
});
|
|
12093
12183
|
};
|
|
12094
12184
|
|