@jsenv/dom 0.17.21 → 0.17.22

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 +109 -9
  2. package/package.json +1 -1
package/dist/jsenv_dom.js CHANGED
@@ -8159,6 +8159,18 @@ const css$5 = /* css */`
8159
8159
  }
8160
8160
  `;
8161
8161
  import.meta.css = [css$5, "@jsenv/dom/src/interaction/drag/drag_gesture.js"];
8162
+
8163
+ /*
8164
+ * Who asked for the capture of a pointer, last. This module is the only place
8165
+ * that ever takes one, so the answer says whether a capture that goes was HANDED
8166
+ * OVER — somebody here took it — or simply LET GO OF by the browser, which does
8167
+ * that on its own more often than the specification suggests, in the middle of a
8168
+ * gesture whose hand is still down and still moving.
8169
+ *
8170
+ * The two must not be answered the same way, and nothing in the event tells them
8171
+ * apart: `lostpointercapture` says the same thing either way.
8172
+ */
8173
+ const captureHolderByPointerId = new Map();
8162
8174
  const createDragGestureController = (options = {}) => {
8163
8175
  const {
8164
8176
  name,
@@ -8786,6 +8798,11 @@ const createDragGestureController = (options = {}) => {
8786
8798
  let captured = false;
8787
8799
  dragGesture.capturePointer = () => {
8788
8800
  captured = true;
8801
+ // Written down before it is taken: this is the only place a capture
8802
+ // is ever taken from, so what this map says is who asked for it
8803
+ // last — which is what tells a hand-over from a capture the browser
8804
+ // dropped on its own (see onCaptureLost).
8805
+ captureHolderByPointerId.set(grabEvent.pointerId, dragGesture);
8789
8806
  target.setPointerCapture(grabEvent.pointerId);
8790
8807
  };
8791
8808
  if (!options?.pointerCaptureDeferred) {
@@ -8833,16 +8850,46 @@ const createDragGestureController = (options = {}) => {
8833
8850
  // above it), and taken as our own it kills the new gesture one
8834
8851
  // millisecond after it started.
8835
8852
  //
8836
- // And when it IS ours, it is a loss, never an end: the ends a gesture
8837
- // has are the pointer going up and the pointer being cancelled, both
8838
- // listened for below. A capture that goes while the pointer is still
8839
- // down was taken by another gesture, or by the element it was held
8840
- // on leaving the document — and what was being carried must go back
8841
- // rather than land wherever the hand happened to be.
8853
+ // And when it IS ours, it is a loss and never an end: the ends a
8854
+ // gesture has are the pointer going up and the pointer being
8855
+ // cancelled, both listened for below. What a loss MEANS is the
8856
+ // question, and the event does not answer it two very different
8857
+ // things arrive as the same one:
8858
+ //
8859
+ // - it was HANDED OVER: another gesture took the pointer, or the
8860
+ // element it was held on left the document. There is nothing to go
8861
+ // on with, and what was being carried must go back rather than land
8862
+ // wherever the hand happened to be.
8863
+ // - it was simply LET GO OF by the browser, with the hand still down
8864
+ // and still moving. It happens, and not rarely: the capture is a
8865
+ // guarantee that events keep coming to one element, and the browser
8866
+ // drops it for reasons of its own that no code here can see. Killing
8867
+ // the gesture for that is dropping an object mid-air — the copy
8868
+ // vanishes, the place the hint had lit up is thrown away, and the
8869
+ // hand is left having done nothing.
8870
+ //
8871
+ // They are told apart by who asked (see captureHolderByPointerId): a
8872
+ // capture nobody here took, on an element still in the document, was
8873
+ // let go of. The gesture does not need it — every move and the release
8874
+ // are read at the WINDOW, not at the element — so it goes on.
8842
8875
  const onCaptureLost = pointerEvent => {
8843
8876
  if (!captured || pointerEvent.target !== target) {
8844
8877
  return;
8845
8878
  }
8879
+ const handedOver = captureHolderByPointerId.get(grabEvent.pointerId) !== dragGesture;
8880
+ if (!handedOver && target.isConnected) {
8881
+ // Nobody took it and the element it was held on is still there:
8882
+ // the browser let the capture go by itself, which it does — a
8883
+ // node moved by a re-render and put straight back, a decision of
8884
+ // its own we are not told the reason for. The hand has not let go
8885
+ // of anything, so neither does the gesture: it is a guarantee that
8886
+ // was lost, not the gesture. Every move and the release are read
8887
+ // at the window (see below), so it goes on without it rather than
8888
+ // dropping what is still being carried — and the drop the hand was
8889
+ // aiming at, which the hint had already lit up, still happens.
8890
+ captured = false;
8891
+ return;
8892
+ }
8846
8893
  onRelease(pointerEvent, {
8847
8894
  cancelled: true
8848
8895
  });
@@ -8902,6 +8949,9 @@ const createDragGestureController = (options = {}) => {
8902
8949
  // that is up no longer exists — the browser has already dropped the
8903
8950
  // capture with it, and asking again throws ("No active pointer with
8904
8951
  // the given id is found") on the most ordinary release there is.
8952
+ if (captureHolderByPointerId.get(grabEvent.pointerId) === dragGesture) {
8953
+ captureHolderByPointerId.delete(grabEvent.pointerId);
8954
+ }
8905
8955
  if (captured && target.hasPointerCapture(grabEvent.pointerId)) {
8906
8956
  target.releasePointerCapture(grabEvent.pointerId);
8907
8957
  }
@@ -11064,7 +11114,10 @@ const roundForConstraints = (value) => {
11064
11114
 
11065
11115
  /**
11066
11116
  * Detects the drop target based on what element is actually under the mouse cursor.
11067
- * Uses document.elementsFromPoint() to respect visual stacking order naturally.
11117
+ * Uses document.elementsFromPoint() to respect visual stacking order naturally,
11118
+ * and falls back on the rectangles alone when the hit test cannot answer — which
11119
+ * is not only "over nothing": during a view transition the browser hands back the
11120
+ * root for every point of the page (see findTargetByGeometry).
11068
11121
  *
11069
11122
  * @param {Object} gestureInfo - Gesture information
11070
11123
  * @param {Element[]} targetElements - Array of potential drop target elements
@@ -11203,8 +11256,20 @@ const getDropTargetInfo = (
11203
11256
  }
11204
11257
  }
11205
11258
  if (!targetElement) {
11206
- targetElement = intersectingTargets[0];
11207
- intersectingIndex = 0;
11259
+ // Nothing in the stack answered. The point may be over no target at all —
11260
+ // and it may also be over one the hit test cannot see: a view transition
11261
+ // covers the page with its pictures, and from then on every point of the
11262
+ // document reads as the root, whatever is really under it. Taking the first
11263
+ // of the overlapped targets then means taking the first one in DOM ORDER,
11264
+ // which has nothing to do with where the hand is: a piece carried onto the
11265
+ // place next door comes back down on the place it left, and the hint says so
11266
+ // by lighting up the wrong one.
11267
+ //
11268
+ // Geometry is what is left, and it is the reading the eye makes anyway: the
11269
+ // place the middle of the carried thing is IN, or — the middle being over a
11270
+ // gap — the one it covers most of.
11271
+ targetElement = findTargetByGeometry(intersectingTargets, dragElementRect);
11272
+ intersectingIndex = intersectingTargets.indexOf(targetElement);
11208
11273
  }
11209
11274
  targetIndex = targetElements.indexOf(targetElement);
11210
11275
 
@@ -11257,6 +11322,41 @@ const getDropTargetInfo = (
11257
11322
  return result;
11258
11323
  };
11259
11324
 
11325
+ /**
11326
+ * Which of the overlapped targets the carried thing is on, said with rectangles
11327
+ * alone: the one holding its centre, or the one it covers the most of. Used when
11328
+ * the hit test cannot answer (see its caller).
11329
+ */
11330
+ const findTargetByGeometry = (targetElements, dragElementRect) => {
11331
+ const dragCenterX = dragElementRect.left + dragElementRect.width / 2;
11332
+ const dragCenterY = dragElementRect.top + dragElementRect.height / 2;
11333
+ let bestElement = null;
11334
+ let bestOverlapArea = -1;
11335
+ for (const targetElement of targetElements) {
11336
+ const targetRect = targetElement.getBoundingClientRect();
11337
+ if (
11338
+ dragCenterX >= targetRect.left &&
11339
+ dragCenterX <= targetRect.right &&
11340
+ dragCenterY >= targetRect.top &&
11341
+ dragCenterY <= targetRect.bottom
11342
+ ) {
11343
+ return targetElement;
11344
+ }
11345
+ const overlapWidth =
11346
+ Math.min(targetRect.right, dragElementRect.right) -
11347
+ Math.max(targetRect.left, dragElementRect.left);
11348
+ const overlapHeight =
11349
+ Math.min(targetRect.bottom, dragElementRect.bottom) -
11350
+ Math.max(targetRect.top, dragElementRect.top);
11351
+ const overlapArea = overlapWidth * overlapHeight;
11352
+ if (overlapArea > bestOverlapArea) {
11353
+ bestOverlapArea = overlapArea;
11354
+ bestElement = targetElement;
11355
+ }
11356
+ }
11357
+ return bestElement;
11358
+ };
11359
+
11260
11360
  const rectangleAreIntersecting = (r1, r2) => {
11261
11361
  return !(
11262
11362
  r2.left > r1.right ||
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsenv/dom",
3
- "version": "0.17.21",
3
+ "version": "0.17.22",
4
4
  "type": "module",
5
5
  "description": "DOM utilities for writing frontend code",
6
6
  "repository": {