@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/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);
|
|
@@ -11857,6 +11862,26 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
11857
11862
|
return active;
|
|
11858
11863
|
});
|
|
11859
11864
|
|
|
11865
|
+
/**
|
|
11866
|
+
* Whether a route action of the page on screen asks nothing yet: its route
|
|
11867
|
+
* matches and its params (or `when`) say no. Gated on app state not there yet,
|
|
11868
|
+
* or never meant to ask on this page — nothing tells one from the other before
|
|
11869
|
+
* it asks. Read by the scroll restoration, which stays ready for it.
|
|
11870
|
+
*
|
|
11871
|
+
* @type {import("@preact/signals").ReadonlySignal<boolean>}
|
|
11872
|
+
*/
|
|
11873
|
+
const routeActionYetToAskSignal = computed(() => {
|
|
11874
|
+
for (const {
|
|
11875
|
+
routeMatchingSignal,
|
|
11876
|
+
askingSignal,
|
|
11877
|
+
} of routeActionEntriesSignal.value) {
|
|
11878
|
+
if (routeMatchingSignal.value && !askingSignal.value) {
|
|
11879
|
+
return true;
|
|
11880
|
+
}
|
|
11881
|
+
}
|
|
11882
|
+
return false;
|
|
11883
|
+
});
|
|
11884
|
+
|
|
11860
11885
|
let activeRouteReadsPrevious = new Set();
|
|
11861
11886
|
/**
|
|
11862
11887
|
* `activeRouteActionsSignal` without the page's code: what to rerun to read
|
|
@@ -11970,6 +11995,7 @@ const routeAction = (
|
|
|
11970
11995
|
...routeActionEntriesSignal.peek(),
|
|
11971
11996
|
{
|
|
11972
11997
|
routeAction: actionBoundToRoute,
|
|
11998
|
+
routeMatchingSignal,
|
|
11973
11999
|
askingSignal: computed(() => Boolean(readParamsAsked())),
|
|
11974
12000
|
},
|
|
11975
12001
|
];
|
|
@@ -12126,10 +12152,11 @@ const haveSameMembers = (setA, setB) => {
|
|
|
12126
12152
|
* moment it is put back, and a position beyond what has arrived is clamped.
|
|
12127
12153
|
* What navi knows a first render left out, it waits for: a `<Box
|
|
12128
12154
|
* mount="after-paint">` is built before the offset that shows it is written
|
|
12129
|
-
* (see mount_after_paint.jsx), and a reload waits for the route
|
|
12130
|
-
*
|
|
12131
|
-
*
|
|
12132
|
-
*
|
|
12155
|
+
* (see mount_after_paint.jsx), and a reload or a return waits for the route
|
|
12156
|
+
* actions the page has nothing to draw for — its code among them — including
|
|
12157
|
+
* one that starts asking after the position is written (see
|
|
12158
|
+
* putBackOnceAnswered). Anything else only the page knows, and it says so
|
|
12159
|
+
* (useHoldScrollRestoration). A wait can be long, so the reader reaching for
|
|
12133
12160
|
* the page during it cancels the position: moved afterwards, they would lose
|
|
12134
12161
|
* whatever they had started on.
|
|
12135
12162
|
*
|
|
@@ -12244,12 +12271,13 @@ const suspendScrollRecording = () => {
|
|
|
12244
12271
|
};
|
|
12245
12272
|
};
|
|
12246
12273
|
|
|
12247
|
-
// The position a reload or a return is about to put back, until it is written
|
|
12248
|
-
//
|
|
12249
|
-
//
|
|
12250
|
-
//
|
|
12251
|
-
//
|
|
12252
|
-
// are is theirs (see
|
|
12274
|
+
// The position a reload or a return is about to put back, until it is written
|
|
12275
|
+
// into a page that will ask nothing more (see stayForLateAsks). Where the
|
|
12276
|
+
// document lands is the last arrival's to say: an arrival decided meanwhile — a
|
|
12277
|
+
// page that sends the reader elsewhere as it mounts — replaces it (see
|
|
12278
|
+
// startAtTop), and must not be scrolled to where the return was going. So does
|
|
12279
|
+
// the reader reaching for the page: where they are is theirs (see
|
|
12280
|
+
// installScrollRestoration).
|
|
12253
12281
|
let pendingRestoration = null;
|
|
12254
12282
|
const startRestoration = (position) => {
|
|
12255
12283
|
const restoration = { position, due: false };
|
|
@@ -12267,11 +12295,13 @@ const putBack = (restoration) => {
|
|
|
12267
12295
|
// The page is not whole yet: the last release puts it back.
|
|
12268
12296
|
return;
|
|
12269
12297
|
}
|
|
12270
|
-
pendingRestoration = null;
|
|
12271
12298
|
if (documentHoldCount) {
|
|
12299
|
+
pendingRestoration = null;
|
|
12272
12300
|
return;
|
|
12273
12301
|
}
|
|
12274
12302
|
scrollTo(restoration.position);
|
|
12303
|
+
restoration.due = false;
|
|
12304
|
+
stayForLateAsks(restoration);
|
|
12275
12305
|
};
|
|
12276
12306
|
|
|
12277
12307
|
// A list scrolling the document that opens on a row puts the document where
|
|
@@ -12329,9 +12359,10 @@ const holdScrollRestoration = () => {
|
|
|
12329
12359
|
* `held` turns false — unless the reader has reached for the page in the
|
|
12330
12360
|
* meantime (a finger, a click, a wheel, a key), or gone elsewhere.
|
|
12331
12361
|
*
|
|
12332
|
-
*
|
|
12333
|
-
*
|
|
12334
|
-
* cannot see: a read the page starts itself, content whose height
|
|
12362
|
+
* The page's route actions are waited for without this — its code, a read
|
|
12363
|
+
* with no answer yet, one gated on app state that asks later. This is for
|
|
12364
|
+
* what navi cannot see: a read the page starts itself, content whose height
|
|
12365
|
+
* arrives late.
|
|
12335
12366
|
*
|
|
12336
12367
|
* ```jsx
|
|
12337
12368
|
* const [games] = useAsyncData(GAMES, { loading: true, run: true });
|
|
@@ -12419,38 +12450,96 @@ const restoreScrollPositionOnLoad = (position) => {
|
|
|
12419
12450
|
const restoration = startRestoration(position);
|
|
12420
12451
|
const stopListening = observeRouteRender(() => {
|
|
12421
12452
|
stopListening();
|
|
12422
|
-
|
|
12423
|
-
|
|
12424
|
-
|
|
12425
|
-
|
|
12426
|
-
|
|
12427
|
-
|
|
12428
|
-
|
|
12429
|
-
|
|
12430
|
-
|
|
12431
|
-
|
|
12432
|
-
|
|
12433
|
-
|
|
12453
|
+
putBackOnceAnswered(restoration);
|
|
12454
|
+
});
|
|
12455
|
+
};
|
|
12456
|
+
|
|
12457
|
+
// Reads the actions to wait for at once: called from the commit of a render,
|
|
12458
|
+
// the ones it had nothing to draw for are those still running then. An answer
|
|
12459
|
+
// can start another read (params made of that answer), so the actions asking
|
|
12460
|
+
// are read again before the position is written.
|
|
12461
|
+
//
|
|
12462
|
+
// The same for a reload and a return: a return to a page this document has
|
|
12463
|
+
// never drawn, or whose read was reset since, finds no answer either.
|
|
12464
|
+
const putBackOnceAnswered = (restoration) => {
|
|
12465
|
+
const unanswered = readUnansweredRouteActions();
|
|
12466
|
+
if (unanswered.length === 0) {
|
|
12467
|
+
queueMicrotask(() => {
|
|
12468
|
+
putBack(restoration);
|
|
12469
|
+
});
|
|
12470
|
+
return;
|
|
12471
|
+
}
|
|
12472
|
+
// A reader drawing its own wait subscribes in an effect, after the first
|
|
12473
|
+
// paint (see afterPaint): an answer that came before is drawn only then.
|
|
12474
|
+
let waitCount = 2;
|
|
12475
|
+
const waitOver = () => {
|
|
12476
|
+
waitCount--;
|
|
12477
|
+
if (waitCount === 0) {
|
|
12478
|
+
whenPageRendered(() => {
|
|
12479
|
+
putBackOnceAnswered(restoration);
|
|
12434
12480
|
});
|
|
12435
|
-
return;
|
|
12436
12481
|
}
|
|
12437
|
-
|
|
12438
|
-
|
|
12439
|
-
|
|
12440
|
-
|
|
12441
|
-
|
|
12442
|
-
|
|
12443
|
-
|
|
12444
|
-
|
|
12445
|
-
|
|
12482
|
+
};
|
|
12483
|
+
afterPaint(waitOver);
|
|
12484
|
+
whenSettled(unanswered, (answered) => {
|
|
12485
|
+
if (answered) {
|
|
12486
|
+
waitOver();
|
|
12487
|
+
}
|
|
12488
|
+
});
|
|
12489
|
+
};
|
|
12490
|
+
|
|
12491
|
+
const readUnansweredRouteActions = () => {
|
|
12492
|
+
const unanswered = [];
|
|
12493
|
+
for (const routeAction of activeRouteActionsSignal.peek()) {
|
|
12494
|
+
if (
|
|
12495
|
+
routeAction.runningStateSignal.peek() === RUNNING &&
|
|
12496
|
+
routeAction.dataSignal.peek() === undefined
|
|
12497
|
+
) {
|
|
12498
|
+
unanswered.push(routeAction);
|
|
12499
|
+
}
|
|
12500
|
+
}
|
|
12501
|
+
return unanswered;
|
|
12502
|
+
};
|
|
12503
|
+
|
|
12504
|
+
// A page can start asking after the position is written: an action gated on
|
|
12505
|
+
// app state not there yet (`when`, or params reading an app-wide read), and
|
|
12506
|
+
// nothing tells "not yet" from "never" before it asks (see
|
|
12507
|
+
// routeActionYetToAskSignal). So a written position stays pending while a
|
|
12508
|
+
// route action of the page has not asked: the next one that starts without an
|
|
12509
|
+
// answer is waited for like the first ones, and the position put back once it
|
|
12510
|
+
// is drawn. Not told when the restoration is dropped (pendingRestoration is no
|
|
12511
|
+
// signal): it notices at its next run.
|
|
12512
|
+
const stayForLateAsks = (restoration) => {
|
|
12513
|
+
effect(function () {
|
|
12514
|
+
let running = false;
|
|
12515
|
+
let starting = false;
|
|
12516
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
12517
|
+
if (routeAction.dataSignal.value !== undefined) {
|
|
12518
|
+
continue;
|
|
12446
12519
|
}
|
|
12447
|
-
|
|
12448
|
-
|
|
12449
|
-
|
|
12450
|
-
if (
|
|
12451
|
-
|
|
12520
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
12521
|
+
if (runningState === RUNNING) {
|
|
12522
|
+
running = true;
|
|
12523
|
+
} else if (runningState === IDLE) {
|
|
12524
|
+
// Asking, and run by an effect of the same batch that has not run yet.
|
|
12525
|
+
starting = true;
|
|
12452
12526
|
}
|
|
12453
|
-
}
|
|
12527
|
+
}
|
|
12528
|
+
const yetToAsk = routeActionYetToAskSignal.value;
|
|
12529
|
+
if (pendingRestoration !== restoration) {
|
|
12530
|
+
this.dispose();
|
|
12531
|
+
return;
|
|
12532
|
+
}
|
|
12533
|
+
if (running) {
|
|
12534
|
+
this.dispose();
|
|
12535
|
+
putBackOnceAnswered(restoration);
|
|
12536
|
+
return;
|
|
12537
|
+
}
|
|
12538
|
+
if (starting || yetToAsk) {
|
|
12539
|
+
return;
|
|
12540
|
+
}
|
|
12541
|
+
this.dispose();
|
|
12542
|
+
pendingRestoration = null;
|
|
12454
12543
|
});
|
|
12455
12544
|
};
|
|
12456
12545
|
|
|
@@ -12502,10 +12591,11 @@ const restoreScrollPosition = (url) => {
|
|
|
12502
12591
|
};
|
|
12503
12592
|
|
|
12504
12593
|
// A traversal back to `url`. The offset is written once the page returned to is
|
|
12505
|
-
// rendered: written sooner, the document
|
|
12506
|
-
//
|
|
12507
|
-
//
|
|
12508
|
-
//
|
|
12594
|
+
// rendered and answered (see putBackOnceAnswered): written sooner, the document
|
|
12595
|
+
// still holds the page being left, or a stand-in for the one arriving, and
|
|
12596
|
+
// clamps it to that height. It is read at once all the same — a page left
|
|
12597
|
+
// taller than the one arriving is clamped by the render itself, and that clamp
|
|
12598
|
+
// is a scroll recorded under the url already returned to.
|
|
12509
12599
|
const restoreScrollPositionOnReturn = (url) => {
|
|
12510
12600
|
pendingRestoration = null;
|
|
12511
12601
|
const href = new URL(url, window.location.href).href;
|
|
@@ -12516,7 +12606,7 @@ const restoreScrollPositionOnReturn = (url) => {
|
|
|
12516
12606
|
}
|
|
12517
12607
|
const restoration = startRestoration(position);
|
|
12518
12608
|
whenPageRendered(() => {
|
|
12519
|
-
|
|
12609
|
+
putBackOnceAnswered(restoration);
|
|
12520
12610
|
});
|
|
12521
12611
|
};
|
|
12522
12612
|
|