@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.
@@ -30351,6 +30351,17 @@ const RouteTravel = ({
30351
30351
  ended: false
30352
30352
  };
30353
30353
  travelRef.current = travel;
30354
+ // A press landing on a playing travel lands on the document root: the box
30355
+ // is captured by the transition and cannot be pointed at, so neither its
30356
+ // touch-action nor its own touchmove listener is on the touch's path. Left
30357
+ // there, the browser takes the finger's first vertical pixels for a scroll
30358
+ // and cancels the pointer under a travel that was just caught. Refusable
30359
+ // from the root for the length of the travel, the gesture that catches it
30360
+ // refuses every touchmove of that press (it is started from the grab), and
30361
+ // the finger's only answer is the travel it landed on.
30362
+ document.documentElement.addEventListener("touchmove", keepTouchRefusable, {
30363
+ passive: false
30364
+ });
30354
30365
  // Whether the document's offset is this row's business at all (see
30355
30366
  // pagesScrollTheDocument), asked once and before anything is swapped.
30356
30367
  travel.scrollsDocument = pagesScrollTheDocument(elementRef.current);
@@ -30724,6 +30735,9 @@ const RouteTravel = ({
30724
30735
  travel.ended = true;
30725
30736
  travel.dropHold?.();
30726
30737
  travel.dropHold = null;
30738
+ if (travelRef.current === travel) {
30739
+ document.documentElement.removeEventListener("touchmove", keepTouchRefusable);
30740
+ }
30727
30741
  // The offset on the scrollport belongs to a page again — the one that
30728
30742
  // arrived, or the one put back. A travel that never got as far as saying
30729
30743
  // which still has to give the recording back; the gesture's own
@@ -31869,9 +31883,10 @@ registerNaviCommand("--navi-void", (source) => {
31869
31883
  });
31870
31884
 
31871
31885
  // "--navi-update:smooth" asks the control to be SEEN moving to the value — a
31872
- // wheel scrolls to it rather than swapping its digits — so a shortcut under
31873
- // two wheels shows which one it changed, and by how much. The value itself is
31874
- // set at once either way; only the drawing is concerned.
31886
+ // wheel scrolls to it rather than swapping its digits, a spin plays the travel
31887
+ // its chevron would play — so a shortcut under a control shows which value it
31888
+ // changed, and by how much. What that movement is, and whether the value
31889
+ // travels with it, belongs to the control: see control_value.md.
31875
31890
  registerNaviCommand("--navi-update", (source, event, { argument }) => {
31876
31891
  const target =
31877
31892
  resolveExplicitTarget(source) || resolveFirstParentControl(source);
@@ -62748,11 +62763,7 @@ const PickerNative = props => {
62748
62763
  e.preventDefault();
62749
62764
  },
62750
62765
  allowed: () => {
62751
- try {
62752
- pickerInput.showPicker();
62753
- } catch {
62754
- pickerInput.click();
62755
- }
62766
+ showNativePicker(pickerInput, e);
62756
62767
  }
62757
62768
  });
62758
62769
  },
@@ -62768,11 +62779,7 @@ const PickerNative = props => {
62768
62779
  const pickerInput = getPickerInput(pickerEl);
62769
62780
  if (pickerInput.type === "color") ; else {
62770
62781
  // other picker might not open the picker when clicking the input surface (only the calendar picker for instance would open)
62771
- try {
62772
- pickerInput.showPicker();
62773
- } catch {
62774
- pickerInput.click();
62775
- }
62782
+ showNativePicker(pickerInput, e);
62776
62783
  }
62777
62784
  }
62778
62785
  };
@@ -63657,6 +63664,32 @@ const PickerCalloutPopup = ({
63657
63664
  );
63658
63665
  };
63659
63666
 
63667
+ // Under a finger the input is focused before the browser's picker is asked
63668
+ // for: iOS presents a date/time input's picker as that input's own inputView,
63669
+ // so what the tap focuses is what opens. showPicker() presents nothing there
63670
+ // and throws nothing either, so the catch below cannot be what learns about
63671
+ // it — and a synthetic click() does not focus a date input there.
63672
+ // A departure from the coarse-pointer policy (see focus_transfer.js), argued
63673
+ // here: the press asked for this input's picker, and the types whose focus
63674
+ // would raise the on-screen keyboard instead are held readOnly already (see
63675
+ // NON_MOBILE_KEYBOARD_TYPES in picker.jsx). No ring: nobody is about to type.
63676
+ // Not with a mouse, where showPicker() presents the picker on its own: the
63677
+ // keyboard stays where it was — a spin's middle, the button that sent the
63678
+ // command — so one keeps going with the keys once the calendar closes, rather
63679
+ // than on an input drawn behind (see the headless variant in picker.jsx).
63680
+ const showNativePicker = (pickerInput, event) => {
63681
+ if (findEvent(event, isTouchDrivenEvent)) {
63682
+ moveFocusTo(pickerInput, {
63683
+ focusVisible: false
63684
+ });
63685
+ }
63686
+ try {
63687
+ pickerInput.showPicker();
63688
+ } catch {
63689
+ pickerInput.click();
63690
+ }
63691
+ };
63692
+
63660
63693
  installImportMetaCssBuild(import.meta);
63661
63694
  const css$A = /* css */`.navi_picker_confirm_body {
63662
63695
  min-width: 180px;
@@ -71905,6 +71938,7 @@ const css$t = /* css */`@layer navi {
71905
71938
  --picker-padding-y-default: 0;
71906
71939
  --picker-border-width: 0px;
71907
71940
  --picker-border-color: transparent;
71941
+ --picker-outline-width: 0px;
71908
71942
  --picker-border-color-hover: var(--picker-border-color);
71909
71943
  --picker-border-color-readonly: var(--picker-border-color);
71910
71944
  --picker-border-color-disabled: var(--picker-border-color);
@@ -72966,6 +73000,10 @@ const css$s = /* css */`@layer navi {
72966
73000
  }
72967
73001
  `;
72968
73002
 
73003
+ // No travel waiting to be played. One array rather than a new one per render,
73004
+ // so "nothing pending" compares equal to itself.
73005
+ const TRAVELS_NONE = [];
73006
+
72969
73007
  /**
72970
73008
  * @type {import("ignore:preact").FunctionComponent<{
72971
73009
  * value?: any,
@@ -73224,6 +73262,126 @@ const Spin = ({
73224
73262
  const valueAtEnd = startIsNext ? valuePrevious : valueNext;
73225
73263
  const startAllowed = startIsNext ? nextAllowed : previousAllowed;
73226
73264
  const endAllowed = startIsNext ? previousAllowed : nextAllowed;
73265
+
73266
+ // A value asked to be SEEN arriving (`--navi-update:smooth`): the spin plays
73267
+ // the travel a chevron would play and lets the value land with the slides, so
73268
+ // a shortcut under it reads as "go to that day" rather than "be that day".
73269
+ // Whatever the distance it is ONE travel — a spin is not a wheel, and three
73270
+ // weeks are read as "it moved forward" rather than as twenty-one slides — so
73271
+ // the slide arriving is the one that shows the value being travelled to.
73272
+ //
73273
+ // Queued rather than held one at a time: two shortcuts pressed in a row are
73274
+ // two travels, each landing on its own value, and the one being played is
73275
+ // always the head. And started from a layout effect rather than from the
73276
+ // request itself, so the arriving slide is written before the container
73277
+ // places it — started there, the first frame would show the neighbouring
73278
+ // value and swap to the target mid-flight.
73279
+ const [travelsPending, setTravelsPendingState] = useState(TRAVELS_NONE);
73280
+ const travelsPendingRef = useRef(TRAVELS_NONE);
73281
+ const travelPending = travelsPending[0];
73282
+ const setTravelsPending = travels => {
73283
+ travelsPendingRef.current = travels.length ? travels : TRAVELS_NONE;
73284
+ setTravelsPendingState(travelsPendingRef.current);
73285
+ };
73286
+ const onRequestSetUIStateRef = useRef();
73287
+ onRequestSetUIStateRef.current = e => {
73288
+ if (editable) {
73289
+ // Nothing travels around a field: the value is typed into the middle.
73290
+ return;
73291
+ }
73292
+ const {
73293
+ behavior,
73294
+ value: valueRequested
73295
+ } = e.detail;
73296
+ if (behavior !== "smooth") {
73297
+ return;
73298
+ }
73299
+ if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
73300
+ // Whoever asked to see less motion is answered first: the value is swapped
73301
+ // in place, which is what `--navi-update` alone does.
73302
+ return;
73303
+ }
73304
+ if (valueRequested === undefined || valueRequested === null || valueRequested === "") {
73305
+ // Nowhere to travel to — the erase a calendar offers, say.
73306
+ return;
73307
+ }
73308
+ const travelsQueued = travelsPendingRef.current;
73309
+ // Where the spin stands when this travel starts: behind the ones already
73310
+ // queued, rather than where the eye is now.
73311
+ const valueFrom = travelsQueued.length ? travelsQueued[travelsQueued.length - 1].value : valueShown;
73312
+ const direction = compareValues(valueRequested, valueFrom);
73313
+ if (direction === 0) {
73314
+ return;
73315
+ }
73316
+ const goingNext = direction > 0;
73317
+ // Out of reach, in both the ways a value can be: past the end of what one
73318
+ // may reach, or one step past it — the first step of the travel is the very
73319
+ // one the chevron that way is refused.
73320
+ 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);
73321
+ if (outOfReach) {
73322
+ // The same refusal a chevron gives, in the same words — said at the value
73323
+ // rather than at the chevron that way, because the press was not on a
73324
+ // chevron: what was asked about is the value, and that is where the
73325
+ // answer belongs.
73326
+ e.preventDefault();
73327
+ e.stopPropagation();
73328
+ openCallout(naviI18n(goingNext ? "spin.nothing_after" : "spin.nothing_before"), {
73329
+ anchorElement: middleRef.current,
73330
+ status: "info",
73331
+ openingEvent: e.detail.event ?? e
73332
+ });
73333
+ return;
73334
+ }
73335
+ // Taken here and answered when the slides land (see onLoop): the control
73336
+ // must not be given the value on the way past, or there would be nothing
73337
+ // left to arrive.
73338
+ e.stopPropagation();
73339
+ // A direction on the map, the way a chevron says it (see wayOut): the
73340
+ // slides are laid out from start to end, so which value sits where is what
73341
+ // decides the way, not which value is bigger.
73342
+ const atStart = goingNext ? startIsNext : !startIsNext;
73343
+ const towardsEnd = atStart ? -1 : 1;
73344
+ setTravelsPending([...travelsQueued, {
73345
+ value: valueRequested,
73346
+ area: atStart ? "start" : "end",
73347
+ dx: vertical ? 0 : towardsEnd,
73348
+ dy: vertical ? towardsEnd : 0,
73349
+ event: e.detail.event ?? e
73350
+ }]);
73351
+ };
73352
+ useLayoutEffect(() => {
73353
+ const middleEl = middleRef.current;
73354
+ const onRequestSetUIState = e => {
73355
+ onRequestSetUIStateRef.current(e);
73356
+ };
73357
+ // On the way DOWN, and on the box holding the control rather than on the
73358
+ // control: the request is dispatched AT the control host, where the
73359
+ // controller is already listening, and the spin has to be able to take it
73360
+ // before the value is set.
73361
+ middleEl.addEventListener("navi_set_ui_state", onRequestSetUIState, true);
73362
+ return () => {
73363
+ middleEl.removeEventListener("navi_set_ui_state", onRequestSetUIState, true);
73364
+ };
73365
+ }, []);
73366
+ useLayoutEffect(() => {
73367
+ if (!travelPending) {
73368
+ return;
73369
+ }
73370
+ // What `--navi-left` and friends ask the container for (see
73371
+ // registerSlideCommand): asked directly because the container already
73372
+ // carries a `commandfor` of its own — pressing the middle opens the picker
73373
+ // — so a command triggered from it would be about the picker.
73374
+ dispatchCustomEvent(document.getElementById(containerId), "navi_slide_move", {
73375
+ event: travelPending.event,
73376
+ dx: travelPending.dx,
73377
+ dy: travelPending.dy
73378
+ });
73379
+ }, [travelPending]);
73380
+ // What the slide the travel is heading for shows: the value being travelled
73381
+ // TO, not the neighbour that place holds at rest — one travel covers the
73382
+ // whole distance, so the far value is what arrives.
73383
+ const valueDrawnAtStart = travelPending?.area === "start" ? travelPending.value : valueAtStart;
73384
+ const valueDrawnAtEnd = travelPending?.area === "end" ? travelPending.value : valueAtEnd;
73227
73385
  const wayOut = atStart => {
73228
73386
  const isNext = atStart ? startIsNext : !startIsNext;
73229
73387
  return jsx(WayOut, {
@@ -73337,6 +73495,17 @@ const Spin = ({
73337
73495
  dy,
73338
73496
  event
73339
73497
  }) => {
73498
+ // A travel asked for by a VALUE (`--navi-update:smooth`):
73499
+ // that value is what lands, whatever the distance it covered,
73500
+ // and the press that asked for it is what says so to uiAction.
73501
+ // Recognized by that press: a chevron travel hurrying this one
73502
+ // along lands first and is a step of its own.
73503
+ const travelLanding = travelsPendingRef.current[0];
73504
+ if (travelLanding && event?.detail?.event === travelLanding.event) {
73505
+ setTravelsPending(travelsPendingRef.current.slice(1));
73506
+ setValue(travelLanding.value, travelLanding.event);
73507
+ return;
73508
+ }
73340
73509
  // One step, whichever axis it came from: the map is a line, so
73341
73510
  // only one of the two is ever anything but zero. Towards the
73342
73511
  // end of the line is a step forward, unless the line was laid
@@ -73369,7 +73538,7 @@ const Spin = ({
73369
73538
  area: "start",
73370
73539
  flex: true,
73371
73540
  align: "center",
73372
- children: renderValue(valueAtStart, {
73541
+ children: renderValue(valueDrawnAtStart, {
73373
73542
  maxLines
73374
73543
  })
73375
73544
  }), jsx(Slide, {
@@ -73389,7 +73558,7 @@ const Spin = ({
73389
73558
  area: "end",
73390
73559
  flex: true,
73391
73560
  align: "center",
73392
- children: renderValue(valueAtEnd, {
73561
+ children: renderValue(valueDrawnAtEnd, {
73393
73562
  maxLines
73394
73563
  })
73395
73564
  })]