@jsenv/navi 0.29.462 → 0.29.464
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 +116 -68
- package/dist/dev/jsenv_navi.js.map +4 -4
- package/dist/jsenv_navi.js +116 -68
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/navigation.md +35 -19
- package/package.json +1 -1
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -11837,6 +11837,8 @@ const keptNameWeakMap = new WeakMap();
|
|
|
11837
11837
|
const getRouteActionKeptName = (routeAction) => {
|
|
11838
11838
|
return keptNameWeakMap.get(routeAction);
|
|
11839
11839
|
};
|
|
11840
|
+
// Shared by every route action without a `when`.
|
|
11841
|
+
const NOT_GATED_SIGNAL = signal(false);
|
|
11840
11842
|
let activeRouteActionsPrevious = new Set();
|
|
11841
11843
|
/**
|
|
11842
11844
|
* The route actions asking something for the page on screen: their route
|
|
@@ -11862,6 +11864,24 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
11862
11864
|
return active;
|
|
11863
11865
|
});
|
|
11864
11866
|
|
|
11867
|
+
/**
|
|
11868
|
+
* Whether a route action of the page on screen waits for app state to ask: its
|
|
11869
|
+
* route matches and its `when` says no. Params that say no are not waiting:
|
|
11870
|
+
* they read the address, and this one asks nothing of the action — a later ask
|
|
11871
|
+
* is the page changing under the reader (a search typed), not arriving. Read
|
|
11872
|
+
* by the scroll restoration, which stays ready for it.
|
|
11873
|
+
*
|
|
11874
|
+
* @type {import("@preact/signals").ReadonlySignal<boolean>}
|
|
11875
|
+
*/
|
|
11876
|
+
const routeActionYetToAskSignal = computed(() => {
|
|
11877
|
+
for (const { gatedSignal } of routeActionEntriesSignal.value) {
|
|
11878
|
+
if (gatedSignal.value) {
|
|
11879
|
+
return true;
|
|
11880
|
+
}
|
|
11881
|
+
}
|
|
11882
|
+
return false;
|
|
11883
|
+
});
|
|
11884
|
+
|
|
11865
11885
|
let activeRouteReadsPrevious = new Set();
|
|
11866
11886
|
/**
|
|
11867
11887
|
* `activeRouteActionsSignal` without the page's code: what to rerun to read
|
|
@@ -11928,7 +11948,9 @@ const activeRouteReadsSignal = computed(() => {
|
|
|
11928
11948
|
* params when the condition holds at that moment — a page's code only some
|
|
11929
11949
|
* readers may download (`() => meIsAdminSignal.value`). A condition reading
|
|
11930
11950
|
* the address belongs in the params: a link reads `when` as it is, not as
|
|
11931
|
-
* the address it leads to will make it.
|
|
11951
|
+
* the address it leads to will make it. It is also what a reload's scroll
|
|
11952
|
+
* position waits for: the page is not whole while a `when` says no, whereas
|
|
11953
|
+
* params that say no ask nothing on this page (see scroll_restoration.js).
|
|
11932
11954
|
* @param {boolean} [options.prefetch=true] - `false` keeps the action from
|
|
11933
11955
|
* being prerun ahead of what asks for it, when the read is not worth a
|
|
11934
11956
|
* hover or a press that may not end in an opening.
|
|
@@ -11976,6 +11998,9 @@ const routeAction = (
|
|
|
11976
11998
|
{
|
|
11977
11999
|
routeAction: actionBoundToRoute,
|
|
11978
12000
|
askingSignal: computed(() => Boolean(readParamsAsked())),
|
|
12001
|
+
gatedSignal: when
|
|
12002
|
+
? computed(() => Boolean(routeMatchingSignal.value) && !when())
|
|
12003
|
+
: NOT_GATED_SIGNAL,
|
|
11979
12004
|
},
|
|
11980
12005
|
];
|
|
11981
12006
|
for (const route of routes) {
|
|
@@ -12131,11 +12156,11 @@ const haveSameMembers = (setA, setB) => {
|
|
|
12131
12156
|
* moment it is put back, and a position beyond what has arrived is clamped.
|
|
12132
12157
|
* What navi knows a first render left out, it waits for: a `<Box
|
|
12133
12158
|
* mount="after-paint">` is built before the offset that shows it is written
|
|
12134
|
-
* (see mount_after_paint.jsx), and a reload waits for the route
|
|
12135
|
-
*
|
|
12136
|
-
*
|
|
12137
|
-
*
|
|
12138
|
-
*
|
|
12159
|
+
* (see mount_after_paint.jsx), and a reload or a return waits for the route
|
|
12160
|
+
* actions the page has nothing to draw for — its code among them — including
|
|
12161
|
+
* one whose `when` lets it ask after the position is written (see
|
|
12162
|
+
* stayForLateAsks). Anything else only the page knows, and it says so
|
|
12163
|
+
* (useHoldScrollRestoration). A wait can be long, so the reader reaching for
|
|
12139
12164
|
* the page during it cancels the position: moved afterwards, they would lose
|
|
12140
12165
|
* whatever they had started on.
|
|
12141
12166
|
*
|
|
@@ -12250,17 +12275,16 @@ const suspendScrollRecording = () => {
|
|
|
12250
12275
|
};
|
|
12251
12276
|
};
|
|
12252
12277
|
|
|
12253
|
-
// The position a reload or a return is about to put back, until it is written
|
|
12254
|
-
//
|
|
12255
|
-
//
|
|
12256
|
-
//
|
|
12257
|
-
//
|
|
12258
|
-
// are is theirs (see
|
|
12278
|
+
// The position a reload or a return is about to put back, until it is written
|
|
12279
|
+
// into a page that will ask nothing more (see stayForLateAsks). Where the
|
|
12280
|
+
// document lands is the last arrival's to say: an arrival decided meanwhile — a
|
|
12281
|
+
// page that sends the reader elsewhere as it mounts — replaces it (see
|
|
12282
|
+
// startAtTop), and must not be scrolled to where the return was going. So does
|
|
12283
|
+
// the reader reaching for the page: where they are is theirs (see
|
|
12284
|
+
// installScrollRestoration).
|
|
12259
12285
|
let pendingRestoration = null;
|
|
12260
|
-
|
|
12261
|
-
|
|
12262
|
-
const startRestoration = (position, { untilReached = false } = {}) => {
|
|
12263
|
-
const restoration = { position, due: false, untilReached };
|
|
12286
|
+
const startRestoration = (position) => {
|
|
12287
|
+
const restoration = { position, due: false };
|
|
12264
12288
|
pendingRestoration = restoration;
|
|
12265
12289
|
return restoration;
|
|
12266
12290
|
};
|
|
@@ -12275,16 +12299,13 @@ const putBack = (restoration) => {
|
|
|
12275
12299
|
// The page is not whole yet: the last release puts it back.
|
|
12276
12300
|
return;
|
|
12277
12301
|
}
|
|
12278
|
-
pendingRestoration = null;
|
|
12279
12302
|
if (documentHoldCount) {
|
|
12303
|
+
pendingRestoration = null;
|
|
12280
12304
|
return;
|
|
12281
12305
|
}
|
|
12282
12306
|
scrollTo(restoration.position);
|
|
12283
|
-
|
|
12284
|
-
|
|
12285
|
-
pendingRestoration = restoration;
|
|
12286
|
-
putBackOnLateAnswer(restoration);
|
|
12287
|
-
}
|
|
12307
|
+
restoration.due = false;
|
|
12308
|
+
stayForLateAsks(restoration);
|
|
12288
12309
|
};
|
|
12289
12310
|
|
|
12290
12311
|
// A list scrolling the document that opens on a row puts the document where
|
|
@@ -12342,9 +12363,10 @@ const holdScrollRestoration = () => {
|
|
|
12342
12363
|
* `held` turns false — unless the reader has reached for the page in the
|
|
12343
12364
|
* meantime (a finger, a click, a wheel, a key), or gone elsewhere.
|
|
12344
12365
|
*
|
|
12345
|
-
*
|
|
12346
|
-
*
|
|
12347
|
-
* cannot see: a read the page starts itself, content whose height
|
|
12366
|
+
* The page's route actions are waited for without this — its code, a read
|
|
12367
|
+
* with no answer yet, one whose `when` holds later. This is for
|
|
12368
|
+
* what navi cannot see: a read the page starts itself, content whose height
|
|
12369
|
+
* arrives late.
|
|
12348
12370
|
*
|
|
12349
12371
|
* ```jsx
|
|
12350
12372
|
* const [games] = useAsyncData(GAMES, { loading: true, run: true });
|
|
@@ -12429,19 +12451,22 @@ const installScrollRestoration = ({ readAddress }) => {
|
|
|
12429
12451
|
// page the position was taken on, and the position stays for the reload that
|
|
12430
12452
|
// brings the page back.
|
|
12431
12453
|
const restoreScrollPositionOnLoad = (position) => {
|
|
12432
|
-
const restoration = startRestoration(position
|
|
12454
|
+
const restoration = startRestoration(position);
|
|
12433
12455
|
const stopListening = observeRouteRender(() => {
|
|
12434
12456
|
stopListening();
|
|
12435
12457
|
putBackOnceAnswered(restoration);
|
|
12436
12458
|
});
|
|
12437
12459
|
};
|
|
12438
12460
|
|
|
12439
|
-
// Reads the actions to wait for at once: from the commit of
|
|
12461
|
+
// Reads the actions to wait for at once: called from the commit of a render,
|
|
12440
12462
|
// the ones it had nothing to draw for are those still running then. An answer
|
|
12441
12463
|
// can start another read (params made of that answer), so the actions asking
|
|
12442
12464
|
// are read again before the position is written.
|
|
12465
|
+
//
|
|
12466
|
+
// The same for a reload and a return: a return to a page this document has
|
|
12467
|
+
// never drawn, or whose read was reset since, finds no answer either.
|
|
12443
12468
|
const putBackOnceAnswered = (restoration) => {
|
|
12444
|
-
const unanswered =
|
|
12469
|
+
const unanswered = readUnansweredRouteActions();
|
|
12445
12470
|
if (unanswered.length === 0) {
|
|
12446
12471
|
queueMicrotask(() => {
|
|
12447
12472
|
putBack(restoration);
|
|
@@ -12469,10 +12494,10 @@ const putBackOnceAnswered = (restoration) => {
|
|
|
12469
12494
|
|
|
12470
12495
|
const readUnansweredRouteActions = () => {
|
|
12471
12496
|
const unanswered = [];
|
|
12472
|
-
for (const routeAction of activeRouteActionsSignal.
|
|
12497
|
+
for (const routeAction of activeRouteActionsSignal.peek()) {
|
|
12473
12498
|
if (
|
|
12474
|
-
routeAction.runningStateSignal.
|
|
12475
|
-
routeAction.dataSignal.
|
|
12499
|
+
routeAction.runningStateSignal.peek() === RUNNING &&
|
|
12500
|
+
routeAction.dataSignal.peek() === undefined
|
|
12476
12501
|
) {
|
|
12477
12502
|
unanswered.push(routeAction);
|
|
12478
12503
|
}
|
|
@@ -12480,33 +12505,49 @@ const readUnansweredRouteActions = () => {
|
|
|
12480
12505
|
return unanswered;
|
|
12481
12506
|
};
|
|
12482
12507
|
|
|
12483
|
-
// A page can
|
|
12484
|
-
//
|
|
12485
|
-
//
|
|
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
|
|
12508
|
+
// A page can still be arriving after the position is written: an action whose
|
|
12509
|
+
// `when` waits for app state not there yet (see routeActionYetToAskSignal). So
|
|
12510
|
+
// a written position stays pending while one does: the next one that starts
|
|
12488
12511
|
// without an answer is waited for like the first ones, and the position put
|
|
12489
|
-
// back
|
|
12490
|
-
//
|
|
12491
|
-
|
|
12512
|
+
// back once it is drawn. Params that say no are not waited for: the address
|
|
12513
|
+
// asks nothing of the action, and one that asks later — a search typed — is
|
|
12514
|
+
// the page moving on under the reader, not arriving. Not told when the
|
|
12515
|
+
// restoration is dropped (pendingRestoration is no signal): it notices at its
|
|
12516
|
+
// next run.
|
|
12517
|
+
const stayForLateAsks = (restoration) => {
|
|
12492
12518
|
effect(function () {
|
|
12519
|
+
let running = false;
|
|
12520
|
+
let starting = false;
|
|
12521
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
12522
|
+
if (routeAction.dataSignal.value !== undefined) {
|
|
12523
|
+
continue;
|
|
12524
|
+
}
|
|
12525
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
12526
|
+
if (runningState === RUNNING) {
|
|
12527
|
+
running = true;
|
|
12528
|
+
} else if (runningState === IDLE) {
|
|
12529
|
+
// Asking, and run by an effect of the same batch that has not run yet.
|
|
12530
|
+
starting = true;
|
|
12531
|
+
}
|
|
12532
|
+
}
|
|
12533
|
+
const yetToAsk = routeActionYetToAskSignal.value;
|
|
12493
12534
|
if (pendingRestoration !== restoration) {
|
|
12494
12535
|
this.dispose();
|
|
12495
12536
|
return;
|
|
12496
12537
|
}
|
|
12497
|
-
if (
|
|
12538
|
+
if (running) {
|
|
12539
|
+
this.dispose();
|
|
12540
|
+
putBackOnceAnswered(restoration);
|
|
12541
|
+
return;
|
|
12542
|
+
}
|
|
12543
|
+
if (starting || yetToAsk) {
|
|
12498
12544
|
return;
|
|
12499
12545
|
}
|
|
12500
12546
|
this.dispose();
|
|
12501
|
-
|
|
12547
|
+
pendingRestoration = null;
|
|
12502
12548
|
});
|
|
12503
12549
|
};
|
|
12504
12550
|
|
|
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
|
-
|
|
12510
12551
|
// Calls back once none of the actions is running, saying whether all of them
|
|
12511
12552
|
// completed.
|
|
12512
12553
|
const whenSettled = (actions, callback) => {
|
|
@@ -12555,10 +12596,11 @@ const restoreScrollPosition = (url) => {
|
|
|
12555
12596
|
};
|
|
12556
12597
|
|
|
12557
12598
|
// A traversal back to `url`. The offset is written once the page returned to is
|
|
12558
|
-
// rendered: written sooner, the document
|
|
12559
|
-
//
|
|
12560
|
-
//
|
|
12561
|
-
//
|
|
12599
|
+
// rendered and answered (see putBackOnceAnswered): written sooner, the document
|
|
12600
|
+
// still holds the page being left, or a stand-in for the one arriving, and
|
|
12601
|
+
// clamps it to that height. It is read at once all the same — a page left
|
|
12602
|
+
// taller than the one arriving is clamped by the render itself, and that clamp
|
|
12603
|
+
// is a scroll recorded under the url already returned to.
|
|
12562
12604
|
const restoreScrollPositionOnReturn = (url) => {
|
|
12563
12605
|
pendingRestoration = null;
|
|
12564
12606
|
const href = new URL(url, window.location.href).href;
|
|
@@ -12569,7 +12611,7 @@ const restoreScrollPositionOnReturn = (url) => {
|
|
|
12569
12611
|
}
|
|
12570
12612
|
const restoration = startRestoration(position);
|
|
12571
12613
|
whenPageRendered(() => {
|
|
12572
|
-
|
|
12614
|
+
putBackOnceAnswered(restoration);
|
|
12573
12615
|
});
|
|
12574
12616
|
};
|
|
12575
12617
|
|
|
@@ -84383,6 +84425,24 @@ const PickerButton = props => {
|
|
|
84383
84425
|
clearConfirm,
|
|
84384
84426
|
requestValue
|
|
84385
84427
|
};
|
|
84428
|
+
// `--navi-select`/`--navi-unselect` about one entry of the list the picker
|
|
84429
|
+
// holds — a chip on the façade, a suggestion beside the field. Answered here
|
|
84430
|
+
// rather than by the control drawing that list in the popup, even though rows
|
|
84431
|
+
// are what such a control owns: a picker given its own value builds its popup
|
|
84432
|
+
// only on first open (see popup_content_mount.js), so before that there is no
|
|
84433
|
+
// such control at all — and building the whole popup to have someone to talk
|
|
84434
|
+
// to, for a cross, is the wrong price. The picker holds the value in the first
|
|
84435
|
+
// place and hands it down whenever the popup is built.
|
|
84436
|
+
//
|
|
84437
|
+
// Heard on both elements, like every request a control answers: a chip inside
|
|
84438
|
+
// the picker aims at its root (the parent control), one standing elsewhere
|
|
84439
|
+
// names it with `commandFor`, and the picker's id is on its input.
|
|
84440
|
+
const onRequestSelect = e => {
|
|
84441
|
+
requestPickerListEntry(ref.current, inputRef.current, e, "select");
|
|
84442
|
+
};
|
|
84443
|
+
const onRequestUnselect = e => {
|
|
84444
|
+
requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
|
|
84445
|
+
};
|
|
84386
84446
|
return (
|
|
84387
84447
|
/* Read-only crosses into everything the picker is made of: what it really
|
|
84388
84448
|
holds is drawn by controls of their own — in the popup, and on the façade
|
|
@@ -84443,23 +84503,9 @@ const PickerButton = props => {
|
|
|
84443
84503
|
,
|
|
84444
84504
|
onnavi_request_open: inputProps.onnavi_request_open,
|
|
84445
84505
|
onnavi_request_close: inputProps.onnavi_request_close,
|
|
84446
|
-
onnavi_open_press: inputProps.onnavi_open_press
|
|
84447
|
-
|
|
84448
|
-
|
|
84449
|
-
// Answered here rather than by the control drawing that list in the
|
|
84450
|
-
// popup, even though rows are what such a control owns: a picker given
|
|
84451
|
-
// its own value builds its popup only on first open (see
|
|
84452
|
-
// popup_content_mount.js), so before that there is no such control at
|
|
84453
|
-
// all — and building the whole popup to have someone to talk to, for a
|
|
84454
|
-
// cross, is the wrong price. The picker holds the value in the first
|
|
84455
|
-
// place and hands it down whenever the popup is built.
|
|
84456
|
-
,
|
|
84457
|
-
onnavi_request_select: e => {
|
|
84458
|
-
requestPickerListEntry(ref.current, inputRef.current, e, "select");
|
|
84459
|
-
},
|
|
84460
|
-
onnavi_request_unselect: e => {
|
|
84461
|
-
requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
|
|
84462
|
-
},
|
|
84506
|
+
onnavi_open_press: inputProps.onnavi_open_press,
|
|
84507
|
+
onnavi_request_select: onRequestSelect,
|
|
84508
|
+
onnavi_request_unselect: onRequestUnselect,
|
|
84463
84509
|
children: [jsx("span", {
|
|
84464
84510
|
className: "navi_picker_box",
|
|
84465
84511
|
style: hangingChevronSize === undefined ? undefined : {
|
|
@@ -84481,6 +84527,8 @@ const PickerButton = props => {
|
|
|
84481
84527
|
children: undefined // we will render children into the div
|
|
84482
84528
|
,
|
|
84483
84529
|
ui: ui,
|
|
84530
|
+
onnavi_request_select: onRequestSelect,
|
|
84531
|
+
onnavi_request_unselect: onRequestUnselect,
|
|
84484
84532
|
onCopy: e => {
|
|
84485
84533
|
const pickerEl = ref.current;
|
|
84486
84534
|
if (isWithinPickerContent(e.target, pickerEl)) {
|