@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/dev/jsenv_navi.js
CHANGED
|
@@ -3424,10 +3424,17 @@ ${lines.join("\n")}`,
|
|
|
3424
3424
|
const staysFailedSet = new Set();
|
|
3425
3425
|
const staysCompletedSet = new Set();
|
|
3426
3426
|
|
|
3427
|
+
// Every decision below reads an action's state from its signals. The plain
|
|
3428
|
+
// properties copying them (action.runningState, action.isPrerun) catch up
|
|
3429
|
+
// only once the effect or batch in progress is over: a reset asked just
|
|
3430
|
+
// before this update in the same effect — a kept answer the lookup found
|
|
3431
|
+
// expired — still reads as completed there, and the run asked after it
|
|
3432
|
+
// would join an answer that is gone.
|
|
3433
|
+
|
|
3427
3434
|
// Step 1: Determine which actions will be reset
|
|
3428
3435
|
{
|
|
3429
3436
|
for (const actionToReset of resetSet) {
|
|
3430
|
-
if (actionToReset.
|
|
3437
|
+
if (actionToReset.runningStateSignal.peek() !== IDLE) {
|
|
3431
3438
|
willResetSet.add(actionToReset);
|
|
3432
3439
|
}
|
|
3433
3440
|
}
|
|
@@ -3441,10 +3448,11 @@ ${lines.join("\n")}`,
|
|
|
3441
3448
|
) => {
|
|
3442
3449
|
const isPrerun = requestType === "prerun";
|
|
3443
3450
|
const isRerun = requestType === "rerun";
|
|
3451
|
+
const runningState = action.runningStateSignal.peek();
|
|
3444
3452
|
|
|
3445
3453
|
if (
|
|
3446
3454
|
isRerun &&
|
|
3447
|
-
|
|
3455
|
+
runningState === COMPLETED &&
|
|
3448
3456
|
!willResetSet.has(action) &&
|
|
3449
3457
|
isRerunHeldByNetworkPolicy(action)
|
|
3450
3458
|
) {
|
|
@@ -3455,10 +3463,7 @@ ${lines.join("\n")}`,
|
|
|
3455
3463
|
);
|
|
3456
3464
|
return;
|
|
3457
3465
|
}
|
|
3458
|
-
if (
|
|
3459
|
-
action.runningState === RUNNING ||
|
|
3460
|
-
action.runningState === COMPLETED
|
|
3461
|
-
) {
|
|
3466
|
+
if (runningState === RUNNING || runningState === COMPLETED) {
|
|
3462
3467
|
// Action is already running/completed
|
|
3463
3468
|
// By default, we don't interfere with already active actions
|
|
3464
3469
|
// Unless it's a rerun or the action is also being reset
|
|
@@ -3474,6 +3479,12 @@ ${lines.join("\n")}`,
|
|
|
3474
3479
|
// The request performs nothing, and still has to be answered: what
|
|
3475
3480
|
// the caller asked for is the answer, not the running.
|
|
3476
3481
|
willJoinSet.add(action);
|
|
3482
|
+
if (requestType === "run" && action.isPrerunSignal.peek()) {
|
|
3483
|
+
// A prerun's run, or its answer, becomes the one this run asked
|
|
3484
|
+
// for. A prerun that failed or was aborted carries nothing to
|
|
3485
|
+
// promote: it is run like any other, in the branches below.
|
|
3486
|
+
willPromoteSet.add(action);
|
|
3487
|
+
}
|
|
3477
3488
|
}
|
|
3478
3489
|
} else if (isPrerun) {
|
|
3479
3490
|
willPrerunSet.add(action);
|
|
@@ -3497,13 +3508,6 @@ ${lines.join("\n")}`,
|
|
|
3497
3508
|
// rerun wins over run - skip run
|
|
3498
3509
|
continue;
|
|
3499
3510
|
}
|
|
3500
|
-
if (actionToRun.isPrerun && actionToRun.runningState !== IDLE) {
|
|
3501
|
-
// Special case: action was prerun but not yet requested to run
|
|
3502
|
-
// Just promote it to "run requested" without rerunning
|
|
3503
|
-
willPromoteSet.add(actionToRun);
|
|
3504
|
-
willJoinSet.add(actionToRun);
|
|
3505
|
-
continue;
|
|
3506
|
-
}
|
|
3507
3511
|
handleActionRequest(actionToRun, "run");
|
|
3508
3512
|
}
|
|
3509
3513
|
|
|
@@ -3528,9 +3532,10 @@ ${lines.join("\n")}`,
|
|
|
3528
3532
|
}
|
|
3529
3533
|
}
|
|
3530
3534
|
for (const actionSettled of settledSet) {
|
|
3531
|
-
|
|
3535
|
+
const runningState = actionSettled.runningStateSignal.peek();
|
|
3536
|
+
if (willResetSet.has(actionSettled)) ; else if (runningState === ABORTED) {
|
|
3532
3537
|
staysAbortedSet.add(actionSettled);
|
|
3533
|
-
} else if (
|
|
3538
|
+
} else if (runningState === FAILED) {
|
|
3534
3539
|
staysFailedSet.add(actionSettled);
|
|
3535
3540
|
} else {
|
|
3536
3541
|
staysCompletedSet.add(actionSettled);
|
|
@@ -12127,7 +12132,8 @@ const haveSameMembers = (setA, setB) => {
|
|
|
12127
12132
|
* What navi knows a first render left out, it waits for: a `<Box
|
|
12128
12133
|
* mount="after-paint">` is built before the offset that shows it is written
|
|
12129
12134
|
* (see mount_after_paint.jsx), and a reload waits for the route actions that
|
|
12130
|
-
* render had nothing to draw for — the page's code among them
|
|
12135
|
+
* render had nothing to draw for — the page's code among them — and for one
|
|
12136
|
+
* that starts asking later, when the position did not fit what was there (see
|
|
12131
12137
|
* restoreScrollPositionOnLoad). Anything else only the page knows, and it says
|
|
12132
12138
|
* so (useHoldScrollRestoration). A wait can be long, so the reader reaching for
|
|
12133
12139
|
* the page during it cancels the position: moved afterwards, they would lose
|
|
@@ -12251,8 +12257,10 @@ const suspendScrollRecording = () => {
|
|
|
12251
12257
|
// So does the reader reaching for the page before it is written: where they
|
|
12252
12258
|
// are is theirs (see installScrollRestoration).
|
|
12253
12259
|
let pendingRestoration = null;
|
|
12254
|
-
|
|
12255
|
-
|
|
12260
|
+
// `untilReached`: a write the document is too short for leaves it pending (see
|
|
12261
|
+
// putBackOnLateAnswer).
|
|
12262
|
+
const startRestoration = (position, { untilReached = false } = {}) => {
|
|
12263
|
+
const restoration = { position, due: false, untilReached };
|
|
12256
12264
|
pendingRestoration = restoration;
|
|
12257
12265
|
return restoration;
|
|
12258
12266
|
};
|
|
@@ -12272,6 +12280,11 @@ const putBack = (restoration) => {
|
|
|
12272
12280
|
return;
|
|
12273
12281
|
}
|
|
12274
12282
|
scrollTo(restoration.position);
|
|
12283
|
+
if (restoration.untilReached && fellShortOf(restoration.position)) {
|
|
12284
|
+
restoration.due = false;
|
|
12285
|
+
pendingRestoration = restoration;
|
|
12286
|
+
putBackOnLateAnswer(restoration);
|
|
12287
|
+
}
|
|
12275
12288
|
};
|
|
12276
12289
|
|
|
12277
12290
|
// A list scrolling the document that opens on a row puts the document where
|
|
@@ -12416,44 +12429,84 @@ const installScrollRestoration = ({ readAddress }) => {
|
|
|
12416
12429
|
// page the position was taken on, and the position stays for the reload that
|
|
12417
12430
|
// brings the page back.
|
|
12418
12431
|
const restoreScrollPositionOnLoad = (position) => {
|
|
12419
|
-
const restoration = startRestoration(position);
|
|
12432
|
+
const restoration = startRestoration(position, { untilReached: true });
|
|
12420
12433
|
const stopListening = observeRouteRender(() => {
|
|
12421
12434
|
stopListening();
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12435
|
+
putBackOnceAnswered(restoration);
|
|
12436
|
+
});
|
|
12437
|
+
};
|
|
12438
|
+
|
|
12439
|
+
// Reads the actions to wait for at once: from the commit of the first render,
|
|
12440
|
+
// the ones it had nothing to draw for are those still running then. An answer
|
|
12441
|
+
// can start another read (params made of that answer), so the actions asking
|
|
12442
|
+
// are read again before the position is written.
|
|
12443
|
+
const putBackOnceAnswered = (restoration) => {
|
|
12444
|
+
const unanswered = untracked(readUnansweredRouteActions);
|
|
12445
|
+
if (unanswered.length === 0) {
|
|
12446
|
+
queueMicrotask(() => {
|
|
12447
|
+
putBack(restoration);
|
|
12448
|
+
});
|
|
12449
|
+
return;
|
|
12450
|
+
}
|
|
12451
|
+
// A reader drawing its own wait subscribes in an effect, after the first
|
|
12452
|
+
// paint (see afterPaint): an answer that came before is drawn only then.
|
|
12453
|
+
let waitCount = 2;
|
|
12454
|
+
const waitOver = () => {
|
|
12455
|
+
waitCount--;
|
|
12456
|
+
if (waitCount === 0) {
|
|
12457
|
+
whenPageRendered(() => {
|
|
12458
|
+
putBackOnceAnswered(restoration);
|
|
12434
12459
|
});
|
|
12460
|
+
}
|
|
12461
|
+
};
|
|
12462
|
+
afterPaint(waitOver);
|
|
12463
|
+
whenSettled(unanswered, (answered) => {
|
|
12464
|
+
if (answered) {
|
|
12465
|
+
waitOver();
|
|
12466
|
+
}
|
|
12467
|
+
});
|
|
12468
|
+
};
|
|
12469
|
+
|
|
12470
|
+
const readUnansweredRouteActions = () => {
|
|
12471
|
+
const unanswered = [];
|
|
12472
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
12473
|
+
if (
|
|
12474
|
+
routeAction.runningStateSignal.value === RUNNING &&
|
|
12475
|
+
routeAction.dataSignal.value === undefined
|
|
12476
|
+
) {
|
|
12477
|
+
unanswered.push(routeAction);
|
|
12478
|
+
}
|
|
12479
|
+
}
|
|
12480
|
+
return unanswered;
|
|
12481
|
+
};
|
|
12482
|
+
|
|
12483
|
+
// A page can start asking after its first render: an action gated on app
|
|
12484
|
+
// state that render did not have yet (`when`, or params reading an app-wide
|
|
12485
|
+
// read), and nothing tells "not yet" from "never" before it asks. So the
|
|
12486
|
+
// position is written into what the page lets it, and a write the document
|
|
12487
|
+
// could not hold is not the end of it: the next route action that starts
|
|
12488
|
+
// without an answer is waited for like the first ones, and the position put
|
|
12489
|
+
// back again. Not told when the restoration is dropped (pendingRestoration is
|
|
12490
|
+
// no signal): it notices at its next run.
|
|
12491
|
+
const putBackOnLateAnswer = (restoration) => {
|
|
12492
|
+
effect(function () {
|
|
12493
|
+
if (pendingRestoration !== restoration) {
|
|
12494
|
+
this.dispose();
|
|
12435
12495
|
return;
|
|
12436
12496
|
}
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
if (waitCount === 0) {
|
|
12443
|
-
whenPageRendered(() => {
|
|
12444
|
-
putBack(restoration);
|
|
12445
|
-
});
|
|
12446
|
-
}
|
|
12447
|
-
};
|
|
12448
|
-
afterPaint(waitOver);
|
|
12449
|
-
whenSettled(unanswered, (answered) => {
|
|
12450
|
-
if (answered) {
|
|
12451
|
-
waitOver();
|
|
12452
|
-
}
|
|
12453
|
-
});
|
|
12497
|
+
if (readUnansweredRouteActions().length === 0) {
|
|
12498
|
+
return;
|
|
12499
|
+
}
|
|
12500
|
+
this.dispose();
|
|
12501
|
+
putBackOnceAnswered(restoration);
|
|
12454
12502
|
});
|
|
12455
12503
|
};
|
|
12456
12504
|
|
|
12505
|
+
// A pixel of slack: the offset kept is the fractional one the browser reports.
|
|
12506
|
+
const fellShortOf = (position) => {
|
|
12507
|
+
return position.y - window.scrollY > 1;
|
|
12508
|
+
};
|
|
12509
|
+
|
|
12457
12510
|
// Calls back once none of the actions is running, saying whether all of them
|
|
12458
12511
|
// completed.
|
|
12459
12512
|
const whenSettled = (actions, callback) => {
|