@jsenv/navi 0.29.461 → 0.29.462
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 +101 -48
- package/dist/dev/jsenv_navi.js.map +3 -3
- package/dist/jsenv_navi.js +101 -48
- package/dist/jsenv_navi.js.map +3 -3
- package/docs/navigation.md +9 -0
- 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);
|
|
@@ -11699,7 +11704,8 @@ const haveSameMembers = (setA, setB) => {
|
|
|
11699
11704
|
* What navi knows a first render left out, it waits for: a `<Box
|
|
11700
11705
|
* mount="after-paint">` is built before the offset that shows it is written
|
|
11701
11706
|
* (see mount_after_paint.jsx), and a reload waits for the route actions that
|
|
11702
|
-
* render had nothing to draw for — the page's code among them
|
|
11707
|
+
* render had nothing to draw for — the page's code among them — and for one
|
|
11708
|
+
* that starts asking later, when the position did not fit what was there (see
|
|
11703
11709
|
* restoreScrollPositionOnLoad). Anything else only the page knows, and it says
|
|
11704
11710
|
* so (useHoldScrollRestoration). A wait can be long, so the reader reaching for
|
|
11705
11711
|
* the page during it cancels the position: moved afterwards, they would lose
|
|
@@ -11823,8 +11829,10 @@ const suspendScrollRecording = () => {
|
|
|
11823
11829
|
// So does the reader reaching for the page before it is written: where they
|
|
11824
11830
|
// are is theirs (see installScrollRestoration).
|
|
11825
11831
|
let pendingRestoration = null;
|
|
11826
|
-
|
|
11827
|
-
|
|
11832
|
+
// `untilReached`: a write the document is too short for leaves it pending (see
|
|
11833
|
+
// putBackOnLateAnswer).
|
|
11834
|
+
const startRestoration = (position, { untilReached = false } = {}) => {
|
|
11835
|
+
const restoration = { position, due: false, untilReached };
|
|
11828
11836
|
pendingRestoration = restoration;
|
|
11829
11837
|
return restoration;
|
|
11830
11838
|
};
|
|
@@ -11844,6 +11852,11 @@ const putBack = (restoration) => {
|
|
|
11844
11852
|
return;
|
|
11845
11853
|
}
|
|
11846
11854
|
scrollTo(restoration.position);
|
|
11855
|
+
if (restoration.untilReached && fellShortOf(restoration.position)) {
|
|
11856
|
+
restoration.due = false;
|
|
11857
|
+
pendingRestoration = restoration;
|
|
11858
|
+
putBackOnLateAnswer(restoration);
|
|
11859
|
+
}
|
|
11847
11860
|
};
|
|
11848
11861
|
|
|
11849
11862
|
// A list scrolling the document that opens on a row puts the document where
|
|
@@ -11988,44 +12001,84 @@ const installScrollRestoration = ({ readAddress }) => {
|
|
|
11988
12001
|
// page the position was taken on, and the position stays for the reload that
|
|
11989
12002
|
// brings the page back.
|
|
11990
12003
|
const restoreScrollPositionOnLoad = (position) => {
|
|
11991
|
-
const restoration = startRestoration(position);
|
|
12004
|
+
const restoration = startRestoration(position, { untilReached: true });
|
|
11992
12005
|
const stopListening = observeRouteRender(() => {
|
|
11993
12006
|
stopListening();
|
|
11994
|
-
|
|
11995
|
-
|
|
11996
|
-
|
|
11997
|
-
|
|
11998
|
-
|
|
11999
|
-
|
|
12000
|
-
|
|
12001
|
-
|
|
12002
|
-
|
|
12003
|
-
|
|
12004
|
-
|
|
12005
|
-
|
|
12007
|
+
putBackOnceAnswered(restoration);
|
|
12008
|
+
});
|
|
12009
|
+
};
|
|
12010
|
+
|
|
12011
|
+
// Reads the actions to wait for at once: from the commit of the first render,
|
|
12012
|
+
// the ones it had nothing to draw for are those still running then. An answer
|
|
12013
|
+
// can start another read (params made of that answer), so the actions asking
|
|
12014
|
+
// are read again before the position is written.
|
|
12015
|
+
const putBackOnceAnswered = (restoration) => {
|
|
12016
|
+
const unanswered = untracked(readUnansweredRouteActions);
|
|
12017
|
+
if (unanswered.length === 0) {
|
|
12018
|
+
queueMicrotask(() => {
|
|
12019
|
+
putBack(restoration);
|
|
12020
|
+
});
|
|
12021
|
+
return;
|
|
12022
|
+
}
|
|
12023
|
+
// A reader drawing its own wait subscribes in an effect, after the first
|
|
12024
|
+
// paint (see afterPaint): an answer that came before is drawn only then.
|
|
12025
|
+
let waitCount = 2;
|
|
12026
|
+
const waitOver = () => {
|
|
12027
|
+
waitCount--;
|
|
12028
|
+
if (waitCount === 0) {
|
|
12029
|
+
whenPageRendered(() => {
|
|
12030
|
+
putBackOnceAnswered(restoration);
|
|
12006
12031
|
});
|
|
12032
|
+
}
|
|
12033
|
+
};
|
|
12034
|
+
afterPaint(waitOver);
|
|
12035
|
+
whenSettled(unanswered, (answered) => {
|
|
12036
|
+
if (answered) {
|
|
12037
|
+
waitOver();
|
|
12038
|
+
}
|
|
12039
|
+
});
|
|
12040
|
+
};
|
|
12041
|
+
|
|
12042
|
+
const readUnansweredRouteActions = () => {
|
|
12043
|
+
const unanswered = [];
|
|
12044
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
12045
|
+
if (
|
|
12046
|
+
routeAction.runningStateSignal.value === RUNNING &&
|
|
12047
|
+
routeAction.dataSignal.value === undefined
|
|
12048
|
+
) {
|
|
12049
|
+
unanswered.push(routeAction);
|
|
12050
|
+
}
|
|
12051
|
+
}
|
|
12052
|
+
return unanswered;
|
|
12053
|
+
};
|
|
12054
|
+
|
|
12055
|
+
// A page can start asking after its first render: an action gated on app
|
|
12056
|
+
// state that render did not have yet (`when`, or params reading an app-wide
|
|
12057
|
+
// read), and nothing tells "not yet" from "never" before it asks. So the
|
|
12058
|
+
// position is written into what the page lets it, and a write the document
|
|
12059
|
+
// could not hold is not the end of it: the next route action that starts
|
|
12060
|
+
// without an answer is waited for like the first ones, and the position put
|
|
12061
|
+
// back again. Not told when the restoration is dropped (pendingRestoration is
|
|
12062
|
+
// no signal): it notices at its next run.
|
|
12063
|
+
const putBackOnLateAnswer = (restoration) => {
|
|
12064
|
+
effect(function () {
|
|
12065
|
+
if (pendingRestoration !== restoration) {
|
|
12066
|
+
this.dispose();
|
|
12007
12067
|
return;
|
|
12008
12068
|
}
|
|
12009
|
-
|
|
12010
|
-
|
|
12011
|
-
|
|
12012
|
-
|
|
12013
|
-
|
|
12014
|
-
if (waitCount === 0) {
|
|
12015
|
-
whenPageRendered(() => {
|
|
12016
|
-
putBack(restoration);
|
|
12017
|
-
});
|
|
12018
|
-
}
|
|
12019
|
-
};
|
|
12020
|
-
afterPaint(waitOver);
|
|
12021
|
-
whenSettled(unanswered, (answered) => {
|
|
12022
|
-
if (answered) {
|
|
12023
|
-
waitOver();
|
|
12024
|
-
}
|
|
12025
|
-
});
|
|
12069
|
+
if (readUnansweredRouteActions().length === 0) {
|
|
12070
|
+
return;
|
|
12071
|
+
}
|
|
12072
|
+
this.dispose();
|
|
12073
|
+
putBackOnceAnswered(restoration);
|
|
12026
12074
|
});
|
|
12027
12075
|
};
|
|
12028
12076
|
|
|
12077
|
+
// A pixel of slack: the offset kept is the fractional one the browser reports.
|
|
12078
|
+
const fellShortOf = (position) => {
|
|
12079
|
+
return position.y - window.scrollY > 1;
|
|
12080
|
+
};
|
|
12081
|
+
|
|
12029
12082
|
// Calls back once none of the actions is running, saying whether all of them
|
|
12030
12083
|
// completed.
|
|
12031
12084
|
const whenSettled = (actions, callback) => {
|