@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/jsenv_navi.js
CHANGED
|
@@ -11409,6 +11409,8 @@ const keptNameWeakMap = new WeakMap();
|
|
|
11409
11409
|
const getRouteActionKeptName = (routeAction) => {
|
|
11410
11410
|
return keptNameWeakMap.get(routeAction);
|
|
11411
11411
|
};
|
|
11412
|
+
// Shared by every route action without a `when`.
|
|
11413
|
+
const NOT_GATED_SIGNAL = signal(false);
|
|
11412
11414
|
let activeRouteActionsPrevious = new Set();
|
|
11413
11415
|
/**
|
|
11414
11416
|
* The route actions asking something for the page on screen: their route
|
|
@@ -11434,6 +11436,24 @@ const activeRouteActionsSignal = computed(() => {
|
|
|
11434
11436
|
return active;
|
|
11435
11437
|
});
|
|
11436
11438
|
|
|
11439
|
+
/**
|
|
11440
|
+
* Whether a route action of the page on screen waits for app state to ask: its
|
|
11441
|
+
* route matches and its `when` says no. Params that say no are not waiting:
|
|
11442
|
+
* they read the address, and this one asks nothing of the action — a later ask
|
|
11443
|
+
* is the page changing under the reader (a search typed), not arriving. Read
|
|
11444
|
+
* by the scroll restoration, which stays ready for it.
|
|
11445
|
+
*
|
|
11446
|
+
* @type {import("@preact/signals").ReadonlySignal<boolean>}
|
|
11447
|
+
*/
|
|
11448
|
+
const routeActionYetToAskSignal = computed(() => {
|
|
11449
|
+
for (const { gatedSignal } of routeActionEntriesSignal.value) {
|
|
11450
|
+
if (gatedSignal.value) {
|
|
11451
|
+
return true;
|
|
11452
|
+
}
|
|
11453
|
+
}
|
|
11454
|
+
return false;
|
|
11455
|
+
});
|
|
11456
|
+
|
|
11437
11457
|
let activeRouteReadsPrevious = new Set();
|
|
11438
11458
|
/**
|
|
11439
11459
|
* `activeRouteActionsSignal` without the page's code: what to rerun to read
|
|
@@ -11500,7 +11520,9 @@ const activeRouteReadsSignal = computed(() => {
|
|
|
11500
11520
|
* params when the condition holds at that moment — a page's code only some
|
|
11501
11521
|
* readers may download (`() => meIsAdminSignal.value`). A condition reading
|
|
11502
11522
|
* the address belongs in the params: a link reads `when` as it is, not as
|
|
11503
|
-
* the address it leads to will make it.
|
|
11523
|
+
* the address it leads to will make it. It is also what a reload's scroll
|
|
11524
|
+
* position waits for: the page is not whole while a `when` says no, whereas
|
|
11525
|
+
* params that say no ask nothing on this page (see scroll_restoration.js).
|
|
11504
11526
|
* @param {boolean} [options.prefetch=true] - `false` keeps the action from
|
|
11505
11527
|
* being prerun ahead of what asks for it, when the read is not worth a
|
|
11506
11528
|
* hover or a press that may not end in an opening.
|
|
@@ -11548,6 +11570,9 @@ const routeAction = (
|
|
|
11548
11570
|
{
|
|
11549
11571
|
routeAction: actionBoundToRoute,
|
|
11550
11572
|
askingSignal: computed(() => Boolean(readParamsAsked())),
|
|
11573
|
+
gatedSignal: when
|
|
11574
|
+
? computed(() => Boolean(routeMatchingSignal.value) && !when())
|
|
11575
|
+
: NOT_GATED_SIGNAL,
|
|
11551
11576
|
},
|
|
11552
11577
|
];
|
|
11553
11578
|
for (const route of routes) {
|
|
@@ -11703,11 +11728,11 @@ const haveSameMembers = (setA, setB) => {
|
|
|
11703
11728
|
* moment it is put back, and a position beyond what has arrived is clamped.
|
|
11704
11729
|
* What navi knows a first render left out, it waits for: a `<Box
|
|
11705
11730
|
* mount="after-paint">` is built before the offset that shows it is written
|
|
11706
|
-
* (see mount_after_paint.jsx), and a reload waits for the route
|
|
11707
|
-
*
|
|
11708
|
-
*
|
|
11709
|
-
*
|
|
11710
|
-
*
|
|
11731
|
+
* (see mount_after_paint.jsx), and a reload or a return waits for the route
|
|
11732
|
+
* actions the page has nothing to draw for — its code among them — including
|
|
11733
|
+
* one whose `when` lets it ask after the position is written (see
|
|
11734
|
+
* stayForLateAsks). Anything else only the page knows, and it says so
|
|
11735
|
+
* (useHoldScrollRestoration). A wait can be long, so the reader reaching for
|
|
11711
11736
|
* the page during it cancels the position: moved afterwards, they would lose
|
|
11712
11737
|
* whatever they had started on.
|
|
11713
11738
|
*
|
|
@@ -11822,17 +11847,16 @@ const suspendScrollRecording = () => {
|
|
|
11822
11847
|
};
|
|
11823
11848
|
};
|
|
11824
11849
|
|
|
11825
|
-
// The position a reload or a return is about to put back, until it is written
|
|
11826
|
-
//
|
|
11827
|
-
//
|
|
11828
|
-
//
|
|
11829
|
-
//
|
|
11830
|
-
// are is theirs (see
|
|
11850
|
+
// The position a reload or a return is about to put back, until it is written
|
|
11851
|
+
// into a page that will ask nothing more (see stayForLateAsks). Where the
|
|
11852
|
+
// document lands is the last arrival's to say: an arrival decided meanwhile — a
|
|
11853
|
+
// page that sends the reader elsewhere as it mounts — replaces it (see
|
|
11854
|
+
// startAtTop), and must not be scrolled to where the return was going. So does
|
|
11855
|
+
// the reader reaching for the page: where they are is theirs (see
|
|
11856
|
+
// installScrollRestoration).
|
|
11831
11857
|
let pendingRestoration = null;
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
const startRestoration = (position, { untilReached = false } = {}) => {
|
|
11835
|
-
const restoration = { position, due: false, untilReached };
|
|
11858
|
+
const startRestoration = (position) => {
|
|
11859
|
+
const restoration = { position, due: false };
|
|
11836
11860
|
pendingRestoration = restoration;
|
|
11837
11861
|
return restoration;
|
|
11838
11862
|
};
|
|
@@ -11847,16 +11871,13 @@ const putBack = (restoration) => {
|
|
|
11847
11871
|
// The page is not whole yet: the last release puts it back.
|
|
11848
11872
|
return;
|
|
11849
11873
|
}
|
|
11850
|
-
pendingRestoration = null;
|
|
11851
11874
|
if (documentHoldCount) {
|
|
11875
|
+
pendingRestoration = null;
|
|
11852
11876
|
return;
|
|
11853
11877
|
}
|
|
11854
11878
|
scrollTo(restoration.position);
|
|
11855
|
-
|
|
11856
|
-
|
|
11857
|
-
pendingRestoration = restoration;
|
|
11858
|
-
putBackOnLateAnswer(restoration);
|
|
11859
|
-
}
|
|
11879
|
+
restoration.due = false;
|
|
11880
|
+
stayForLateAsks(restoration);
|
|
11860
11881
|
};
|
|
11861
11882
|
|
|
11862
11883
|
// A list scrolling the document that opens on a row puts the document where
|
|
@@ -11914,9 +11935,10 @@ const holdScrollRestoration = () => {
|
|
|
11914
11935
|
* `held` turns false — unless the reader has reached for the page in the
|
|
11915
11936
|
* meantime (a finger, a click, a wheel, a key), or gone elsewhere.
|
|
11916
11937
|
*
|
|
11917
|
-
*
|
|
11918
|
-
*
|
|
11919
|
-
* cannot see: a read the page starts itself, content whose height
|
|
11938
|
+
* The page's route actions are waited for without this — its code, a read
|
|
11939
|
+
* with no answer yet, one whose `when` holds later. This is for
|
|
11940
|
+
* what navi cannot see: a read the page starts itself, content whose height
|
|
11941
|
+
* arrives late.
|
|
11920
11942
|
*
|
|
11921
11943
|
* ```jsx
|
|
11922
11944
|
* const [games] = useAsyncData(GAMES, { loading: true, run: true });
|
|
@@ -12001,19 +12023,22 @@ const installScrollRestoration = ({ readAddress }) => {
|
|
|
12001
12023
|
// page the position was taken on, and the position stays for the reload that
|
|
12002
12024
|
// brings the page back.
|
|
12003
12025
|
const restoreScrollPositionOnLoad = (position) => {
|
|
12004
|
-
const restoration = startRestoration(position
|
|
12026
|
+
const restoration = startRestoration(position);
|
|
12005
12027
|
const stopListening = observeRouteRender(() => {
|
|
12006
12028
|
stopListening();
|
|
12007
12029
|
putBackOnceAnswered(restoration);
|
|
12008
12030
|
});
|
|
12009
12031
|
};
|
|
12010
12032
|
|
|
12011
|
-
// Reads the actions to wait for at once: from the commit of
|
|
12033
|
+
// Reads the actions to wait for at once: called from the commit of a render,
|
|
12012
12034
|
// the ones it had nothing to draw for are those still running then. An answer
|
|
12013
12035
|
// can start another read (params made of that answer), so the actions asking
|
|
12014
12036
|
// are read again before the position is written.
|
|
12037
|
+
//
|
|
12038
|
+
// The same for a reload and a return: a return to a page this document has
|
|
12039
|
+
// never drawn, or whose read was reset since, finds no answer either.
|
|
12015
12040
|
const putBackOnceAnswered = (restoration) => {
|
|
12016
|
-
const unanswered =
|
|
12041
|
+
const unanswered = readUnansweredRouteActions();
|
|
12017
12042
|
if (unanswered.length === 0) {
|
|
12018
12043
|
queueMicrotask(() => {
|
|
12019
12044
|
putBack(restoration);
|
|
@@ -12041,10 +12066,10 @@ const putBackOnceAnswered = (restoration) => {
|
|
|
12041
12066
|
|
|
12042
12067
|
const readUnansweredRouteActions = () => {
|
|
12043
12068
|
const unanswered = [];
|
|
12044
|
-
for (const routeAction of activeRouteActionsSignal.
|
|
12069
|
+
for (const routeAction of activeRouteActionsSignal.peek()) {
|
|
12045
12070
|
if (
|
|
12046
|
-
routeAction.runningStateSignal.
|
|
12047
|
-
routeAction.dataSignal.
|
|
12071
|
+
routeAction.runningStateSignal.peek() === RUNNING &&
|
|
12072
|
+
routeAction.dataSignal.peek() === undefined
|
|
12048
12073
|
) {
|
|
12049
12074
|
unanswered.push(routeAction);
|
|
12050
12075
|
}
|
|
@@ -12052,33 +12077,49 @@ const readUnansweredRouteActions = () => {
|
|
|
12052
12077
|
return unanswered;
|
|
12053
12078
|
};
|
|
12054
12079
|
|
|
12055
|
-
// A page can
|
|
12056
|
-
//
|
|
12057
|
-
//
|
|
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
|
|
12080
|
+
// A page can still be arriving after the position is written: an action whose
|
|
12081
|
+
// `when` waits for app state not there yet (see routeActionYetToAskSignal). So
|
|
12082
|
+
// a written position stays pending while one does: the next one that starts
|
|
12060
12083
|
// without an answer is waited for like the first ones, and the position put
|
|
12061
|
-
// back
|
|
12062
|
-
//
|
|
12063
|
-
|
|
12084
|
+
// back once it is drawn. Params that say no are not waited for: the address
|
|
12085
|
+
// asks nothing of the action, and one that asks later — a search typed — is
|
|
12086
|
+
// the page moving on under the reader, not arriving. Not told when the
|
|
12087
|
+
// restoration is dropped (pendingRestoration is no signal): it notices at its
|
|
12088
|
+
// next run.
|
|
12089
|
+
const stayForLateAsks = (restoration) => {
|
|
12064
12090
|
effect(function () {
|
|
12091
|
+
let running = false;
|
|
12092
|
+
let starting = false;
|
|
12093
|
+
for (const routeAction of activeRouteActionsSignal.value) {
|
|
12094
|
+
if (routeAction.dataSignal.value !== undefined) {
|
|
12095
|
+
continue;
|
|
12096
|
+
}
|
|
12097
|
+
const runningState = routeAction.runningStateSignal.value;
|
|
12098
|
+
if (runningState === RUNNING) {
|
|
12099
|
+
running = true;
|
|
12100
|
+
} else if (runningState === IDLE) {
|
|
12101
|
+
// Asking, and run by an effect of the same batch that has not run yet.
|
|
12102
|
+
starting = true;
|
|
12103
|
+
}
|
|
12104
|
+
}
|
|
12105
|
+
const yetToAsk = routeActionYetToAskSignal.value;
|
|
12065
12106
|
if (pendingRestoration !== restoration) {
|
|
12066
12107
|
this.dispose();
|
|
12067
12108
|
return;
|
|
12068
12109
|
}
|
|
12069
|
-
if (
|
|
12110
|
+
if (running) {
|
|
12111
|
+
this.dispose();
|
|
12112
|
+
putBackOnceAnswered(restoration);
|
|
12113
|
+
return;
|
|
12114
|
+
}
|
|
12115
|
+
if (starting || yetToAsk) {
|
|
12070
12116
|
return;
|
|
12071
12117
|
}
|
|
12072
12118
|
this.dispose();
|
|
12073
|
-
|
|
12119
|
+
pendingRestoration = null;
|
|
12074
12120
|
});
|
|
12075
12121
|
};
|
|
12076
12122
|
|
|
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
|
-
|
|
12082
12123
|
// Calls back once none of the actions is running, saying whether all of them
|
|
12083
12124
|
// completed.
|
|
12084
12125
|
const whenSettled = (actions, callback) => {
|
|
@@ -12127,10 +12168,11 @@ const restoreScrollPosition = (url) => {
|
|
|
12127
12168
|
};
|
|
12128
12169
|
|
|
12129
12170
|
// A traversal back to `url`. The offset is written once the page returned to is
|
|
12130
|
-
// rendered: written sooner, the document
|
|
12131
|
-
//
|
|
12132
|
-
//
|
|
12133
|
-
//
|
|
12171
|
+
// rendered and answered (see putBackOnceAnswered): written sooner, the document
|
|
12172
|
+
// still holds the page being left, or a stand-in for the one arriving, and
|
|
12173
|
+
// clamps it to that height. It is read at once all the same — a page left
|
|
12174
|
+
// taller than the one arriving is clamped by the render itself, and that clamp
|
|
12175
|
+
// is a scroll recorded under the url already returned to.
|
|
12134
12176
|
const restoreScrollPositionOnReturn = (url) => {
|
|
12135
12177
|
pendingRestoration = null;
|
|
12136
12178
|
const href = new URL(url, window.location.href).href;
|
|
@@ -12141,7 +12183,7 @@ const restoreScrollPositionOnReturn = (url) => {
|
|
|
12141
12183
|
}
|
|
12142
12184
|
const restoration = startRestoration(position);
|
|
12143
12185
|
whenPageRendered(() => {
|
|
12144
|
-
|
|
12186
|
+
putBackOnceAnswered(restoration);
|
|
12145
12187
|
});
|
|
12146
12188
|
};
|
|
12147
12189
|
|
|
@@ -82798,6 +82840,24 @@ const PickerButton = props => {
|
|
|
82798
82840
|
clearConfirm,
|
|
82799
82841
|
requestValue
|
|
82800
82842
|
};
|
|
82843
|
+
// `--navi-select`/`--navi-unselect` about one entry of the list the picker
|
|
82844
|
+
// holds — a chip on the façade, a suggestion beside the field. Answered here
|
|
82845
|
+
// rather than by the control drawing that list in the popup, even though rows
|
|
82846
|
+
// are what such a control owns: a picker given its own value builds its popup
|
|
82847
|
+
// only on first open (see popup_content_mount.js), so before that there is no
|
|
82848
|
+
// such control at all — and building the whole popup to have someone to talk
|
|
82849
|
+
// to, for a cross, is the wrong price. The picker holds the value in the first
|
|
82850
|
+
// place and hands it down whenever the popup is built.
|
|
82851
|
+
//
|
|
82852
|
+
// Heard on both elements, like every request a control answers: a chip inside
|
|
82853
|
+
// the picker aims at its root (the parent control), one standing elsewhere
|
|
82854
|
+
// names it with `commandFor`, and the picker's id is on its input.
|
|
82855
|
+
const onRequestSelect = e => {
|
|
82856
|
+
requestPickerListEntry(ref.current, inputRef.current, e, "select");
|
|
82857
|
+
};
|
|
82858
|
+
const onRequestUnselect = e => {
|
|
82859
|
+
requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
|
|
82860
|
+
};
|
|
82801
82861
|
return (
|
|
82802
82862
|
/* Read-only crosses into everything the picker is made of: what it really
|
|
82803
82863
|
holds is drawn by controls of their own — in the popup, and on the façade
|
|
@@ -82858,23 +82918,9 @@ const PickerButton = props => {
|
|
|
82858
82918
|
,
|
|
82859
82919
|
onnavi_request_open: inputProps.onnavi_request_open,
|
|
82860
82920
|
onnavi_request_close: inputProps.onnavi_request_close,
|
|
82861
|
-
onnavi_open_press: inputProps.onnavi_open_press
|
|
82862
|
-
|
|
82863
|
-
|
|
82864
|
-
// Answered here rather than by the control drawing that list in the
|
|
82865
|
-
// popup, even though rows are what such a control owns: a picker given
|
|
82866
|
-
// its own value builds its popup only on first open (see
|
|
82867
|
-
// popup_content_mount.js), so before that there is no such control at
|
|
82868
|
-
// all — and building the whole popup to have someone to talk to, for a
|
|
82869
|
-
// cross, is the wrong price. The picker holds the value in the first
|
|
82870
|
-
// place and hands it down whenever the popup is built.
|
|
82871
|
-
,
|
|
82872
|
-
onnavi_request_select: e => {
|
|
82873
|
-
requestPickerListEntry(ref.current, inputRef.current, e, "select");
|
|
82874
|
-
},
|
|
82875
|
-
onnavi_request_unselect: e => {
|
|
82876
|
-
requestPickerListEntry(ref.current, inputRef.current, e, "unselect");
|
|
82877
|
-
},
|
|
82921
|
+
onnavi_open_press: inputProps.onnavi_open_press,
|
|
82922
|
+
onnavi_request_select: onRequestSelect,
|
|
82923
|
+
onnavi_request_unselect: onRequestUnselect,
|
|
82878
82924
|
children: [jsx("span", {
|
|
82879
82925
|
className: "navi_picker_box",
|
|
82880
82926
|
style: hangingChevronSize === undefined ? undefined : {
|
|
@@ -82896,6 +82942,8 @@ const PickerButton = props => {
|
|
|
82896
82942
|
children: undefined // we will render children into the div
|
|
82897
82943
|
,
|
|
82898
82944
|
ui: ui,
|
|
82945
|
+
onnavi_request_select: onRequestSelect,
|
|
82946
|
+
onnavi_request_unselect: onRequestUnselect,
|
|
82899
82947
|
onCopy: e => {
|
|
82900
82948
|
const pickerEl = ref.current;
|
|
82901
82949
|
if (isWithinPickerContent(e.target, pickerEl)) {
|