@jsenv/dom 0.17.74 → 0.17.76

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 +98 -18
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -9350,8 +9350,11 @@ const createDragGestureController = (options = {}) => {
9350
9350
  const deltaX = Math.abs(dragX - grabX);
9351
9351
  const deltaY = Math.abs(dragY - grabY);
9352
9352
  if (direction.x && direction.y) {
9353
- // Both directions: check both axes
9354
- if (deltaX < threshold && deltaY < threshold) {
9353
+ // The distance walked, not the larger of the two axes: a browser
9354
+ // measures its own touch slop that way, and a gesture racing it for
9355
+ // a touch (see DRAG_START_THRESHOLD_TOUCH in drag_to_travel.js) must
9356
+ // not be 1.4 times later than it on a diagonal.
9357
+ if (Math.hypot(deltaX, deltaY) < threshold) {
9355
9358
  return dragData;
9356
9359
  }
9357
9360
  } else if (direction.x) {
@@ -12360,6 +12363,19 @@ import.meta.css = /* css */ [`:root[data-drag-travel-gesture] {
12360
12363
  // How far a pointer goes before it is a travel rather than a click: below this
12361
12364
  // a press that wandered a pixel is still a press, and nothing budges.
12362
12365
  const DRAG_START_THRESHOLD = 10;
12366
+ // …and for a finger or a pen, which the browser pans with and has a deadline:
12367
+ // ~8px in, it commits the touch to its own pan if that way is open to it (the
12368
+ // axis touch-action leaves, or any axis over a scroller inside the box, which
12369
+ // resets touch-action for what it holds). Chrome does it with the first
12370
+ // touchmove it sends, held back until the touch leaves its slop; Safari with
12371
+ // its pan recognizer. Refusing the touchmoves after that moment changes
12372
+ // nothing — the pointer is cancelled and the swipe does nothing, or the travel
12373
+ // already under way goes back. So the axis has to be read before it, with a
12374
+ // margin: Safari does not wait for the answer to a report that jumps over its
12375
+ // decision. The price is a tap that shook more than this: it is a swipe that
12376
+ // went nowhere, and its click is lost (Chrome would click it up to 8px, Safari
12377
+ // never once a touchmove is refused).
12378
+ const DRAG_START_THRESHOLD_TOUCH = 6;
12363
12379
  // How much the cross axis must dominate the travel axis, over the first
12364
12380
  // reported pixels, to take the press away from the box (see the axis decision
12365
12381
  // in onDrag). Sized against the two hands it separates: a thumb's arc leans up
@@ -12383,10 +12399,12 @@ const DRAG_COMMIT_RATIO = 0.3;
12383
12399
  // a movement from a tap that shook.
12384
12400
  const DRAG_DRIFT_VELOCITY = 0.03;
12385
12401
  const DRAG_DRIFT_DISTANCE = 8;
12386
- // Thrown back: a hand moving AWAY from what it was bringing in this fast asks
12387
- // for it to be put back, whatever the distance already covered. Well above the
12388
- // drift bar, so a hand merely wavering as it lets go does not read as a throw.
12389
- const DRAG_THROW_BACK_VELOCITY = 0.3;
12402
+ // Thrown: a hand moving this fast as it lets go says where things go, whatever
12403
+ // the distance already covered. AWAY from what it was bringing in, it asks for
12404
+ // it to be put back (travelsAfter); the way a travel it caught in flight was
12405
+ // already going, it asks for the one after (see thrownOn). Well above the drift
12406
+ // bar, so a hand merely wavering as it lets go does not read as a throw.
12407
+ const DRAG_THROW_VELOCITY = 0.3;
12390
12408
  // Pulling towards nothing: what travels follows at a fraction of the finger, so
12391
12409
  // the gesture is answered (something moves) while saying there is nothing that
12392
12410
  // way. Let go and it comes back — a wall one can lean on, never walk through.
@@ -12656,7 +12674,7 @@ const travelsAfter = ({
12656
12674
  // two thirds and thrown back still arrives: the gesture was read as the
12657
12675
  // place it was let go of rather than as a movement.
12658
12676
  if (
12659
- Math.abs(velocity) > DRAG_THROW_BACK_VELOCITY &&
12677
+ Math.abs(velocity) > DRAG_THROW_VELOCITY &&
12660
12678
  Math.sign(velocity) !== sign
12661
12679
  ) {
12662
12680
  return false;
@@ -12671,6 +12689,27 @@ const travelsAfter = ({
12671
12689
  return Math.abs(pulled) > size * commitRatio;
12672
12690
  };
12673
12691
 
12692
+ // A travel caught in flight and thrown on the way it was already going: a
12693
+ // second push, and it asks for the screen AFTER the one on its way — two
12694
+ // gestures, two screens, as a second push over a wheel's tail is. Read as the
12695
+ // verdict above, it only confirms what was already arriving: the throw stops
12696
+ // the travel at the press and lets it go on to the same place, and what the
12697
+ // hand feels is its second swipe swallowed. The hand has to have moved that way
12698
+ // by more than a tremor, so a travel merely touched still carries on to where
12699
+ // it was going (see the slack rule in travelsAfter).
12700
+ const thrownOn = ({ pulled, slack, velocity, towardsSomething, cancelled }) => {
12701
+ if (!slack || cancelled || !towardsSomething) {
12702
+ return false;
12703
+ }
12704
+ const way = Math.sign(slack);
12705
+ return (
12706
+ Math.sign(pulled) === way &&
12707
+ (pulled - slack) * way >= DRAG_START_THRESHOLD &&
12708
+ Math.sign(velocity) === way &&
12709
+ Math.abs(velocity) > DRAG_THROW_VELOCITY
12710
+ );
12711
+ };
12712
+
12674
12713
  /**
12675
12714
  * Read a press, and tell the caller what the hand is doing with it.
12676
12715
  *
@@ -12715,7 +12754,7 @@ const travelsAfter = ({
12715
12754
  * @param {(detail: {axis: string, pulled: number, size: number, progress: number, event: PointerEvent}) => void} options.onPull
12716
12755
  * - the finger has moved. `pulled` is in px from the resting place, `progress`
12717
12756
  * the same as a fraction of the box, signed the same way.
12718
- * @param {(detail: {axis: string, sign: number, event: PointerEvent}) => false|{size: number, travelBack?: boolean, travelOn?: boolean}} [options.onEdge]
12757
+ * @param {(detail: {axis: string, sign: number, thrown: boolean, event: PointerEvent}) => false|{size: number, travelBack?: boolean, travelOn?: boolean}} [options.onEdge]
12719
12758
  * - the hand has reached an end of the box it holds and keeps going: `sign`
12720
12759
  * says which one — the far edge, a box walked whole, or its start, a box
12721
12760
  * walked back to where it began. Answer with the geometry of the box that
@@ -12723,9 +12762,15 @@ const travelsAfter = ({
12723
12762
  * become its first ones, so nothing is spent twice and the hand feels one
12724
12763
  * continuous movement. Answer `false` (or leave it out) for a wall — the
12725
12764
  * gesture stays on the box it has and leans on it.
12765
+ * `thrown`: the hand did not walk there, it let go throwing a travel it had
12766
+ * caught on the way it was already going (see thrownOn). The box is asked for
12767
+ * at the release, with the picture still short of the end; answer `false` if
12768
+ * it cannot be handed over from there, and the travel simply arrives.
12726
12769
  * @param {(detail: {axis: string, pulled: number, size: number, sign: number, travels: boolean, cancelled: boolean, event: PointerEvent}) => void} options.onEnd
12727
12770
  * - the finger is off. `travels` is the gesture's answer: carry on to what was
12728
- * being pulled in, or put things back.
12771
+ * being pulled in, or put things back. `sign` is the side of what was being
12772
+ * pulled in — the side `pulled` is on, except after a throw handed over at
12773
+ * the release, where the picture still stands short of the new box.
12729
12774
  * @param {() => void} [options.onGiveUp] - the press is over without ever
12730
12775
  * becoming a travel: it stayed still, leaned the wrong way, or `onStart`
12731
12776
  * refused it. Nothing was painted and nothing has to be put back — this is
@@ -12777,6 +12822,10 @@ const startDragToTravel = (
12777
12822
  if (immediate && !axesLeft.includes(immediate)) {
12778
12823
  return null;
12779
12824
  }
12825
+ const startThreshold =
12826
+ pointerDownEvent.pointerType === "mouse"
12827
+ ? DRAG_START_THRESHOLD
12828
+ : DRAG_START_THRESHOLD_TOUCH;
12780
12829
 
12781
12830
  // The travel in hand: null until the finger has picked an axis and the caller
12782
12831
  // has accepted it.
@@ -12832,10 +12881,11 @@ const startDragToTravel = (
12832
12881
  // from where the finger IS: nothing is spent twice, and the gesture is one
12833
12882
  // movement rather than a wall the hand had to let go of to cross.
12834
12883
  // Returns where the new box stands, or null when there is nothing that way.
12835
- const relayTo = (sign, distance, gestureInfo) => {
12884
+ const relayTo = (sign, distance, gestureInfo, { thrown = false } = {}) => {
12836
12885
  const next = onEdge({
12837
12886
  axis: travel.axis,
12838
12887
  sign,
12888
+ thrown,
12839
12889
  event: gestureInfo.dragEvent,
12840
12890
  });
12841
12891
  if (!next || !next.size) {
@@ -12961,10 +13011,10 @@ const startDragToTravel = (
12961
13011
  // and is owed to it.
12962
13012
  origin: immediate
12963
13013
  ? 0
12964
- : covered > DRAG_START_THRESHOLD
12965
- ? DRAG_START_THRESHOLD
12966
- : covered < -DRAG_START_THRESHOLD
12967
- ? -DRAG_START_THRESHOLD
13014
+ : covered > startThreshold
13015
+ ? startThreshold
13016
+ : covered < -startThreshold
13017
+ ? -startThreshold
12968
13018
  : covered,
12969
13019
  pulled: started.slack || 0,
12970
13020
  };
@@ -13042,6 +13092,37 @@ const startDragToTravel = (
13042
13092
  axis === "x" ? gestureInfo.velocityX : gestureInfo.velocityY;
13043
13093
  const releaseEvent = gestureInfo.releaseEvent || gestureInfo.dragEvent;
13044
13094
  const { cancelled } = gestureInfo;
13095
+ if (thrownOn({ pulled, slack, velocity, towardsSomething, cancelled })) {
13096
+ const sign = pulled > 0 ? 1 : -1;
13097
+ // Where the picture stands, said from the box past the one in hand:
13098
+ // short of it by what is left of the travel that was caught.
13099
+ const relayed = relayTo(sign, pulled - sign * size, gestureInfo, {
13100
+ thrown: true,
13101
+ });
13102
+ if (relayed !== null) {
13103
+ // Nothing moves: the same place, measured from the new box — so the
13104
+ // caller's picture of the gesture agrees with it before it is let go.
13105
+ onPull({
13106
+ axis,
13107
+ pulled: relayed,
13108
+ size: travel.size,
13109
+ progress: relayed / travel.size,
13110
+ event: releaseEvent,
13111
+ });
13112
+ onEnd({
13113
+ axis,
13114
+ pulled: relayed,
13115
+ size: travel.size,
13116
+ // The way it was thrown, not the side the picture stands on: it
13117
+ // still sits short of the box it was bringing in.
13118
+ sign,
13119
+ travels: true,
13120
+ cancelled: false,
13121
+ event: releaseEvent,
13122
+ });
13123
+ return;
13124
+ }
13125
+ }
13045
13126
  onEnd({
13046
13127
  axis,
13047
13128
  pulled,
@@ -13094,7 +13175,7 @@ const startDragToTravel = (
13094
13175
  } else {
13095
13176
  dragAfterIntent(pointerDownEvent, grab, {
13096
13177
  longPress: false,
13097
- threshold: DRAG_START_THRESHOLD,
13178
+ threshold: startThreshold,
13098
13179
  selection: "manual",
13099
13180
  });
13100
13181
  }
@@ -13819,9 +13900,8 @@ const installPanZoom = (
13819
13900
  };
13820
13901
 
13821
13902
  // 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
13903
+ // tap, whatever `touch-action` says (see navi's docs/mobile_touch.md), so the
13904
+ // surface refuses every touchmove while it moves. Whether one can be refused at all is settled when
13825
13905
  // the touch begins, before the surface moves: the listener goes down with the
13826
13906
  // surface and refuses nothing until then — a tap, a hold, or under
13827
13907
  // `afterHold` the page scrolling, which is the whole point of the wait.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.74",
3
+ "version": "0.17.76",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {