@jsenv/navi 0.29.329 → 0.29.331

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.
@@ -31216,6 +31216,17 @@ const RouteTravel = ({
31216
31216
  ended: false
31217
31217
  };
31218
31218
  travelRef.current = travel;
31219
+ // A press landing on a playing travel lands on the document root: the box
31220
+ // is captured by the transition and cannot be pointed at, so neither its
31221
+ // touch-action nor its own touchmove listener is on the touch's path. Left
31222
+ // there, the browser takes the finger's first vertical pixels for a scroll
31223
+ // and cancels the pointer under a travel that was just caught. Refusable
31224
+ // from the root for the length of the travel, the gesture that catches it
31225
+ // refuses every touchmove of that press (it is started from the grab), and
31226
+ // the finger's only answer is the travel it landed on.
31227
+ document.documentElement.addEventListener("touchmove", keepTouchRefusable, {
31228
+ passive: false
31229
+ });
31219
31230
  // Whether the document's offset is this row's business at all (see
31220
31231
  // pagesScrollTheDocument), asked once and before anything is swapped.
31221
31232
  travel.scrollsDocument = pagesScrollTheDocument(elementRef.current);
@@ -31589,6 +31600,9 @@ const RouteTravel = ({
31589
31600
  travel.ended = true;
31590
31601
  travel.dropHold?.();
31591
31602
  travel.dropHold = null;
31603
+ if (travelRef.current === travel) {
31604
+ document.documentElement.removeEventListener("touchmove", keepTouchRefusable);
31605
+ }
31592
31606
  // The offset on the scrollport belongs to a page again — the one that
31593
31607
  // arrived, or the one put back. A travel that never got as far as saying
31594
31608
  // which still has to give the recording back; the gesture's own
@@ -32749,9 +32763,10 @@ registerNaviCommand("--navi-void", (source) => {
32749
32763
  });
32750
32764
 
32751
32765
  // "--navi-update:smooth" asks the control to be SEEN moving to the value — a
32752
- // wheel scrolls to it rather than swapping its digits — so a shortcut under
32753
- // two wheels shows which one it changed, and by how much. The value itself is
32754
- // set at once either way; only the drawing is concerned.
32766
+ // wheel scrolls to it rather than swapping its digits, a spin plays the travel
32767
+ // its chevron would play — so a shortcut under a control shows which value it
32768
+ // changed, and by how much. What that movement is, and whether the value
32769
+ // travels with it, belongs to the control: see control_value.md.
32755
32770
  registerNaviCommand("--navi-update", (source, event, { argument }) => {
32756
32771
  const target =
32757
32772
  resolveExplicitTarget(source) || resolveFirstParentControl(source);
@@ -64008,11 +64023,7 @@ const PickerNative = props => {
64008
64023
  e.preventDefault();
64009
64024
  },
64010
64025
  allowed: () => {
64011
- try {
64012
- pickerInput.showPicker();
64013
- } catch {
64014
- pickerInput.click();
64015
- }
64026
+ showNativePicker(pickerInput, e);
64016
64027
  }
64017
64028
  });
64018
64029
  },
@@ -64028,11 +64039,7 @@ const PickerNative = props => {
64028
64039
  const pickerInput = getPickerInput(pickerEl);
64029
64040
  if (pickerInput.type === "color") ; else {
64030
64041
  // other picker might not open the picker when clicking the input surface (only the calendar picker for instance would open)
64031
- try {
64032
- pickerInput.showPicker();
64033
- } catch {
64034
- pickerInput.click();
64035
- }
64042
+ showNativePicker(pickerInput, e);
64036
64043
  }
64037
64044
  }
64038
64045
  };
@@ -64931,6 +64938,32 @@ const PickerCalloutPopup = ({
64931
64938
  );
64932
64939
  };
64933
64940
 
64941
+ // Under a finger the input is focused before the browser's picker is asked
64942
+ // for: iOS presents a date/time input's picker as that input's own inputView,
64943
+ // so what the tap focuses is what opens. showPicker() presents nothing there
64944
+ // and throws nothing either, so the catch below cannot be what learns about
64945
+ // it — and a synthetic click() does not focus a date input there.
64946
+ // A departure from the coarse-pointer policy (see focus_transfer.js), argued
64947
+ // here: the press asked for this input's picker, and the types whose focus
64948
+ // would raise the on-screen keyboard instead are held readOnly already (see
64949
+ // NON_MOBILE_KEYBOARD_TYPES in picker.jsx). No ring: nobody is about to type.
64950
+ // Not with a mouse, where showPicker() presents the picker on its own: the
64951
+ // keyboard stays where it was — a spin's middle, the button that sent the
64952
+ // command — so one keeps going with the keys once the calendar closes, rather
64953
+ // than on an input drawn behind (see the headless variant in picker.jsx).
64954
+ const showNativePicker = (pickerInput, event) => {
64955
+ if (findEvent(event, isTouchDrivenEvent)) {
64956
+ moveFocusTo(pickerInput, {
64957
+ focusVisible: false
64958
+ });
64959
+ }
64960
+ try {
64961
+ pickerInput.showPicker();
64962
+ } catch {
64963
+ pickerInput.click();
64964
+ }
64965
+ };
64966
+
64934
64967
  installImportMetaCssBuild(import.meta);
64935
64968
  const css$A = /* css */`.navi_picker_confirm_body {
64936
64969
  min-width: 180px;
@@ -73244,6 +73277,7 @@ const css$t = /* css */`@layer navi {
73244
73277
  --picker-padding-y-default: 0;
73245
73278
  --picker-border-width: 0px;
73246
73279
  --picker-border-color: transparent;
73280
+ --picker-outline-width: 0px;
73247
73281
  --picker-border-color-hover: var(--picker-border-color);
73248
73282
  --picker-border-color-readonly: var(--picker-border-color);
73249
73283
  --picker-border-color-disabled: var(--picker-border-color);
@@ -74873,6 +74907,10 @@ const css$s = /* css */`@layer navi {
74873
74907
  }
74874
74908
  `;
74875
74909
 
74910
+ // No travel waiting to be played. One array rather than a new one per render,
74911
+ // so "nothing pending" compares equal to itself.
74912
+ const TRAVELS_NONE = [];
74913
+
74876
74914
  /**
74877
74915
  * @type {import("ignore:preact").FunctionComponent<{
74878
74916
  * value?: any,
@@ -75131,6 +75169,126 @@ const Spin = ({
75131
75169
  const valueAtEnd = startIsNext ? valuePrevious : valueNext;
75132
75170
  const startAllowed = startIsNext ? nextAllowed : previousAllowed;
75133
75171
  const endAllowed = startIsNext ? previousAllowed : nextAllowed;
75172
+
75173
+ // A value asked to be SEEN arriving (`--navi-update:smooth`): the spin plays
75174
+ // the travel a chevron would play and lets the value land with the slides, so
75175
+ // a shortcut under it reads as "go to that day" rather than "be that day".
75176
+ // Whatever the distance it is ONE travel — a spin is not a wheel, and three
75177
+ // weeks are read as "it moved forward" rather than as twenty-one slides — so
75178
+ // the slide arriving is the one that shows the value being travelled to.
75179
+ //
75180
+ // Queued rather than held one at a time: two shortcuts pressed in a row are
75181
+ // two travels, each landing on its own value, and the one being played is
75182
+ // always the head. And started from a layout effect rather than from the
75183
+ // request itself, so the arriving slide is written before the container
75184
+ // places it — started there, the first frame would show the neighbouring
75185
+ // value and swap to the target mid-flight.
75186
+ const [travelsPending, setTravelsPendingState] = useState(TRAVELS_NONE);
75187
+ const travelsPendingRef = useRef(TRAVELS_NONE);
75188
+ const travelPending = travelsPending[0];
75189
+ const setTravelsPending = travels => {
75190
+ travelsPendingRef.current = travels.length ? travels : TRAVELS_NONE;
75191
+ setTravelsPendingState(travelsPendingRef.current);
75192
+ };
75193
+ const onRequestSetUIStateRef = useRef();
75194
+ onRequestSetUIStateRef.current = e => {
75195
+ if (editable) {
75196
+ // Nothing travels around a field: the value is typed into the middle.
75197
+ return;
75198
+ }
75199
+ const {
75200
+ behavior,
75201
+ value: valueRequested
75202
+ } = e.detail;
75203
+ if (behavior !== "smooth") {
75204
+ return;
75205
+ }
75206
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
75207
+ // Whoever asked to see less motion is answered first: the value is swapped
75208
+ // in place, which is what `--navi-update` alone does.
75209
+ return;
75210
+ }
75211
+ if (valueRequested === undefined || valueRequested === null || valueRequested === "") {
75212
+ // Nowhere to travel to — the erase a calendar offers, say.
75213
+ return;
75214
+ }
75215
+ const travelsQueued = travelsPendingRef.current;
75216
+ // Where the spin stands when this travel starts: behind the ones already
75217
+ // queued, rather than where the eye is now.
75218
+ const valueFrom = travelsQueued.length ? travelsQueued[travelsQueued.length - 1].value : valueShown;
75219
+ const direction = compareValues(valueRequested, valueFrom);
75220
+ if (direction === 0) {
75221
+ return;
75222
+ }
75223
+ const goingNext = direction > 0;
75224
+ // Out of reach, in both the ways a value can be: past the end of what one
75225
+ // may reach, or one step past it — the first step of the travel is the very
75226
+ // one the chevron that way is refused.
75227
+ 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);
75228
+ if (outOfReach) {
75229
+ // The same refusal a chevron gives, in the same words — said at the value
75230
+ // rather than at the chevron that way, because the press was not on a
75231
+ // chevron: what was asked about is the value, and that is where the
75232
+ // answer belongs.
75233
+ e.preventDefault();
75234
+ e.stopPropagation();
75235
+ openCallout(naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before"), {
75236
+ anchorElement: middleRef.current,
75237
+ status: "info",
75238
+ openingEvent: e.detail.event ?? e
75239
+ });
75240
+ return;
75241
+ }
75242
+ // Taken here and answered when the slides land (see onLoop): the control
75243
+ // must not be given the value on the way past, or there would be nothing
75244
+ // left to arrive.
75245
+ e.stopPropagation();
75246
+ // A direction on the map, the way a chevron says it (see wayOut): the
75247
+ // slides are laid out from start to end, so which value sits where is what
75248
+ // decides the way, not which value is bigger.
75249
+ const atStart = goingNext ? startIsNext : !startIsNext;
75250
+ const towardsEnd = atStart ? -1 : 1;
75251
+ setTravelsPending([...travelsQueued, {
75252
+ value: valueRequested,
75253
+ area: atStart ? "start" : "end",
75254
+ dx: vertical ? 0 : towardsEnd,
75255
+ dy: vertical ? towardsEnd : 0,
75256
+ event: e.detail.event ?? e
75257
+ }]);
75258
+ };
75259
+ useLayoutEffect(() => {
75260
+ const middleEl = middleRef.current;
75261
+ const onRequestSetUIState = e => {
75262
+ onRequestSetUIStateRef.current(e);
75263
+ };
75264
+ // On the way DOWN, and on the box holding the control rather than on the
75265
+ // control: the request is dispatched AT the control host, where the
75266
+ // controller is already listening, and the spin has to be able to take it
75267
+ // before the value is set.
75268
+ middleEl.addEventListener("navi_set_ui_state", onRequestSetUIState, true);
75269
+ return () => {
75270
+ middleEl.removeEventListener("navi_set_ui_state", onRequestSetUIState, true);
75271
+ };
75272
+ }, []);
75273
+ useLayoutEffect(() => {
75274
+ if (!travelPending) {
75275
+ return;
75276
+ }
75277
+ // What `--navi-left` and friends ask the container for (see
75278
+ // registerSlideCommand): asked directly because the container already
75279
+ // carries a `commandfor` of its own — pressing the middle opens the picker
75280
+ // — so a command triggered from it would be about the picker.
75281
+ dispatchCustomEvent(document.getElementById(containerId), "navi_slide_move", {
75282
+ event: travelPending.event,
75283
+ dx: travelPending.dx,
75284
+ dy: travelPending.dy
75285
+ });
75286
+ }, [travelPending]);
75287
+ // What the slide the travel is heading for shows: the value being travelled
75288
+ // TO, not the neighbour that place holds at rest — one travel covers the
75289
+ // whole distance, so the far value is what arrives.
75290
+ const valueDrawnAtStart = travelPending?.area === "start" ? travelPending.value : valueAtStart;
75291
+ const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
75134
75292
  const wayOut = atStart => {
75135
75293
  const isNext = atStart ? startIsNext : !startIsNext;
75136
75294
  return jsx(WayOut, {
@@ -75244,6 +75402,17 @@ const Spin = ({
75244
75402
  dy,
75245
75403
  event
75246
75404
  }) => {
75405
+ // A travel asked for by a VALUE (`--navi-update:smooth`):
75406
+ // that value is what lands, whatever the distance it covered,
75407
+ // and the press that asked for it is what says so to uiAction.
75408
+ // Recognized by that press: a chevron travel hurrying this one
75409
+ // along lands first and is a step of its own.
75410
+ const travelLanding = travelsPendingRef.current[0];
75411
+ if (travelLanding && event?.detail?.event === travelLanding.event) {
75412
+ setTravelsPending(travelsPendingRef.current.slice(1));
75413
+ setValue(travelLanding.value, travelLanding.event);
75414
+ return;
75415
+ }
75247
75416
  // One step, whichever axis it came from: the map is a line, so
75248
75417
  // only one of the two is ever anything but zero. Towards the
75249
75418
  // end of the line is a step forward, unless the line was laid
@@ -75276,7 +75445,7 @@ const Spin = ({
75276
75445
  area: "start",
75277
75446
  flex: true,
75278
75447
  align: "center",
75279
- children: renderValue(valueAtStart, {
75448
+ children: renderValue(valueDrawnAtStart, {
75280
75449
  maxLines
75281
75450
  })
75282
75451
  }), jsx(Slide, {
@@ -75296,7 +75465,7 @@ const Spin = ({
75296
75465
  area: "end",
75297
75466
  flex: true,
75298
75467
  align: "center",
75299
- children: renderValue(valueAtEnd, {
75468
+ children: renderValue(valueDrawnAtEnd, {
75300
75469
  maxLines
75301
75470
  })
75302
75471
  })]