@jsenv/navi 0.29.329 → 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 +169 -15
- package/dist/dev/jsenv_navi.js.map +4 -4
- package/dist/jsenv_navi.js +169 -15
- package/dist/jsenv_navi.js.map +4 -4
- package/docs/control_value.md +21 -15
- package/package.json +2 -2
package/dist/jsenv_navi.js
CHANGED
|
@@ -31869,9 +31869,10 @@ registerNaviCommand("--navi-void", (source) => {
|
|
|
31869
31869
|
});
|
|
31870
31870
|
|
|
31871
31871
|
// "--navi-update:smooth" asks the control to be SEEN moving to the value — a
|
|
31872
|
-
// wheel scrolls to it rather than swapping its digits
|
|
31873
|
-
//
|
|
31874
|
-
//
|
|
31872
|
+
// wheel scrolls to it rather than swapping its digits, a spin plays the travel
|
|
31873
|
+
// its chevron would play — so a shortcut under a control shows which value it
|
|
31874
|
+
// changed, and by how much. What that movement is, and whether the value
|
|
31875
|
+
// travels with it, belongs to the control: see control_value.md.
|
|
31875
31876
|
registerNaviCommand("--navi-update", (source, event, { argument }) => {
|
|
31876
31877
|
const target =
|
|
31877
31878
|
resolveExplicitTarget(source) || resolveFirstParentControl(source);
|
|
@@ -62748,11 +62749,7 @@ const PickerNative = props => {
|
|
|
62748
62749
|
e.preventDefault();
|
|
62749
62750
|
},
|
|
62750
62751
|
allowed: () => {
|
|
62751
|
-
|
|
62752
|
-
pickerInput.showPicker();
|
|
62753
|
-
} catch {
|
|
62754
|
-
pickerInput.click();
|
|
62755
|
-
}
|
|
62752
|
+
showNativePicker(pickerInput, e);
|
|
62756
62753
|
}
|
|
62757
62754
|
});
|
|
62758
62755
|
},
|
|
@@ -62768,11 +62765,7 @@ const PickerNative = props => {
|
|
|
62768
62765
|
const pickerInput = getPickerInput(pickerEl);
|
|
62769
62766
|
if (pickerInput.type === "color") ; else {
|
|
62770
62767
|
// other picker might not open the picker when clicking the input surface (only the calendar picker for instance would open)
|
|
62771
|
-
|
|
62772
|
-
pickerInput.showPicker();
|
|
62773
|
-
} catch {
|
|
62774
|
-
pickerInput.click();
|
|
62775
|
-
}
|
|
62768
|
+
showNativePicker(pickerInput, e);
|
|
62776
62769
|
}
|
|
62777
62770
|
}
|
|
62778
62771
|
};
|
|
@@ -63657,6 +63650,32 @@ const PickerCalloutPopup = ({
|
|
|
63657
63650
|
);
|
|
63658
63651
|
};
|
|
63659
63652
|
|
|
63653
|
+
// Under a finger the input is focused before the browser's picker is asked
|
|
63654
|
+
// for: iOS presents a date/time input's picker as that input's own inputView,
|
|
63655
|
+
// so what the tap focuses is what opens. showPicker() presents nothing there
|
|
63656
|
+
// and throws nothing either, so the catch below cannot be what learns about
|
|
63657
|
+
// it — and a synthetic click() does not focus a date input there.
|
|
63658
|
+
// A departure from the coarse-pointer policy (see focus_transfer.js), argued
|
|
63659
|
+
// here: the press asked for this input's picker, and the types whose focus
|
|
63660
|
+
// would raise the on-screen keyboard instead are held readOnly already (see
|
|
63661
|
+
// NON_MOBILE_KEYBOARD_TYPES in picker.jsx). No ring: nobody is about to type.
|
|
63662
|
+
// Not with a mouse, where showPicker() presents the picker on its own: the
|
|
63663
|
+
// keyboard stays where it was — a spin's middle, the button that sent the
|
|
63664
|
+
// command — so one keeps going with the keys once the calendar closes, rather
|
|
63665
|
+
// than on an input drawn behind (see the headless variant in picker.jsx).
|
|
63666
|
+
const showNativePicker = (pickerInput, event) => {
|
|
63667
|
+
if (findEvent(event, isTouchDrivenEvent)) {
|
|
63668
|
+
moveFocusTo(pickerInput, {
|
|
63669
|
+
focusVisible: false
|
|
63670
|
+
});
|
|
63671
|
+
}
|
|
63672
|
+
try {
|
|
63673
|
+
pickerInput.showPicker();
|
|
63674
|
+
} catch {
|
|
63675
|
+
pickerInput.click();
|
|
63676
|
+
}
|
|
63677
|
+
};
|
|
63678
|
+
|
|
63660
63679
|
installImportMetaCssBuild(import.meta);
|
|
63661
63680
|
const css$A = /* css */`.navi_picker_confirm_body {
|
|
63662
63681
|
min-width: 180px;
|
|
@@ -72966,6 +72985,10 @@ const css$s = /* css */`@layer navi {
|
|
|
72966
72985
|
}
|
|
72967
72986
|
`;
|
|
72968
72987
|
|
|
72988
|
+
// No travel waiting to be played. One array rather than a new one per render,
|
|
72989
|
+
// so "nothing pending" compares equal to itself.
|
|
72990
|
+
const TRAVELS_NONE = [];
|
|
72991
|
+
|
|
72969
72992
|
/**
|
|
72970
72993
|
* @type {import("ignore:preact").FunctionComponent<{
|
|
72971
72994
|
* value?: any,
|
|
@@ -73224,6 +73247,126 @@ const Spin = ({
|
|
|
73224
73247
|
const valueAtEnd = startIsNext ? valuePrevious : valueNext;
|
|
73225
73248
|
const startAllowed = startIsNext ? nextAllowed : previousAllowed;
|
|
73226
73249
|
const endAllowed = startIsNext ? previousAllowed : nextAllowed;
|
|
73250
|
+
|
|
73251
|
+
// A value asked to be SEEN arriving (`--navi-update:smooth`): the spin plays
|
|
73252
|
+
// the travel a chevron would play and lets the value land with the slides, so
|
|
73253
|
+
// a shortcut under it reads as "go to that day" rather than "be that day".
|
|
73254
|
+
// Whatever the distance it is ONE travel — a spin is not a wheel, and three
|
|
73255
|
+
// weeks are read as "it moved forward" rather than as twenty-one slides — so
|
|
73256
|
+
// the slide arriving is the one that shows the value being travelled to.
|
|
73257
|
+
//
|
|
73258
|
+
// Queued rather than held one at a time: two shortcuts pressed in a row are
|
|
73259
|
+
// two travels, each landing on its own value, and the one being played is
|
|
73260
|
+
// always the head. And started from a layout effect rather than from the
|
|
73261
|
+
// request itself, so the arriving slide is written before the container
|
|
73262
|
+
// places it — started there, the first frame would show the neighbouring
|
|
73263
|
+
// value and swap to the target mid-flight.
|
|
73264
|
+
const [travelsPending, setTravelsPendingState] = useState(TRAVELS_NONE);
|
|
73265
|
+
const travelsPendingRef = useRef(TRAVELS_NONE);
|
|
73266
|
+
const travelPending = travelsPending[0];
|
|
73267
|
+
const setTravelsPending = travels => {
|
|
73268
|
+
travelsPendingRef.current = travels.length ? travels : TRAVELS_NONE;
|
|
73269
|
+
setTravelsPendingState(travelsPendingRef.current);
|
|
73270
|
+
};
|
|
73271
|
+
const onRequestSetUIStateRef = useRef();
|
|
73272
|
+
onRequestSetUIStateRef.current = e => {
|
|
73273
|
+
if (editable) {
|
|
73274
|
+
// Nothing travels around a field: the value is typed into the middle.
|
|
73275
|
+
return;
|
|
73276
|
+
}
|
|
73277
|
+
const {
|
|
73278
|
+
behavior,
|
|
73279
|
+
value: valueRequested
|
|
73280
|
+
} = e.detail;
|
|
73281
|
+
if (behavior !== "smooth") {
|
|
73282
|
+
return;
|
|
73283
|
+
}
|
|
73284
|
+
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
73285
|
+
// Whoever asked to see less motion is answered first: the value is swapped
|
|
73286
|
+
// in place, which is what `--navi-update` alone does.
|
|
73287
|
+
return;
|
|
73288
|
+
}
|
|
73289
|
+
if (valueRequested === undefined || valueRequested === null || valueRequested === "") {
|
|
73290
|
+
// Nowhere to travel to — the erase a calendar offers, say.
|
|
73291
|
+
return;
|
|
73292
|
+
}
|
|
73293
|
+
const travelsQueued = travelsPendingRef.current;
|
|
73294
|
+
// Where the spin stands when this travel starts: behind the ones already
|
|
73295
|
+
// queued, rather than where the eye is now.
|
|
73296
|
+
const valueFrom = travelsQueued.length ? travelsQueued[travelsQueued.length - 1].value : valueShown;
|
|
73297
|
+
const direction = compareValues(valueRequested, valueFrom);
|
|
73298
|
+
if (direction === 0) {
|
|
73299
|
+
return;
|
|
73300
|
+
}
|
|
73301
|
+
const goingNext = direction > 0;
|
|
73302
|
+
// Out of reach, in both the ways a value can be: past the end of what one
|
|
73303
|
+
// may reach, or one step past it — the first step of the travel is the very
|
|
73304
|
+
// one the chevron that way is refused.
|
|
73305
|
+
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);
|
|
73306
|
+
if (outOfReach) {
|
|
73307
|
+
// The same refusal a chevron gives, in the same words — said at the value
|
|
73308
|
+
// rather than at the chevron that way, because the press was not on a
|
|
73309
|
+
// chevron: what was asked about is the value, and that is where the
|
|
73310
|
+
// answer belongs.
|
|
73311
|
+
e.preventDefault();
|
|
73312
|
+
e.stopPropagation();
|
|
73313
|
+
openCallout(naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before"), {
|
|
73314
|
+
anchorElement: middleRef.current,
|
|
73315
|
+
status: "info",
|
|
73316
|
+
openingEvent: e.detail.event ?? e
|
|
73317
|
+
});
|
|
73318
|
+
return;
|
|
73319
|
+
}
|
|
73320
|
+
// Taken here and answered when the slides land (see onLoop): the control
|
|
73321
|
+
// must not be given the value on the way past, or there would be nothing
|
|
73322
|
+
// left to arrive.
|
|
73323
|
+
e.stopPropagation();
|
|
73324
|
+
// A direction on the map, the way a chevron says it (see wayOut): the
|
|
73325
|
+
// slides are laid out from start to end, so which value sits where is what
|
|
73326
|
+
// decides the way, not which value is bigger.
|
|
73327
|
+
const atStart = goingNext ? startIsNext : !startIsNext;
|
|
73328
|
+
const towardsEnd = atStart ? -1 : 1;
|
|
73329
|
+
setTravelsPending([...travelsQueued, {
|
|
73330
|
+
value: valueRequested,
|
|
73331
|
+
area: atStart ? "start" : "end",
|
|
73332
|
+
dx: vertical ? 0 : towardsEnd,
|
|
73333
|
+
dy: vertical ? towardsEnd : 0,
|
|
73334
|
+
event: e.detail.event ?? e
|
|
73335
|
+
}]);
|
|
73336
|
+
};
|
|
73337
|
+
useLayoutEffect(() => {
|
|
73338
|
+
const middleEl = middleRef.current;
|
|
73339
|
+
const onRequestSetUIState = e => {
|
|
73340
|
+
onRequestSetUIStateRef.current(e);
|
|
73341
|
+
};
|
|
73342
|
+
// On the way DOWN, and on the box holding the control rather than on the
|
|
73343
|
+
// control: the request is dispatched AT the control host, where the
|
|
73344
|
+
// controller is already listening, and the spin has to be able to take it
|
|
73345
|
+
// before the value is set.
|
|
73346
|
+
middleEl.addEventListener("navi_set_ui_state", onRequestSetUIState, true);
|
|
73347
|
+
return () => {
|
|
73348
|
+
middleEl.removeEventListener("navi_set_ui_state", onRequestSetUIState, true);
|
|
73349
|
+
};
|
|
73350
|
+
}, []);
|
|
73351
|
+
useLayoutEffect(() => {
|
|
73352
|
+
if (!travelPending) {
|
|
73353
|
+
return;
|
|
73354
|
+
}
|
|
73355
|
+
// What `--navi-left` and friends ask the container for (see
|
|
73356
|
+
// registerSlideCommand): asked directly because the container already
|
|
73357
|
+
// carries a `commandfor` of its own — pressing the middle opens the picker
|
|
73358
|
+
// — so a command triggered from it would be about the picker.
|
|
73359
|
+
dispatchCustomEvent(document.getElementById(containerId), "navi_slide_move", {
|
|
73360
|
+
event: travelPending.event,
|
|
73361
|
+
dx: travelPending.dx,
|
|
73362
|
+
dy: travelPending.dy
|
|
73363
|
+
});
|
|
73364
|
+
}, [travelPending]);
|
|
73365
|
+
// What the slide the travel is heading for shows: the value being travelled
|
|
73366
|
+
// TO, not the neighbour that place holds at rest — one travel covers the
|
|
73367
|
+
// whole distance, so the far value is what arrives.
|
|
73368
|
+
const valueDrawnAtStart = travelPending?.area === "start" ? travelPending.value : valueAtStart;
|
|
73369
|
+
const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
|
|
73227
73370
|
const wayOut = atStart => {
|
|
73228
73371
|
const isNext = atStart ? startIsNext : !startIsNext;
|
|
73229
73372
|
return jsx(WayOut, {
|
|
@@ -73337,6 +73480,17 @@ const Spin = ({
|
|
|
73337
73480
|
dy,
|
|
73338
73481
|
event
|
|
73339
73482
|
}) => {
|
|
73483
|
+
// A travel asked for by a VALUE (`--navi-update:smooth`):
|
|
73484
|
+
// that value is what lands, whatever the distance it covered,
|
|
73485
|
+
// and the press that asked for it is what says so to uiAction.
|
|
73486
|
+
// Recognized by that press: a chevron travel hurrying this one
|
|
73487
|
+
// along lands first and is a step of its own.
|
|
73488
|
+
const travelLanding = travelsPendingRef.current[0];
|
|
73489
|
+
if (travelLanding && event?.detail?.event === travelLanding.event) {
|
|
73490
|
+
setTravelsPending(travelsPendingRef.current.slice(1));
|
|
73491
|
+
setValue(travelLanding.value, travelLanding.event);
|
|
73492
|
+
return;
|
|
73493
|
+
}
|
|
73340
73494
|
// One step, whichever axis it came from: the map is a line, so
|
|
73341
73495
|
// only one of the two is ever anything but zero. Towards the
|
|
73342
73496
|
// end of the line is a step forward, unless the line was laid
|
|
@@ -73369,7 +73523,7 @@ const Spin = ({
|
|
|
73369
73523
|
area: "start",
|
|
73370
73524
|
flex: true,
|
|
73371
73525
|
align: "center",
|
|
73372
|
-
children: renderValue(
|
|
73526
|
+
children: renderValue(valueDrawnAtStart, {
|
|
73373
73527
|
maxLines
|
|
73374
73528
|
})
|
|
73375
73529
|
}), jsx(Slide, {
|
|
@@ -73389,7 +73543,7 @@ const Spin = ({
|
|
|
73389
73543
|
area: "end",
|
|
73390
73544
|
flex: true,
|
|
73391
73545
|
align: "center",
|
|
73392
|
-
children: renderValue(
|
|
73546
|
+
children: renderValue(valueDrawnAtEnd, {
|
|
73393
73547
|
maxLines
|
|
73394
73548
|
})
|
|
73395
73549
|
})]
|