@jsenv/dom 0.17.73 → 0.17.74

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.
Files changed (2) hide show
  1. package/dist/jsenv_dom.js +94 -35
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -4549,6 +4549,39 @@ const clickIsSuppressed = (clickEvent) =>
4549
4549
  it. */
4550
4550
  const MENU_GRACE_AFTER_CANCEL = 300;
4551
4551
 
4552
+ /* The presses a gesture has taken, and the holds still counting on each. Keyed
4553
+ by the `pointerdown` itself: every listener of one press is handed that same
4554
+ object, so a hold asked for further along the dispatch that took the press is
4555
+ told as surely as one already counting. */
4556
+ const takenPressSet = new WeakSet();
4557
+ const holdGiveUpsByPress = new WeakMap();
4558
+
4559
+ /**
4560
+ * A gesture takes the press: the pointer is captured on `element`, and every
4561
+ * hold waiting on that press gives up — whatever the press turned out to be, it
4562
+ * is that gesture and not a hold.
4563
+ *
4564
+ * Said here rather than left to the capture, because the browser announces a
4565
+ * capture only when it MOVES. A finger lands already captured to what it touched
4566
+ * (see implicitCaptureHolder below): a gesture taking that same element changes
4567
+ * nothing the browser reports, and a hold listening for the capture alone
4568
+ * answers in the middle of the gesture that took its press.
4569
+ *
4570
+ * @param {PointerEvent} pressEvent The `pointerdown` of the press taken.
4571
+ * @param {Element} element What holds the pointer from here.
4572
+ */
4573
+ const takePress = (pressEvent, element) => {
4574
+ element.setPointerCapture(pressEvent.pointerId);
4575
+ takenPressSet.add(pressEvent);
4576
+ const holdGiveUps = holdGiveUpsByPress.get(pressEvent);
4577
+ if (!holdGiveUps) {
4578
+ return;
4579
+ }
4580
+ for (const giveUp of [...holdGiveUps]) {
4581
+ giveUp();
4582
+ }
4583
+ };
4584
+
4552
4585
  /**
4553
4586
  * Waits for a press to be held still, then tells the caller.
4554
4587
  *
@@ -4563,7 +4596,7 @@ const MENU_GRACE_AFTER_CANCEL = 300;
4563
4596
  * @param {function} [options.onPressStart] The wait began (a cue that the press
4564
4597
  * counts).
4565
4598
  * @param {function} [options.onPressCancel] The pointer moved or lifted before
4566
- * the wait was over.
4599
+ * the wait was over, or another gesture took the press (see takePress).
4567
4600
  * @param {(pressEvent: PointerEvent, handle: {endPress: () => void}) => void} options.onPressHeld
4568
4601
  * The wait completed. Whatever the press now means outlives this call — an
4569
4602
  * object is being carried, a menu is open under the finger — so the caller
@@ -4703,22 +4736,28 @@ const waitForPressHeld = (
4703
4736
  }
4704
4737
  cancelPress(pointerEndEvent);
4705
4738
  };
4706
- // Somebody else settled what this press is. Taking the pointer is how a gesture
4707
- // says it — and it says it about the same finger this wait is counting on, so
4708
- // whatever the press turned out to be, it is not a hold. Two waits on one press
4709
- // is the ordinary case rather than an odd one: an element that can be picked up
4710
- // AND held answers a finger with two delays, the shorter one wins, and without
4711
- // this the longer one would answer a hundred milliseconds into the carry.
4712
- // The listener goes with the countdown, so the gesture THIS wait starts (which
4713
- // captures the pointer from inside onPressHeld) never reaches it.
4739
+ // Somebody else settled what this press is, and whatever it turned out to be,
4740
+ // it is not a hold. Two waits on one press is the ordinary case rather than an
4741
+ // odd one: an element that can be picked up AND held answers a finger with two
4742
+ // delays, the shorter one wins, and without this the longer one would answer a
4743
+ // hundred milliseconds into the carry. A gesture says it with takePress. Both
4744
+ // ways of hearing it go with the countdown, so the gesture THIS wait starts
4745
+ // (which takes the press from inside onPressHeld) never reaches them.
4746
+ let holdGiveUps = holdGiveUpsByPress.get(pressEvent);
4747
+ if (!holdGiveUps) {
4748
+ holdGiveUps = new Set();
4749
+ holdGiveUpsByPress.set(pressEvent, holdGiveUps);
4750
+ }
4751
+ holdGiveUps.add(cancelPress);
4752
+ // A gesture that captures the pointer without takePress: heard only when the
4753
+ // capture moves, the one it takes on the implicit holder being announced to
4754
+ // nobody (see takePress).
4714
4755
  const onGotPointerCapture = (captureEvent) => {
4715
4756
  if (captureEvent.pointerId !== pointerId) {
4716
4757
  return;
4717
4758
  }
4718
4759
  if (captureEvent.target === implicitCaptureHolder) {
4719
4760
  // The capture the press was born with, not one somebody took (see above).
4720
- // A gesture taking that same element instead changes nothing for the
4721
- // browser, so it announces nothing, and there is nothing to miss here.
4722
4761
  return;
4723
4762
  }
4724
4763
  cancelPress(captureEvent);
@@ -4730,13 +4769,20 @@ const waitForPressHeld = (
4730
4769
  window.addEventListener("pointercancel", onPointerEnd);
4731
4770
  window.addEventListener("gotpointercapture", onGotPointerCapture, true);
4732
4771
  countdownCleanupCallbacks.push(() => {
4772
+ holdGiveUps.delete(cancelPress);
4733
4773
  window.removeEventListener("pointermove", onPointerMove);
4734
4774
  window.removeEventListener("pointerup", onPointerEnd);
4735
4775
  window.removeEventListener("pointercancel", onPointerEnd);
4736
4776
  window.removeEventListener("gotpointercapture", onGotPointerCapture, true);
4737
4777
  });
4738
4778
 
4739
- onPressStart?.(pressEvent);
4779
+ if (takenPressSet.has(pressEvent)) {
4780
+ // Taken before this wait was asked for, by a listener earlier in the same
4781
+ // dispatch: over before it began.
4782
+ cancelPress();
4783
+ } else {
4784
+ onPressStart?.(pressEvent);
4785
+ }
4740
4786
 
4741
4787
  return {
4742
4788
  cancel: () => {
@@ -9615,7 +9661,10 @@ const createDragGestureController = (options = {}) => {
9615
9661
  // last — which is what tells a hand-over from a capture the browser
9616
9662
  // dropped on its own (see onCaptureLost).
9617
9663
  captureHolderByPointerId.set(grabEvent.pointerId, dragGesture);
9618
- target.setPointerCapture(grabEvent.pointerId);
9664
+ // The capture is often where the finger landed, which the browser
9665
+ // does not announce again: takePress is what tells a hold waiting
9666
+ // on this press that it is a drag.
9667
+ takePress(grabEvent, target);
9619
9668
  };
9620
9669
  if (!options?.pointerCaptureDeferred) {
9621
9670
  dragGesture.capturePointer();
@@ -13603,8 +13652,8 @@ const installPanZoom = (
13603
13652
  // is the pan on contact, which a surface that does not pan has none of.
13604
13653
  keepTheHand();
13605
13654
  }
13606
- for (const pointerId of pointers.keys()) {
13607
- element.setPointerCapture(pointerId);
13655
+ for (const pointer of pointers.values()) {
13656
+ takePress(pointer.pressEvent, element);
13608
13657
  }
13609
13658
  anchor = readHand(anchorWhere);
13610
13659
  // The click the release leaves behind is not for what is under the hand.
@@ -13675,6 +13724,7 @@ const installPanZoom = (
13675
13724
  window.addEventListener("pointercancel", onPointerEnd, true);
13676
13725
  }
13677
13726
  const pointer = {
13727
+ pressEvent: event,
13678
13728
  x: event.clientX,
13679
13729
  y: event.clientY,
13680
13730
  startX: event.clientX,
@@ -13686,7 +13736,7 @@ const installPanZoom = (
13686
13736
  };
13687
13737
  pointers.set(event.pointerId, pointer);
13688
13738
  if (active) {
13689
- element.setPointerCapture(event.pointerId);
13739
+ takePress(event, element);
13690
13740
  anchor = readHand();
13691
13741
  return;
13692
13742
  }
@@ -13768,13 +13818,13 @@ const installPanZoom = (
13768
13818
  }
13769
13819
  };
13770
13820
 
13771
- // Whether a touchmove can be refused AT ALL is decided when the touch begins,
13772
- // from the non-passive listeners the browser knows about then — and here the
13773
- // gesture that would refuse it is not born until the hold is over. So the
13774
- // listener goes down with the surface and refuses nothing until the surface is
13775
- // the one moving: before that the page is scrolling, which is the whole point
13776
- // of the wait. Only in `afterHold`; a surface at `touch-action: none` has
13777
- // already been left nothing to refuse.
13821
+ // A touch drag left unrefused makes Chrome Android drop the click of the NEXT
13822
+ // tap, whatever `touch-action` says (see navi's
13823
+ // docs/mobile_tap_suppression_after_drag.md), so the surface refuses every
13824
+ // touchmove while it moves. Whether one can be refused at all is settled when
13825
+ // the touch begins, before the surface moves: the listener goes down with the
13826
+ // surface and refuses nothing until then — a tap, a hold, or under
13827
+ // `afterHold` the page scrolling, which is the whole point of the wait.
13778
13828
  const preventTouchScroll = (touchMoveEvent) => {
13779
13829
  if (active && touchMoveEvent.cancelable) {
13780
13830
  touchMoveEvent.preventDefault();
@@ -13847,11 +13897,9 @@ const installPanZoom = (
13847
13897
 
13848
13898
  element.addEventListener("pointerdown", onPointerDown);
13849
13899
  element.addEventListener("lostpointercapture", onLostPointerCapture);
13850
- if (afterHold) {
13851
- element.addEventListener("touchmove", preventTouchScroll, {
13852
- passive: false,
13853
- });
13854
- }
13900
+ element.addEventListener("touchmove", preventTouchScroll, {
13901
+ passive: false,
13902
+ });
13855
13903
  if (onZoom) {
13856
13904
  element.addEventListener("wheel", onWheel, { passive: false });
13857
13905
  }
@@ -14783,12 +14831,13 @@ const refuseDragTo = (
14783
14831
  onRefuse?.({ event });
14784
14832
  return null;
14785
14833
  }
14786
- // Nothing is carried, and the pointer is taken all the same: taking it is
14834
+ // Nothing is carried, and the press is taken all the same: taking it is
14787
14835
  // how a gesture says the press is settled, and another wait counting on the
14788
- // same finger reads it (see press_held.js). A `longpress` declared beside
14789
- // the drag is answered by the grab when there is one; there must be no
14836
+ // same finger gives up (see takePress in press_held.js), even when the
14837
+ // finger landed on this very element. A `longpress` declared beside the
14838
+ // drag is answered by the grab when there is one; there must be no
14790
14839
  // difference when there is none.
14791
- draggedElement.setPointerCapture(event.pointerId);
14840
+ takePress(event, draggedElement);
14792
14841
  // And the click the browser fires afterwards belongs to what answered the
14793
14842
  // press, a refusal included: something pulled and told to stay put must not
14794
14843
  // also be clicked. Lifted at the release rather than with the click, which
@@ -15504,14 +15553,16 @@ const getRectInside = (element, containerElement) => {
15504
15553
  //
15505
15554
  // Layer 2 — inner clone (navi-drag-clone):
15506
15555
  // A deep clone of the grabbed element, and what the eye follows — so it is
15507
- // the one named (view-transition-name: navi-drag-clone).
15556
+ // the one named: a view-transition-name of its own (navi-drag-clone-<n>),
15557
+ // and the view-transition-class navi-drag-clone that every copy shares.
15508
15558
  // Casts the shadow and applies transform: scale(var(--drag-clone-scale, 1.03))
15509
15559
  // via the CSS rule for [navi-drag-clone], giving the "lifted" feel. The
15510
15560
  // transform-origin is set to the grab point so the element expands naturally
15511
15561
  // from where the user clicked.
15512
15562
  // On release, the `navi-drag-clone` attribute is removed inside
15513
15563
  // startViewTransition to drop the scale back to 1 as the "new" state; the
15514
- // name stays, so the scale is morphed with the box rather than cross-faded.
15564
+ // name and the class stay, so the scale is morphed with the box rather than
15565
+ // cross-faded.
15515
15566
 
15516
15567
  // The chevron is the one the table's column drop preview uses, rotated by the
15517
15568
  // CSS above so each cap points into the line.
@@ -15756,6 +15807,7 @@ const liftDragClone = (cloneWrapper, pointerEvent) => {
15756
15807
  cloneWrapper.firstElementChild.setAttribute("navi-drag-clone", "");
15757
15808
  };
15758
15809
 
15810
+ let dragCloneCount = 0;
15759
15811
  const createDragClone = (element, pointerEvent) => {
15760
15812
  const rect = element.getBoundingClientRect();
15761
15813
 
@@ -15792,7 +15844,14 @@ const createDragClone = (element, pointerEvent) => {
15792
15844
  // dresses `[data-grabbed]` on its own element once, and the copy is that element.
15793
15845
  // (The original wears it too, but it is hidden — see navi-drag-clone-source.)
15794
15846
  elementClone.setAttribute("data-grabbed", "");
15795
- elementClone.style.viewTransitionName = "navi-drag-clone";
15847
+ // A name of its own: a copy outlives its gesture while its answer is asked,
15848
+ // so two can be on screen at once, and a name claimed twice aborts the
15849
+ // transition. The class is what every copy shares — inline like the name,
15850
+ // since a landing takes the [navi-drag-clone] attribute off before the
15851
+ // capture its group's class is read from.
15852
+ dragCloneCount++;
15853
+ elementClone.style.viewTransitionName = `navi-drag-clone-${dragCloneCount}`;
15854
+ elementClone.style.viewTransitionClass = "navi-drag-clone";
15796
15855
  // The copy takes the wrapper's box, and nothing the page said about where the
15797
15856
  // ORIGINAL stands may place it: a piece drawn at "left: 40px; top: 130px" on
15798
15857
  // its board, a marker centred by a translate, a card pushed by a margin — the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.73",
3
+ "version": "0.17.74",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {