@jsenv/navi 0.29.328 → 0.29.330
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 +200 -32
- package/dist/dev/jsenv_navi.js.map +6 -6
- package/dist/jsenv_navi.js +200 -32
- package/dist/jsenv_navi.js.map +6 -6
- package/docs/control_value.md +21 -15
- package/package.json +2 -2
package/dist/dev/jsenv_navi.js
CHANGED
|
@@ -24224,10 +24224,16 @@ const createRoutePattern = (
|
|
|
24224
24224
|
// search params are updated. When buildMostPreciseUrl performs an ancestor
|
|
24225
24225
|
// optimisation (e.g. "/map/isochrone/compare" → "/map/isochrone") it is trusted
|
|
24226
24226
|
// as-is because the built pathname will differ from the route's own base pathname.
|
|
24227
|
-
//
|
|
24228
|
-
//
|
|
24229
|
-
//
|
|
24230
|
-
|
|
24227
|
+
// A weak param is never inherited from its signal by a url built for a link
|
|
24228
|
+
// (see readSignalForUrlBuild). A url amended in place is another matter: the
|
|
24229
|
+
// route is matching, so the signal IS this visit's state, kept in step with
|
|
24230
|
+
// the address by every routing pass — and what it holds is carried over, so
|
|
24231
|
+
// that changing another param does not end the visit this one qualifies.
|
|
24232
|
+
// Read from the signal, not from the url being amended: several signals
|
|
24233
|
+
// written in one batch flush one url write at a time, and the routing pass
|
|
24234
|
+
// of the first would take the others for gone — and reset them — if the url
|
|
24235
|
+
// it wrote did not already carry what they hold.
|
|
24236
|
+
const carryOverWeakParams = (params) => {
|
|
24231
24237
|
let paramsWithWeak = params;
|
|
24232
24238
|
for (const connection of connections) {
|
|
24233
24239
|
const { paramName } = connection;
|
|
@@ -24235,17 +24241,13 @@ const createRoutePattern = (
|
|
|
24235
24241
|
continue;
|
|
24236
24242
|
}
|
|
24237
24243
|
const currentValue = connection.signal.peek();
|
|
24238
|
-
if (currentValue
|
|
24239
|
-
continue;
|
|
24240
|
-
}
|
|
24241
|
-
const currentParams = applyOn(currentUrl);
|
|
24242
|
-
if (!currentParams || currentParams[paramName] === undefined) {
|
|
24244
|
+
if (connection.isDefaultValue(currentValue)) {
|
|
24243
24245
|
continue;
|
|
24244
24246
|
}
|
|
24245
24247
|
if (paramsWithWeak === params) {
|
|
24246
24248
|
paramsWithWeak = { ...params };
|
|
24247
24249
|
}
|
|
24248
|
-
paramsWithWeak[paramName] =
|
|
24250
|
+
paramsWithWeak[paramName] = currentValue;
|
|
24249
24251
|
}
|
|
24250
24252
|
return paramsWithWeak;
|
|
24251
24253
|
};
|
|
@@ -24281,7 +24283,7 @@ const createRoutePattern = (
|
|
|
24281
24283
|
};
|
|
24282
24284
|
|
|
24283
24285
|
const buildUrlPreservingPath = (currentUrl, params = {}) => {
|
|
24284
|
-
params = carryOverWeakParams(
|
|
24286
|
+
params = carryOverWeakParams(params);
|
|
24285
24287
|
if (currentUrl) {
|
|
24286
24288
|
params = carryOverPathParams(currentUrl, params);
|
|
24287
24289
|
}
|
|
@@ -32747,9 +32749,10 @@ registerNaviCommand("--navi-void", (source) => {
|
|
|
32747
32749
|
});
|
|
32748
32750
|
|
|
32749
32751
|
// "--navi-update:smooth" asks the control to be SEEN moving to the value — a
|
|
32750
|
-
// wheel scrolls to it rather than swapping its digits
|
|
32751
|
-
//
|
|
32752
|
-
//
|
|
32752
|
+
// wheel scrolls to it rather than swapping its digits, a spin plays the travel
|
|
32753
|
+
// its chevron would play — so a shortcut under a control shows which value it
|
|
32754
|
+
// changed, and by how much. What that movement is, and whether the value
|
|
32755
|
+
// travels with it, belongs to the control: see control_value.md.
|
|
32753
32756
|
registerNaviCommand("--navi-update", (source, event, { argument }) => {
|
|
32754
32757
|
const target =
|
|
32755
32758
|
resolveExplicitTarget(source) || resolveFirstParentControl(source);
|
|
@@ -36837,7 +36840,7 @@ const useUIStateController = (
|
|
|
36837
36840
|
// reach until that popup opens. Letting it block interaction would make
|
|
36838
36841
|
// a picker whose content is loading impossible to open at all.
|
|
36839
36842
|
getInteractionBlockingControls: () => [],
|
|
36840
|
-
onUIAction: (e, { skipCommand } = {}) => {
|
|
36843
|
+
onUIAction: (e, { skipCommand, skipBoundSignal } = {}) => {
|
|
36841
36844
|
if (controlType === "button" && controller.controlHostProps.name) {
|
|
36842
36845
|
const buttonName = controller.controlHostProps.name;
|
|
36843
36846
|
const parentController = controller.parentUIStateController;
|
|
@@ -36867,7 +36870,9 @@ const useUIStateController = (
|
|
|
36867
36870
|
}
|
|
36868
36871
|
// Trigger uiAction/command side effects without changing UI state.
|
|
36869
36872
|
const currentUIState = controller.uiState;
|
|
36870
|
-
|
|
36873
|
+
if (!skipBoundSignal) {
|
|
36874
|
+
writeBoundSignal(currentUIState);
|
|
36875
|
+
}
|
|
36871
36876
|
s.uiActionInternal?.(currentUIState, e);
|
|
36872
36877
|
if (s.uiAction) {
|
|
36873
36878
|
debugUIState(`calling uiAction for ${controlType}`, currentUIState);
|
|
@@ -37093,7 +37098,15 @@ const useUIStateController = (
|
|
|
37093
37098
|
// sync, but do NOT fire the command and do NOT notify the parent —
|
|
37094
37099
|
// both would cause an infinite loop when a parent cascades state
|
|
37095
37100
|
// down to its children (child command would re-trigger the cascade).
|
|
37096
|
-
|
|
37101
|
+
// A control following what it was given (state_prop_change) does
|
|
37102
|
+
// not write its bound signal either: the signal is what it
|
|
37103
|
+
// follows. The one write that would change it is an emptied
|
|
37104
|
+
// signal refilled with the suggestion the control fell back on —
|
|
37105
|
+
// a state the app just took back, put back by the control.
|
|
37106
|
+
controller.onUIAction(e, {
|
|
37107
|
+
skipCommand: true,
|
|
37108
|
+
skipBoundSignal: e.type === "state_prop_change",
|
|
37109
|
+
});
|
|
37097
37110
|
}
|
|
37098
37111
|
if (
|
|
37099
37112
|
e.type === "facade_propagate_up" ||
|
|
@@ -63996,11 +64009,7 @@ const PickerNative = props => {
|
|
|
63996
64009
|
e.preventDefault();
|
|
63997
64010
|
},
|
|
63998
64011
|
allowed: () => {
|
|
63999
|
-
|
|
64000
|
-
pickerInput.showPicker();
|
|
64001
|
-
} catch {
|
|
64002
|
-
pickerInput.click();
|
|
64003
|
-
}
|
|
64012
|
+
showNativePicker(pickerInput, e);
|
|
64004
64013
|
}
|
|
64005
64014
|
});
|
|
64006
64015
|
},
|
|
@@ -64016,11 +64025,7 @@ const PickerNative = props => {
|
|
|
64016
64025
|
const pickerInput = getPickerInput(pickerEl);
|
|
64017
64026
|
if (pickerInput.type === "color") ; else {
|
|
64018
64027
|
// other picker might not open the picker when clicking the input surface (only the calendar picker for instance would open)
|
|
64019
|
-
|
|
64020
|
-
pickerInput.showPicker();
|
|
64021
|
-
} catch {
|
|
64022
|
-
pickerInput.click();
|
|
64023
|
-
}
|
|
64028
|
+
showNativePicker(pickerInput, e);
|
|
64024
64029
|
}
|
|
64025
64030
|
}
|
|
64026
64031
|
};
|
|
@@ -64919,6 +64924,32 @@ const PickerCalloutPopup = ({
|
|
|
64919
64924
|
);
|
|
64920
64925
|
};
|
|
64921
64926
|
|
|
64927
|
+
// Under a finger the input is focused before the browser's picker is asked
|
|
64928
|
+
// for: iOS presents a date/time input's picker as that input's own inputView,
|
|
64929
|
+
// so what the tap focuses is what opens. showPicker() presents nothing there
|
|
64930
|
+
// and throws nothing either, so the catch below cannot be what learns about
|
|
64931
|
+
// it — and a synthetic click() does not focus a date input there.
|
|
64932
|
+
// A departure from the coarse-pointer policy (see focus_transfer.js), argued
|
|
64933
|
+
// here: the press asked for this input's picker, and the types whose focus
|
|
64934
|
+
// would raise the on-screen keyboard instead are held readOnly already (see
|
|
64935
|
+
// NON_MOBILE_KEYBOARD_TYPES in picker.jsx). No ring: nobody is about to type.
|
|
64936
|
+
// Not with a mouse, where showPicker() presents the picker on its own: the
|
|
64937
|
+
// keyboard stays where it was — a spin's middle, the button that sent the
|
|
64938
|
+
// command — so one keeps going with the keys once the calendar closes, rather
|
|
64939
|
+
// than on an input drawn behind (see the headless variant in picker.jsx).
|
|
64940
|
+
const showNativePicker = (pickerInput, event) => {
|
|
64941
|
+
if (findEvent(event, isTouchDrivenEvent)) {
|
|
64942
|
+
moveFocusTo(pickerInput, {
|
|
64943
|
+
focusVisible: false
|
|
64944
|
+
});
|
|
64945
|
+
}
|
|
64946
|
+
try {
|
|
64947
|
+
pickerInput.showPicker();
|
|
64948
|
+
} catch {
|
|
64949
|
+
pickerInput.click();
|
|
64950
|
+
}
|
|
64951
|
+
};
|
|
64952
|
+
|
|
64922
64953
|
installImportMetaCssBuild(import.meta);
|
|
64923
64954
|
const css$A = /* css */`.navi_picker_confirm_body {
|
|
64924
64955
|
min-width: 180px;
|
|
@@ -74861,6 +74892,10 @@ const css$s = /* css */`@layer navi {
|
|
|
74861
74892
|
}
|
|
74862
74893
|
`;
|
|
74863
74894
|
|
|
74895
|
+
// No travel waiting to be played. One array rather than a new one per render,
|
|
74896
|
+
// so "nothing pending" compares equal to itself.
|
|
74897
|
+
const TRAVELS_NONE = [];
|
|
74898
|
+
|
|
74864
74899
|
/**
|
|
74865
74900
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
74866
74901
|
* value?: any,
|
|
@@ -74985,8 +75020,11 @@ const Spin = ({
|
|
|
74985
75020
|
// elsewhere on the page — and the control follows it. A control seeded from a
|
|
74986
75021
|
// signal only writes back into it (see resolveInputProps), which is enough
|
|
74987
75022
|
// for a field one only ever types into and not for a value that is also moved
|
|
74988
|
-
// from outside.
|
|
74989
|
-
// the
|
|
75023
|
+
// from outside. A signal holding nothing is left alone: the control goes back
|
|
75024
|
+
// to the suggestion it started on by itself (see resolveValueState), and
|
|
75025
|
+
// asking it to reset would be an act of its own, written into the signal —
|
|
75026
|
+
// the signal could then never hold nothing while a spin is bound to it, and a
|
|
75027
|
+
// day the app took back would come back as today.
|
|
74990
75028
|
const signalValue = signalProp ? signalProp.value : undefined;
|
|
74991
75029
|
// The value as of the render this effect belongs to, and not one the closure
|
|
74992
75030
|
// captured a while ago: a step writes the signal, the signal brings us back
|
|
@@ -75000,7 +75038,6 @@ const Spin = ({
|
|
|
75000
75038
|
return;
|
|
75001
75039
|
}
|
|
75002
75040
|
if (signalValue === undefined) {
|
|
75003
|
-
dispatchRequestResetUIState(controlEl);
|
|
75004
75041
|
return;
|
|
75005
75042
|
}
|
|
75006
75043
|
if (signalValue !== valueRef.current) {
|
|
@@ -75117,6 +75154,126 @@ const Spin = ({
|
|
|
75117
75154
|
const valueAtEnd = startIsNext ? valuePrevious : valueNext;
|
|
75118
75155
|
const startAllowed = startIsNext ? nextAllowed : previousAllowed;
|
|
75119
75156
|
const endAllowed = startIsNext ? previousAllowed : nextAllowed;
|
|
75157
|
+
|
|
75158
|
+
// A value asked to be SEEN arriving (`--navi-update:smooth`): the spin plays
|
|
75159
|
+
// the travel a chevron would play and lets the value land with the slides, so
|
|
75160
|
+
// a shortcut under it reads as "go to that day" rather than "be that day".
|
|
75161
|
+
// Whatever the distance it is ONE travel — a spin is not a wheel, and three
|
|
75162
|
+
// weeks are read as "it moved forward" rather than as twenty-one slides — so
|
|
75163
|
+
// the slide arriving is the one that shows the value being travelled to.
|
|
75164
|
+
//
|
|
75165
|
+
// Queued rather than held one at a time: two shortcuts pressed in a row are
|
|
75166
|
+
// two travels, each landing on its own value, and the one being played is
|
|
75167
|
+
// always the head. And started from a layout effect rather than from the
|
|
75168
|
+
// request itself, so the arriving slide is written before the container
|
|
75169
|
+
// places it — started there, the first frame would show the neighbouring
|
|
75170
|
+
// value and swap to the target mid-flight.
|
|
75171
|
+
const [travelsPending, setTravelsPendingState] = useState(TRAVELS_NONE);
|
|
75172
|
+
const travelsPendingRef = useRef(TRAVELS_NONE);
|
|
75173
|
+
const travelPending = travelsPending[0];
|
|
75174
|
+
const setTravelsPending = travels => {
|
|
75175
|
+
travelsPendingRef.current = travels.length ? travels : TRAVELS_NONE;
|
|
75176
|
+
setTravelsPendingState(travelsPendingRef.current);
|
|
75177
|
+
};
|
|
75178
|
+
const onRequestSetUIStateRef = useRef();
|
|
75179
|
+
onRequestSetUIStateRef.current = e => {
|
|
75180
|
+
if (editable) {
|
|
75181
|
+
// Nothing travels around a field: the value is typed into the middle.
|
|
75182
|
+
return;
|
|
75183
|
+
}
|
|
75184
|
+
const {
|
|
75185
|
+
behavior,
|
|
75186
|
+
value: valueRequested
|
|
75187
|
+
} = e.detail;
|
|
75188
|
+
if (behavior !== "smooth") {
|
|
75189
|
+
return;
|
|
75190
|
+
}
|
|
75191
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
75192
|
+
// Whoever asked to see less motion is answered first: the value is swapped
|
|
75193
|
+
// in place, which is what `--navi-update` alone does.
|
|
75194
|
+
return;
|
|
75195
|
+
}
|
|
75196
|
+
if (valueRequested === undefined || valueRequested === null || valueRequested === "") {
|
|
75197
|
+
// Nowhere to travel to — the erase a calendar offers, say.
|
|
75198
|
+
return;
|
|
75199
|
+
}
|
|
75200
|
+
const travelsQueued = travelsPendingRef.current;
|
|
75201
|
+
// Where the spin stands when this travel starts: behind the ones already
|
|
75202
|
+
// queued, rather than where the eye is now.
|
|
75203
|
+
const valueFrom = travelsQueued.length ? travelsQueued[travelsQueued.length - 1].value : valueShown;
|
|
75204
|
+
const direction = compareValues(valueRequested, valueFrom);
|
|
75205
|
+
if (direction === 0) {
|
|
75206
|
+
return;
|
|
75207
|
+
}
|
|
75208
|
+
const goingNext = direction > 0;
|
|
75209
|
+
// Out of reach, in both the ways a value can be: past the end of what one
|
|
75210
|
+
// may reach, or one step past it — the first step of the travel is the very
|
|
75211
|
+
// one the chevron that way is refused.
|
|
75212
|
+
const outOfReach = goingNext ? hasMax && (compareValues(valueRequested, max) > 0 || compareValues(valueAtStep(valueFrom, step), max) > 0) : hasMin && (compareValues(valueRequested, min) < 0 || compareValues(valueAtStep(valueFrom, -step), min) < 0);
|
|
75213
|
+
if (outOfReach) {
|
|
75214
|
+
// The same refusal a chevron gives, in the same words — said at the value
|
|
75215
|
+
// rather than at the chevron that way, because the press was not on a
|
|
75216
|
+
// chevron: what was asked about is the value, and that is where the
|
|
75217
|
+
// answer belongs.
|
|
75218
|
+
e.preventDefault();
|
|
75219
|
+
e.stopPropagation();
|
|
75220
|
+
openCallout(naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before"), {
|
|
75221
|
+
anchorElement: middleRef.current,
|
|
75222
|
+
status: "info",
|
|
75223
|
+
openingEvent: e.detail.event ?? e
|
|
75224
|
+
});
|
|
75225
|
+
return;
|
|
75226
|
+
}
|
|
75227
|
+
// Taken here and answered when the slides land (see onLoop): the control
|
|
75228
|
+
// must not be given the value on the way past, or there would be nothing
|
|
75229
|
+
// left to arrive.
|
|
75230
|
+
e.stopPropagation();
|
|
75231
|
+
// A direction on the map, the way a chevron says it (see wayOut): the
|
|
75232
|
+
// slides are laid out from start to end, so which value sits where is what
|
|
75233
|
+
// decides the way, not which value is bigger.
|
|
75234
|
+
const atStart = goingNext ? startIsNext : !startIsNext;
|
|
75235
|
+
const towardsEnd = atStart ? -1 : 1;
|
|
75236
|
+
setTravelsPending([...travelsQueued, {
|
|
75237
|
+
value: valueRequested,
|
|
75238
|
+
area: atStart ? "start" : "end",
|
|
75239
|
+
dx: vertical ? 0 : towardsEnd,
|
|
75240
|
+
dy: vertical ? towardsEnd : 0,
|
|
75241
|
+
event: e.detail.event ?? e
|
|
75242
|
+
}]);
|
|
75243
|
+
};
|
|
75244
|
+
useLayoutEffect(() => {
|
|
75245
|
+
const middleEl = middleRef.current;
|
|
75246
|
+
const onRequestSetUIState = e => {
|
|
75247
|
+
onRequestSetUIStateRef.current(e);
|
|
75248
|
+
};
|
|
75249
|
+
// On the way DOWN, and on the box holding the control rather than on the
|
|
75250
|
+
// control: the request is dispatched AT the control host, where the
|
|
75251
|
+
// controller is already listening, and the spin has to be able to take it
|
|
75252
|
+
// before the value is set.
|
|
75253
|
+
middleEl.addEventListener("navi_set_ui_state", onRequestSetUIState, true);
|
|
75254
|
+
return () => {
|
|
75255
|
+
middleEl.removeEventListener("navi_set_ui_state", onRequestSetUIState, true);
|
|
75256
|
+
};
|
|
75257
|
+
}, []);
|
|
75258
|
+
useLayoutEffect(() => {
|
|
75259
|
+
if (!travelPending) {
|
|
75260
|
+
return;
|
|
75261
|
+
}
|
|
75262
|
+
// What `--navi-left` and friends ask the container for (see
|
|
75263
|
+
// registerSlideCommand): asked directly because the container already
|
|
75264
|
+
// carries a `commandfor` of its own — pressing the middle opens the picker
|
|
75265
|
+
// — so a command triggered from it would be about the picker.
|
|
75266
|
+
dispatchCustomEvent(document.getElementById(containerId), "navi_slide_move", {
|
|
75267
|
+
event: travelPending.event,
|
|
75268
|
+
dx: travelPending.dx,
|
|
75269
|
+
dy: travelPending.dy
|
|
75270
|
+
});
|
|
75271
|
+
}, [travelPending]);
|
|
75272
|
+
// What the slide the travel is heading for shows: the value being travelled
|
|
75273
|
+
// TO, not the neighbour that place holds at rest — one travel covers the
|
|
75274
|
+
// whole distance, so the far value is what arrives.
|
|
75275
|
+
const valueDrawnAtStart = travelPending?.area === "start" ? travelPending.value : valueAtStart;
|
|
75276
|
+
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
75120
75277
|
const wayOut = atStart => {
|
|
75121
75278
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
75122
75279
|
return jsx(WayOut, {
|
|
@@ -75230,6 +75387,17 @@ const Spin = ({
|
|
|
75230
75387
|
dy,
|
|
75231
75388
|
event
|
|
75232
75389
|
}) => {
|
|
75390
|
+
// A travel asked for by a VALUE (`--navi-update:smooth`):
|
|
75391
|
+
// that value is what lands, whatever the distance it covered,
|
|
75392
|
+
// and the press that asked for it is what says so to uiAction.
|
|
75393
|
+
// Recognized by that press: a chevron travel hurrying this one
|
|
75394
|
+
// along lands first and is a step of its own.
|
|
75395
|
+
const travelLanding = travelsPendingRef.current[0];
|
|
75396
|
+
if (travelLanding && event?.detail?.event === travelLanding.event) {
|
|
75397
|
+
setTravelsPending(travelsPendingRef.current.slice(1));
|
|
75398
|
+
setValue(travelLanding.value, travelLanding.event);
|
|
75399
|
+
return;
|
|
75400
|
+
}
|
|
75233
75401
|
// One step, whichever axis it came from: the map is a line, so
|
|
75234
75402
|
// only one of the two is ever anything but zero. Towards the
|
|
75235
75403
|
// end of the line is a step forward, unless the line was laid
|
|
@@ -75262,7 +75430,7 @@ const Spin = ({
|
|
|
75262
75430
|
area: "start",
|
|
75263
75431
|
flex: true,
|
|
75264
75432
|
align: "center",
|
|
75265
|
-
children: renderValue(
|
|
75433
|
+
children: renderValue(valueDrawnAtStart, {
|
|
75266
75434
|
maxLines
|
|
75267
75435
|
})
|
|
75268
75436
|
}), jsx(Slide, {
|
|
@@ -75282,7 +75450,7 @@ const Spin = ({
|
|
|
75282
75450
|
area: "end",
|
|
75283
75451
|
flex: true,
|
|
75284
75452
|
align: "center",
|
|
75285
|
-
children: renderValue(
|
|
75453
|
+
children: renderValue(valueDrawnAtEnd, {
|
|
75286
75454
|
maxLines
|
|
75287
75455
|
})
|
|
75288
75456
|
})]
|