svelte-dnd-action 0.9.68 → 0.9.70

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.
package/README.md CHANGED
@@ -120,6 +120,7 @@ An options-object with the following attributes:
120
120
  | `transformDraggedElement` | Function | No | `() => {}` | A function that is invoked when the draggable element enters the dnd-zone or hover overs a new index in the current dnd-zone. <br />Signature:<br />function(element, data, index) {}<br />**element**: The dragged element. <br />**data**: The data of the item from the items array.<br />**index**: The index the dragged element will become in the new dnd-zone.<br /><br />This allows you to override properties on the dragged element, such as innerHTML to change how it displays. If what you are after is altering styles, do it to the children, not to the dragged element itself |
121
121
  | `autoAriaDisabled` | Boolean | No | `false` | Setting it to true will disable all the automatically added aria attributes and aria alerts (for example when the user starts/ stops dragging using the keyboard).<br /> **Use it only if you intend to implement your own custom instructions, roles and alerts.** In such a case, you might find the exported function `alertToScreenReader(string)` useful. |
122
122
  | `centreDraggedOnCursor` | Boolean | No | `false` | Setting it to true will cause elements from this dnd-zone to position their center on the cursor on drag start, effectively turning the cursor to the focal point that triggers all the dnd events (ex: entering another zone). Useful for dnd-zones with large items that can be dragged over small items. |
123
+ | `useCursorForDetection` | Boolean | No | `false` | Setting it to true will use the cursor position instead of the dragged element's center for drop zone detection. This improves accuracy when dragging large elements over small drop targets. Unlike `centreDraggedOnCursor`, this option does not reposition the dragged element. |
123
124
  | `dropAnimationDisabled` | Boolean | No | `false` | Setting it to true will disable the animation of the dropped element to its final place. |
124
125
  | `delayTouchStart` | Boolean \| Number | No | `false` | Prevents accidental drags on touch devices that should have been scrolls.
125
126
  • `true` – enable with sensible default (80 ms).
package/dist/index.d.ts CHANGED
@@ -64,6 +64,10 @@ export interface Options<T extends Item = Item> {
64
64
  * false/undefined => drag starts instantly (legacy behaviour)
65
65
  */
66
66
  delayTouchStart?: boolean | number;
67
+ /**
68
+ * Use cursor position instead of dragged element center for drop zone detection
69
+ */
70
+ useCursorForDetection?: boolean;
67
71
  }
68
72
 
69
73
  export interface DndZoneAttributes<T> {
package/dist/index.js CHANGED
@@ -480,25 +480,14 @@
480
480
  }
481
481
 
482
482
  /**
483
- * @param {HTMLElement} elA
484
- * @param {HTMLElement} elB
485
- * @return {boolean}
486
- */
487
- function isCenterOfAInsideB(elA, elB) {
488
- var centerOfA = findCenterOfElement(elA);
489
- var rectOfB = getAbsoluteRectNoTransforms(elB);
490
- return isPointInsideRect(centerOfA, rectOfB);
491
- }
492
-
493
- /**
494
- * @param {HTMLElement|ChildNode} elA
495
- * @param {HTMLElement|ChildNode} elB
483
+ * Calculate distance from a point to the center of an element
484
+ * @param {{x: number, y: number}} point
485
+ * @param {HTMLElement|ChildNode} el
496
486
  * @return {number}
497
487
  */
498
- function calcDistanceBetweenCenters(elA, elB) {
499
- var centerOfA = findCenterOfElement(elA);
500
- var centerOfB = findCenterOfElement(elB);
501
- return calcDistance(centerOfA, centerOfB);
488
+ function calcDistanceFromPointToCenter(point, el) {
489
+ var centerOfEl = findCenterOfElement(el);
490
+ return calcDistance(point, centerOfEl);
502
491
  }
503
492
 
504
493
  /**
@@ -637,12 +626,13 @@
637
626
  */
638
627
  /**
639
628
  * Find the index for the dragged element in the list it is dragged over
640
- * @param {HTMLElement} floatingAboveEl
629
+ * @param {{x: number, y: number}} referencePoint - The reference point for detection (cursor position or element center)
641
630
  * @param {HTMLElement} collectionBelowEl
642
631
  * @returns {Index|null} - if the element is over the container the Index object otherwise null
643
632
  */
644
- function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
645
- if (!isCenterOfAInsideB(floatingAboveEl, collectionBelowEl)) {
633
+ function findWouldBeIndex(referencePoint, collectionBelowEl) {
634
+ var collectionRect = getAbsoluteRectNoTransforms(collectionBelowEl);
635
+ if (!isPointInsideRect(referencePoint, collectionRect)) {
646
636
  return null;
647
637
  }
648
638
  var children = collectionBelowEl.children;
@@ -658,10 +648,11 @@
658
648
  // the search could be more efficient but keeping it simple for now
659
649
  // a possible improvement: pass in the lastIndex it was found in and check there first, then expand from there
660
650
  for (var i = 0; i < children.length; i++) {
661
- if (isCenterOfAInsideB(floatingAboveEl, children[i])) {
651
+ var childRect = getAbsoluteRectNoTransforms(children[i]);
652
+ if (isPointInsideRect(referencePoint, childRect)) {
662
653
  var cachedShadowRect = dzToShadowIndexToRect.has(collectionBelowEl) && dzToShadowIndexToRect.get(collectionBelowEl).get(i);
663
654
  if (cachedShadowRect) {
664
- if (!isPointInsideRect(findCenterOfElement(floatingAboveEl), cachedShadowRect)) {
655
+ if (!isPointInsideRect(referencePoint, cachedShadowRect)) {
665
656
  return {
666
657
  index: shadowElIndex,
667
658
  isProximityBased: false
@@ -680,7 +671,7 @@
680
671
  var indexOfMin = undefined;
681
672
  // we are checking all of them because we don't know whether we are dealing with a horizontal or vertical container and where the floating element entered from
682
673
  for (var _i = 0; _i < children.length; _i++) {
683
- var distance = calcDistanceBetweenCenters(floatingAboveEl, children[_i]);
674
+ var distance = calcDistanceFromPointToCenter(referencePoint, children[_i]);
684
675
  if (distance < minDistanceSoFar) {
685
676
  minDistanceSoFar = distance;
686
677
  indexOfMin = _i;
@@ -705,7 +696,7 @@
705
696
  phantom.style.visibility = "hidden";
706
697
  phantom.style.pointerEvents = "none";
707
698
  collectionBelowEl.appendChild(phantom);
708
- var phantomDistance = calcDistanceBetweenCenters(floatingAboveEl, phantom);
699
+ var phantomDistance = calcDistanceFromPointToCenter(referencePoint, phantom);
709
700
  if (phantomDistance < minDistanceSoFar) {
710
701
  indexOfMin = originalLen; // index of phantom slot in original list
711
702
  }
@@ -791,10 +782,12 @@
791
782
  * @param {HTMLElement} draggedEl
792
783
  * @param {number} [intervalMs = INTERVAL_MS]
793
784
  * @param {MultiScroller} multiScroller
785
+ * @param {function(): {x: number, y: number}} getReferencePoint - Function that returns the reference point for detection (cursor or element center)
794
786
  */
795
787
  function observe(draggedEl, dropZones) {
796
788
  var intervalMs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INTERVAL_MS;
797
789
  var multiScroller = arguments.length > 3 ? arguments[3] : undefined;
790
+ var getReferencePoint = arguments.length > 4 ? arguments[4] : undefined;
798
791
  // initialization
799
792
  var lastDropZoneFound;
800
793
  var lastIndexFound;
@@ -809,10 +802,10 @@
809
802
  * The main function in this module. Tracks where everything is/ should be a take the actions
810
803
  */
811
804
  function andNow() {
812
- var currentCenterOfDragged = findCenterOfElement(draggedEl);
805
+ var referencePoint = getReferencePoint();
813
806
  var scrolled = multiScroller.multiScrollIfNeeded();
814
807
  // we only want to make a new decision after the element was moved a bit to prevent flickering
815
- if (!scrolled && lastCentrePositionOfDragged && Math.abs(lastCentrePositionOfDragged.x - currentCenterOfDragged.x) < TOLERANCE_PX && Math.abs(lastCentrePositionOfDragged.y - currentCenterOfDragged.y) < TOLERANCE_PX) {
808
+ if (!scrolled && lastCentrePositionOfDragged && Math.abs(lastCentrePositionOfDragged.x - referencePoint.x) < TOLERANCE_PX && Math.abs(lastCentrePositionOfDragged.y - referencePoint.y) < TOLERANCE_PX) {
816
809
  next = window.setTimeout(andNow, intervalMs);
817
810
  return;
818
811
  }
@@ -823,7 +816,7 @@
823
816
  dispatchDraggedLeftDocument(draggedEl);
824
817
  return;
825
818
  }
826
- lastCentrePositionOfDragged = currentCenterOfDragged;
819
+ lastCentrePositionOfDragged = referencePoint;
827
820
  // this is a simple algorithm, potential improvement: first look at lastDropZoneFound
828
821
  var isDraggedInADropZone = false;
829
822
  var _iterator = _createForOfIteratorHelper(dropZonesFromDeepToShallow),
@@ -832,7 +825,7 @@
832
825
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
833
826
  var dz = _step.value;
834
827
  if (scrolled) resetIndexesCache();
835
- var indexObj = findWouldBeIndex(draggedEl, dz);
828
+ var indexObj = findWouldBeIndex(referencePoint, dz);
836
829
  if (indexObj === null) {
837
830
  // it is not inside
838
831
  continue;
@@ -1419,6 +1412,7 @@
1419
1412
  var multiScroller;
1420
1413
  var touchDragHoldTimer;
1421
1414
  var touchHoldElapsed = false;
1415
+ var useCursorForDetectionActive = false;
1422
1416
 
1423
1417
  // a map from type to a set of drop-zones
1424
1418
  var typeToDropZones$1 = new Map();
@@ -1478,7 +1472,16 @@
1478
1472
  multiScroller = createMultiScroller(dropZones, function () {
1479
1473
  return currentMousePosition;
1480
1474
  });
1481
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller);
1475
+ // Returns reference point in document coordinates - either cursor position or element center
1476
+ var getReferencePoint = useCursorForDetectionActive ? function () {
1477
+ return {
1478
+ x: currentMousePosition.x + window.scrollX,
1479
+ y: currentMousePosition.y + window.scrollY
1480
+ };
1481
+ } : function () {
1482
+ return findCenterOfElement(draggedEl);
1483
+ };
1484
+ observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1482
1485
  }
1483
1486
  function unWatchDraggedElement() {
1484
1487
  printDebug(function () {
@@ -1764,6 +1767,7 @@
1764
1767
  }
1765
1768
  touchDragHoldTimer = undefined;
1766
1769
  touchHoldElapsed = false;
1770
+ useCursorForDetectionActive = false;
1767
1771
  if (scheduledForRemovalAfterDrop.length) {
1768
1772
  printDebug(function () {
1769
1773
  return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
@@ -1790,6 +1794,7 @@
1790
1794
  dropTargetClasses: [],
1791
1795
  transformDraggedElement: function transformDraggedElement() {},
1792
1796
  centreDraggedOnCursor: false,
1797
+ useCursorForDetection: false,
1793
1798
  dropAnimationDisabled: false,
1794
1799
  delayTouchStartMs: 0
1795
1800
  };
@@ -1935,17 +1940,24 @@
1935
1940
  var originDropZoneRoot = rootNode.body || rootNode;
1936
1941
  var originalItems = config.items,
1937
1942
  type = config.type,
1938
- centreDraggedOnCursor = config.centreDraggedOnCursor;
1943
+ centreDraggedOnCursor = config.centreDraggedOnCursor,
1944
+ useCursorForDetection = config.useCursorForDetection;
1939
1945
  var items = _toConsumableArray(originalItems);
1940
1946
  draggedElData = items[currentIdx];
1941
1947
  draggedElType = type;
1942
1948
  shadowElData = createShadowElData(draggedElData);
1949
+ useCursorForDetectionActive = useCursorForDetection;
1943
1950
 
1944
1951
  // creating the draggable element
1945
1952
  draggedEl = createDraggedElementFrom(originalDragTarget, centreDraggedOnCursor && currentMousePosition);
1946
1953
  originDropZoneRoot.appendChild(draggedEl);
1947
1954
  // We will keep the original dom node in the dom because touch events keep firing on it, we want to re-add it after the framework removes it
1948
1955
  function keepOriginalElementInDom() {
1956
+ // if the drag was already finalized (cleanupPostDrop cleared draggedEl), this rAF loop
1957
+ // must not re-arm the watcher: the original element might leave the DOM long after the
1958
+ // drop (ex: its component unmounts), which would call watchDraggedElement() with an
1959
+ // undefined draggedEl and crash inside observe() (getBoundingClientRect of undefined)
1960
+ if (!draggedEl) return;
1949
1961
  if (!originalDragTarget.parentElement) {
1950
1962
  originalDragTarget.setAttribute(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, true);
1951
1963
  originDropZoneRoot.appendChild(originalDragTarget);
@@ -2014,6 +2026,8 @@
2014
2026
  transformDraggedElement = _ref2$transformDragge === void 0 ? function () {} : _ref2$transformDragge,
2015
2027
  _ref2$centreDraggedOn = _ref2.centreDraggedOnCursor,
2016
2028
  centreDraggedOnCursor = _ref2$centreDraggedOn === void 0 ? false : _ref2$centreDraggedOn,
2029
+ _ref2$useCursorForDet = _ref2.useCursorForDetection,
2030
+ useCursorForDetection = _ref2$useCursorForDet === void 0 ? false : _ref2$useCursorForDet,
2017
2031
  _ref2$dropAnimationDi = _ref2.dropAnimationDisabled,
2018
2032
  dropAnimationDisabled = _ref2$dropAnimationDi === void 0 ? false : _ref2$dropAnimationDi,
2019
2033
  _ref2$delayTouchStart = _ref2.delayTouchStart,
@@ -2035,6 +2049,7 @@
2035
2049
  config.morphDisabled = morphDisabled;
2036
2050
  config.transformDraggedElement = transformDraggedElement;
2037
2051
  config.centreDraggedOnCursor = centreDraggedOnCursor;
2052
+ config.useCursorForDetection = useCursorForDetection;
2038
2053
  config.dropAnimationDisabled = dropAnimationDisabled;
2039
2054
 
2040
2055
  // realtime update for dropTargetStyle
@@ -2626,7 +2641,7 @@
2626
2641
  return handles;
2627
2642
  }
2628
2643
 
2629
- var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "delayTouchStart", "dropAnimationDisabled"];
2644
+ var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "useCursorForDetection", "delayTouchStart", "dropAnimationDisabled"];
2630
2645
 
2631
2646
  /**
2632
2647
  * A custom action to turn any container to a dnd zone and all of its direct children to draggables
@@ -2700,6 +2715,7 @@
2700
2715
  options.transformDraggedElement;
2701
2716
  options.autoAriaDisabled;
2702
2717
  options.centreDraggedOnCursor;
2718
+ options.useCursorForDetection;
2703
2719
  var delayTouchStart = options.delayTouchStart;
2704
2720
  options.dropAnimationDisabled;
2705
2721
  var rest = _objectWithoutProperties(options, _excluded);
package/dist/index.mjs CHANGED
@@ -474,25 +474,14 @@ function findCenterOfElement(el) {
474
474
  }
475
475
 
476
476
  /**
477
- * @param {HTMLElement} elA
478
- * @param {HTMLElement} elB
479
- * @return {boolean}
480
- */
481
- function isCenterOfAInsideB(elA, elB) {
482
- var centerOfA = findCenterOfElement(elA);
483
- var rectOfB = getAbsoluteRectNoTransforms(elB);
484
- return isPointInsideRect(centerOfA, rectOfB);
485
- }
486
-
487
- /**
488
- * @param {HTMLElement|ChildNode} elA
489
- * @param {HTMLElement|ChildNode} elB
477
+ * Calculate distance from a point to the center of an element
478
+ * @param {{x: number, y: number}} point
479
+ * @param {HTMLElement|ChildNode} el
490
480
  * @return {number}
491
481
  */
492
- function calcDistanceBetweenCenters(elA, elB) {
493
- var centerOfA = findCenterOfElement(elA);
494
- var centerOfB = findCenterOfElement(elB);
495
- return calcDistance(centerOfA, centerOfB);
482
+ function calcDistanceFromPointToCenter(point, el) {
483
+ var centerOfEl = findCenterOfElement(el);
484
+ return calcDistance(point, centerOfEl);
496
485
  }
497
486
 
498
487
  /**
@@ -631,12 +620,13 @@ function cacheShadowRect(dz) {
631
620
  */
632
621
  /**
633
622
  * Find the index for the dragged element in the list it is dragged over
634
- * @param {HTMLElement} floatingAboveEl
623
+ * @param {{x: number, y: number}} referencePoint - The reference point for detection (cursor position or element center)
635
624
  * @param {HTMLElement} collectionBelowEl
636
625
  * @returns {Index|null} - if the element is over the container the Index object otherwise null
637
626
  */
638
- function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
639
- if (!isCenterOfAInsideB(floatingAboveEl, collectionBelowEl)) {
627
+ function findWouldBeIndex(referencePoint, collectionBelowEl) {
628
+ var collectionRect = getAbsoluteRectNoTransforms(collectionBelowEl);
629
+ if (!isPointInsideRect(referencePoint, collectionRect)) {
640
630
  return null;
641
631
  }
642
632
  var children = collectionBelowEl.children;
@@ -652,10 +642,11 @@ function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
652
642
  // the search could be more efficient but keeping it simple for now
653
643
  // a possible improvement: pass in the lastIndex it was found in and check there first, then expand from there
654
644
  for (var i = 0; i < children.length; i++) {
655
- if (isCenterOfAInsideB(floatingAboveEl, children[i])) {
645
+ var childRect = getAbsoluteRectNoTransforms(children[i]);
646
+ if (isPointInsideRect(referencePoint, childRect)) {
656
647
  var cachedShadowRect = dzToShadowIndexToRect.has(collectionBelowEl) && dzToShadowIndexToRect.get(collectionBelowEl).get(i);
657
648
  if (cachedShadowRect) {
658
- if (!isPointInsideRect(findCenterOfElement(floatingAboveEl), cachedShadowRect)) {
649
+ if (!isPointInsideRect(referencePoint, cachedShadowRect)) {
659
650
  return {
660
651
  index: shadowElIndex,
661
652
  isProximityBased: false
@@ -674,7 +665,7 @@ function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
674
665
  var indexOfMin = undefined;
675
666
  // we are checking all of them because we don't know whether we are dealing with a horizontal or vertical container and where the floating element entered from
676
667
  for (var _i = 0; _i < children.length; _i++) {
677
- var distance = calcDistanceBetweenCenters(floatingAboveEl, children[_i]);
668
+ var distance = calcDistanceFromPointToCenter(referencePoint, children[_i]);
678
669
  if (distance < minDistanceSoFar) {
679
670
  minDistanceSoFar = distance;
680
671
  indexOfMin = _i;
@@ -699,7 +690,7 @@ function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
699
690
  phantom.style.visibility = "hidden";
700
691
  phantom.style.pointerEvents = "none";
701
692
  collectionBelowEl.appendChild(phantom);
702
- var phantomDistance = calcDistanceBetweenCenters(floatingAboveEl, phantom);
693
+ var phantomDistance = calcDistanceFromPointToCenter(referencePoint, phantom);
703
694
  if (phantomDistance < minDistanceSoFar) {
704
695
  indexOfMin = originalLen; // index of phantom slot in original list
705
696
  }
@@ -785,10 +776,12 @@ var next;
785
776
  * @param {HTMLElement} draggedEl
786
777
  * @param {number} [intervalMs = INTERVAL_MS]
787
778
  * @param {MultiScroller} multiScroller
779
+ * @param {function(): {x: number, y: number}} getReferencePoint - Function that returns the reference point for detection (cursor or element center)
788
780
  */
789
781
  function observe(draggedEl, dropZones) {
790
782
  var intervalMs = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : INTERVAL_MS;
791
783
  var multiScroller = arguments.length > 3 ? arguments[3] : undefined;
784
+ var getReferencePoint = arguments.length > 4 ? arguments[4] : undefined;
792
785
  // initialization
793
786
  var lastDropZoneFound;
794
787
  var lastIndexFound;
@@ -803,10 +796,10 @@ function observe(draggedEl, dropZones) {
803
796
  * The main function in this module. Tracks where everything is/ should be a take the actions
804
797
  */
805
798
  function andNow() {
806
- var currentCenterOfDragged = findCenterOfElement(draggedEl);
799
+ var referencePoint = getReferencePoint();
807
800
  var scrolled = multiScroller.multiScrollIfNeeded();
808
801
  // we only want to make a new decision after the element was moved a bit to prevent flickering
809
- if (!scrolled && lastCentrePositionOfDragged && Math.abs(lastCentrePositionOfDragged.x - currentCenterOfDragged.x) < TOLERANCE_PX && Math.abs(lastCentrePositionOfDragged.y - currentCenterOfDragged.y) < TOLERANCE_PX) {
802
+ if (!scrolled && lastCentrePositionOfDragged && Math.abs(lastCentrePositionOfDragged.x - referencePoint.x) < TOLERANCE_PX && Math.abs(lastCentrePositionOfDragged.y - referencePoint.y) < TOLERANCE_PX) {
810
803
  next = window.setTimeout(andNow, intervalMs);
811
804
  return;
812
805
  }
@@ -817,7 +810,7 @@ function observe(draggedEl, dropZones) {
817
810
  dispatchDraggedLeftDocument(draggedEl);
818
811
  return;
819
812
  }
820
- lastCentrePositionOfDragged = currentCenterOfDragged;
813
+ lastCentrePositionOfDragged = referencePoint;
821
814
  // this is a simple algorithm, potential improvement: first look at lastDropZoneFound
822
815
  var isDraggedInADropZone = false;
823
816
  var _iterator = _createForOfIteratorHelper(dropZonesFromDeepToShallow),
@@ -826,7 +819,7 @@ function observe(draggedEl, dropZones) {
826
819
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
827
820
  var dz = _step.value;
828
821
  if (scrolled) resetIndexesCache();
829
- var indexObj = findWouldBeIndex(draggedEl, dz);
822
+ var indexObj = findWouldBeIndex(referencePoint, dz);
830
823
  if (indexObj === null) {
831
824
  // it is not inside
832
825
  continue;
@@ -1413,6 +1406,7 @@ var scheduledForRemovalAfterDrop = [];
1413
1406
  var multiScroller;
1414
1407
  var touchDragHoldTimer;
1415
1408
  var touchHoldElapsed = false;
1409
+ var useCursorForDetectionActive = false;
1416
1410
 
1417
1411
  // a map from type to a set of drop-zones
1418
1412
  var typeToDropZones$1 = new Map();
@@ -1472,7 +1466,16 @@ function watchDraggedElement() {
1472
1466
  multiScroller = createMultiScroller(dropZones, function () {
1473
1467
  return currentMousePosition;
1474
1468
  });
1475
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller);
1469
+ // Returns reference point in document coordinates - either cursor position or element center
1470
+ var getReferencePoint = useCursorForDetectionActive ? function () {
1471
+ return {
1472
+ x: currentMousePosition.x + window.scrollX,
1473
+ y: currentMousePosition.y + window.scrollY
1474
+ };
1475
+ } : function () {
1476
+ return findCenterOfElement(draggedEl);
1477
+ };
1478
+ observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1476
1479
  }
1477
1480
  function unWatchDraggedElement() {
1478
1481
  printDebug(function () {
@@ -1758,6 +1761,7 @@ function cleanupPostDrop() {
1758
1761
  }
1759
1762
  touchDragHoldTimer = undefined;
1760
1763
  touchHoldElapsed = false;
1764
+ useCursorForDetectionActive = false;
1761
1765
  if (scheduledForRemovalAfterDrop.length) {
1762
1766
  printDebug(function () {
1763
1767
  return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
@@ -1784,6 +1788,7 @@ function dndzone$2(node, options) {
1784
1788
  dropTargetClasses: [],
1785
1789
  transformDraggedElement: function transformDraggedElement() {},
1786
1790
  centreDraggedOnCursor: false,
1791
+ useCursorForDetection: false,
1787
1792
  dropAnimationDisabled: false,
1788
1793
  delayTouchStartMs: 0
1789
1794
  };
@@ -1929,17 +1934,24 @@ function dndzone$2(node, options) {
1929
1934
  var originDropZoneRoot = rootNode.body || rootNode;
1930
1935
  var originalItems = config.items,
1931
1936
  type = config.type,
1932
- centreDraggedOnCursor = config.centreDraggedOnCursor;
1937
+ centreDraggedOnCursor = config.centreDraggedOnCursor,
1938
+ useCursorForDetection = config.useCursorForDetection;
1933
1939
  var items = _toConsumableArray(originalItems);
1934
1940
  draggedElData = items[currentIdx];
1935
1941
  draggedElType = type;
1936
1942
  shadowElData = createShadowElData(draggedElData);
1943
+ useCursorForDetectionActive = useCursorForDetection;
1937
1944
 
1938
1945
  // creating the draggable element
1939
1946
  draggedEl = createDraggedElementFrom(originalDragTarget, centreDraggedOnCursor && currentMousePosition);
1940
1947
  originDropZoneRoot.appendChild(draggedEl);
1941
1948
  // We will keep the original dom node in the dom because touch events keep firing on it, we want to re-add it after the framework removes it
1942
1949
  function keepOriginalElementInDom() {
1950
+ // if the drag was already finalized (cleanupPostDrop cleared draggedEl), this rAF loop
1951
+ // must not re-arm the watcher: the original element might leave the DOM long after the
1952
+ // drop (ex: its component unmounts), which would call watchDraggedElement() with an
1953
+ // undefined draggedEl and crash inside observe() (getBoundingClientRect of undefined)
1954
+ if (!draggedEl) return;
1943
1955
  if (!originalDragTarget.parentElement) {
1944
1956
  originalDragTarget.setAttribute(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, true);
1945
1957
  originDropZoneRoot.appendChild(originalDragTarget);
@@ -2008,6 +2020,8 @@ function dndzone$2(node, options) {
2008
2020
  transformDraggedElement = _ref2$transformDragge === void 0 ? function () {} : _ref2$transformDragge,
2009
2021
  _ref2$centreDraggedOn = _ref2.centreDraggedOnCursor,
2010
2022
  centreDraggedOnCursor = _ref2$centreDraggedOn === void 0 ? false : _ref2$centreDraggedOn,
2023
+ _ref2$useCursorForDet = _ref2.useCursorForDetection,
2024
+ useCursorForDetection = _ref2$useCursorForDet === void 0 ? false : _ref2$useCursorForDet,
2011
2025
  _ref2$dropAnimationDi = _ref2.dropAnimationDisabled,
2012
2026
  dropAnimationDisabled = _ref2$dropAnimationDi === void 0 ? false : _ref2$dropAnimationDi,
2013
2027
  _ref2$delayTouchStart = _ref2.delayTouchStart,
@@ -2029,6 +2043,7 @@ function dndzone$2(node, options) {
2029
2043
  config.morphDisabled = morphDisabled;
2030
2044
  config.transformDraggedElement = transformDraggedElement;
2031
2045
  config.centreDraggedOnCursor = centreDraggedOnCursor;
2046
+ config.useCursorForDetection = useCursorForDetection;
2032
2047
  config.dropAnimationDisabled = dropAnimationDisabled;
2033
2048
 
2034
2049
  // realtime update for dropTargetStyle
@@ -2620,7 +2635,7 @@ function dndzone$1(node, options) {
2620
2635
  return handles;
2621
2636
  }
2622
2637
 
2623
- var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "delayTouchStart", "dropAnimationDisabled"];
2638
+ var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "useCursorForDetection", "delayTouchStart", "dropAnimationDisabled"];
2624
2639
 
2625
2640
  /**
2626
2641
  * A custom action to turn any container to a dnd zone and all of its direct children to draggables
@@ -2694,6 +2709,7 @@ function validateOptions(options) {
2694
2709
  options.transformDraggedElement;
2695
2710
  options.autoAriaDisabled;
2696
2711
  options.centreDraggedOnCursor;
2712
+ options.useCursorForDetection;
2697
2713
  var delayTouchStart = options.delayTouchStart;
2698
2714
  options.dropAnimationDisabled;
2699
2715
  var rest = _objectWithoutProperties(options, _excluded);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "svelte-dnd-action",
3
3
  "description": "*An awesome drag and drop library for Svelte 3 and 4 (not using the browser's built-in dnd, thanks god): Rich animations, nested containers, touch support and more *",
4
- "version": "0.9.68",
4
+ "version": "0.9.70",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
package/src/action.js CHANGED
@@ -77,6 +77,7 @@ function validateOptions(options) {
77
77
  transformDraggedElement,
78
78
  autoAriaDisabled,
79
79
  centreDraggedOnCursor,
80
+ useCursorForDetection,
80
81
  delayTouchStart,
81
82
  dropAnimationDisabled,
82
83
  ...rest
@@ -128,25 +128,14 @@ export function findCenterOfElement(el) {
128
128
  }
129
129
 
130
130
  /**
131
- * @param {HTMLElement} elA
132
- * @param {HTMLElement} elB
133
- * @return {boolean}
134
- */
135
- export function isCenterOfAInsideB(elA, elB) {
136
- const centerOfA = findCenterOfElement(elA);
137
- const rectOfB = getAbsoluteRectNoTransforms(elB);
138
- return isPointInsideRect(centerOfA, rectOfB);
139
- }
140
-
141
- /**
142
- * @param {HTMLElement|ChildNode} elA
143
- * @param {HTMLElement|ChildNode} elB
131
+ * Calculate distance from a point to the center of an element
132
+ * @param {{x: number, y: number}} point
133
+ * @param {HTMLElement|ChildNode} el
144
134
  * @return {number}
145
135
  */
146
- export function calcDistanceBetweenCenters(elA, elB) {
147
- const centerOfA = findCenterOfElement(elA);
148
- const centerOfB = findCenterOfElement(elB);
149
- return calcDistance(centerOfA, centerOfB);
136
+ export function calcDistanceFromPointToCenter(point, el) {
137
+ const centerOfEl = findCenterOfElement(el);
138
+ return calcDistance(point, centerOfEl);
150
139
  }
151
140
 
152
141
  /**
@@ -1,4 +1,4 @@
1
- import {isCenterOfAInsideB, calcDistanceBetweenCenters, getAbsoluteRectNoTransforms, isPointInsideRect, findCenterOfElement} from "./intersection";
1
+ import {getAbsoluteRectNoTransforms, isPointInsideRect, calcDistanceFromPointToCenter} from "./intersection";
2
2
  import {printDebug, SHADOW_ELEMENT_ATTRIBUTE_NAME} from "../constants";
3
3
 
4
4
  let dzToShadowIndexToRect;
@@ -37,14 +37,16 @@ function cacheShadowRect(dz) {
37
37
  */
38
38
  /**
39
39
  * Find the index for the dragged element in the list it is dragged over
40
- * @param {HTMLElement} floatingAboveEl
40
+ * @param {{x: number, y: number}} referencePoint - The reference point for detection (cursor position or element center)
41
41
  * @param {HTMLElement} collectionBelowEl
42
42
  * @returns {Index|null} - if the element is over the container the Index object otherwise null
43
43
  */
44
- export function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
45
- if (!isCenterOfAInsideB(floatingAboveEl, collectionBelowEl)) {
44
+ export function findWouldBeIndex(referencePoint, collectionBelowEl) {
45
+ const collectionRect = getAbsoluteRectNoTransforms(collectionBelowEl);
46
+ if (!isPointInsideRect(referencePoint, collectionRect)) {
46
47
  return null;
47
48
  }
49
+
48
50
  const children = collectionBelowEl.children;
49
51
  // the container is empty, floating element should be the first
50
52
  if (children.length === 0) {
@@ -55,10 +57,11 @@ export function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
55
57
  // the search could be more efficient but keeping it simple for now
56
58
  // a possible improvement: pass in the lastIndex it was found in and check there first, then expand from there
57
59
  for (let i = 0; i < children.length; i++) {
58
- if (isCenterOfAInsideB(floatingAboveEl, children[i])) {
60
+ const childRect = getAbsoluteRectNoTransforms(children[i]);
61
+ if (isPointInsideRect(referencePoint, childRect)) {
59
62
  const cachedShadowRect = dzToShadowIndexToRect.has(collectionBelowEl) && dzToShadowIndexToRect.get(collectionBelowEl).get(i);
60
63
  if (cachedShadowRect) {
61
- if (!isPointInsideRect(findCenterOfElement(floatingAboveEl), cachedShadowRect)) {
64
+ if (!isPointInsideRect(referencePoint, cachedShadowRect)) {
62
65
  return {index: shadowElIndex, isProximityBased: false};
63
66
  }
64
67
  }
@@ -71,7 +74,7 @@ export function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
71
74
  let indexOfMin = undefined;
72
75
  // we are checking all of them because we don't know whether we are dealing with a horizontal or vertical container and where the floating element entered from
73
76
  for (let i = 0; i < children.length; i++) {
74
- const distance = calcDistanceBetweenCenters(floatingAboveEl, children[i]);
77
+ const distance = calcDistanceFromPointToCenter(referencePoint, children[i]);
75
78
  if (distance < minDistanceSoFar) {
76
79
  minDistanceSoFar = distance;
77
80
  indexOfMin = i;
@@ -97,7 +100,7 @@ export function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
97
100
  phantom.style.pointerEvents = "none";
98
101
  collectionBelowEl.appendChild(phantom);
99
102
 
100
- const phantomDistance = calcDistanceBetweenCenters(floatingAboveEl, phantom);
103
+ const phantomDistance = calcDistanceFromPointToCenter(referencePoint, phantom);
101
104
  if (phantomDistance < minDistanceSoFar) {
102
105
  indexOfMin = originalLen; // index of phantom slot in original list
103
106
  }
@@ -1,5 +1,5 @@
1
1
  import {findWouldBeIndex, resetIndexesCache} from "./listUtil";
2
- import {findCenterOfElement, isElementOffDocument} from "./intersection";
2
+ import {isElementOffDocument} from "./intersection";
3
3
  import {
4
4
  dispatchDraggedElementEnteredContainer,
5
5
  dispatchDraggedElementLeftContainerForAnother,
@@ -20,8 +20,9 @@ let next;
20
20
  * @param {HTMLElement} draggedEl
21
21
  * @param {number} [intervalMs = INTERVAL_MS]
22
22
  * @param {MultiScroller} multiScroller
23
+ * @param {function(): {x: number, y: number}} getReferencePoint - Function that returns the reference point for detection (cursor or element center)
23
24
  */
24
- export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS, multiScroller) {
25
+ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS, multiScroller, getReferencePoint) {
25
26
  // initialization
26
27
  let lastDropZoneFound;
27
28
  let lastIndexFound;
@@ -34,14 +35,14 @@ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS, multiScr
34
35
  * The main function in this module. Tracks where everything is/ should be a take the actions
35
36
  */
36
37
  function andNow() {
37
- const currentCenterOfDragged = findCenterOfElement(draggedEl);
38
+ const referencePoint = getReferencePoint();
38
39
  const scrolled = multiScroller.multiScrollIfNeeded();
39
40
  // we only want to make a new decision after the element was moved a bit to prevent flickering
40
41
  if (
41
42
  !scrolled &&
42
43
  lastCentrePositionOfDragged &&
43
- Math.abs(lastCentrePositionOfDragged.x - currentCenterOfDragged.x) < TOLERANCE_PX &&
44
- Math.abs(lastCentrePositionOfDragged.y - currentCenterOfDragged.y) < TOLERANCE_PX
44
+ Math.abs(lastCentrePositionOfDragged.x - referencePoint.x) < TOLERANCE_PX &&
45
+ Math.abs(lastCentrePositionOfDragged.y - referencePoint.y) < TOLERANCE_PX
45
46
  ) {
46
47
  next = window.setTimeout(andNow, intervalMs);
47
48
  return;
@@ -52,12 +53,12 @@ export function observe(draggedEl, dropZones, intervalMs = INTERVAL_MS, multiScr
52
53
  return;
53
54
  }
54
55
 
55
- lastCentrePositionOfDragged = currentCenterOfDragged;
56
+ lastCentrePositionOfDragged = referencePoint;
56
57
  // this is a simple algorithm, potential improvement: first look at lastDropZoneFound
57
58
  let isDraggedInADropZone = false;
58
59
  for (const dz of dropZonesFromDeepToShallow) {
59
60
  if (scrolled) resetIndexesCache();
60
- const indexObj = findWouldBeIndex(draggedEl, dz);
61
+ const indexObj = findWouldBeIndex(referencePoint, dz);
61
62
  if (indexObj === null) {
62
63
  // it is not inside
63
64
  continue;
@@ -33,7 +33,7 @@ import {
33
33
  DRAGGED_OVER_INDEX_EVENT_NAME
34
34
  } from "./helpers/dispatcher";
35
35
  import {areArraysShallowEqualSameOrder, areObjectsShallowEqual, toString} from "./helpers/util";
36
- import {getBoundingRectNoTransforms} from "./helpers/intersection";
36
+ import {getBoundingRectNoTransforms, findCenterOfElement} from "./helpers/intersection";
37
37
 
38
38
  const DEFAULT_DROP_ZONE_TYPE = "--any--";
39
39
  const MIN_OBSERVATION_INTERVAL_MS = 100;
@@ -63,6 +63,7 @@ let scheduledForRemovalAfterDrop = [];
63
63
  let multiScroller;
64
64
  let touchDragHoldTimer;
65
65
  let touchHoldElapsed = false;
66
+ let useCursorForDetectionActive = false;
66
67
 
67
68
  // a map from type to a set of drop-zones
68
69
  const typeToDropZones = new Map();
@@ -106,7 +107,14 @@ function watchDraggedElement() {
106
107
  const setIntervalMs = Math.max(...Array.from(dropZones.keys()).map(dz => dzToConfig.get(dz).dropAnimationDurationMs));
107
108
  const observationIntervalMs = setIntervalMs === 0 ? DISABLED_OBSERVATION_INTERVAL_MS : Math.max(setIntervalMs, MIN_OBSERVATION_INTERVAL_MS); // if setIntervalMs is 0 it goes to 20, otherwise it is max between it and min observation.
108
109
  multiScroller = createMultiScroller(dropZones, () => currentMousePosition);
109
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller);
110
+ // Returns reference point in document coordinates - either cursor position or element center
111
+ const getReferencePoint = useCursorForDetectionActive
112
+ ? () => ({
113
+ x: currentMousePosition.x + window.scrollX,
114
+ y: currentMousePosition.y + window.scrollY
115
+ })
116
+ : () => findCenterOfElement(draggedEl);
117
+ observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
110
118
  }
111
119
  function unWatchDraggedElement() {
112
120
  printDebug(() => "unwatching dragged element");
@@ -339,6 +347,7 @@ function cleanupPostDrop() {
339
347
  }
340
348
  touchDragHoldTimer = undefined;
341
349
  touchHoldElapsed = false;
350
+ useCursorForDetectionActive = false;
342
351
  if (scheduledForRemovalAfterDrop.length) {
343
352
  printDebug(() => ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop]);
344
353
  scheduledForRemovalAfterDrop.forEach(({dz, destroy}) => {
@@ -362,6 +371,7 @@ export function dndzone(node, options) {
362
371
  dropTargetClasses: [],
363
372
  transformDraggedElement: () => {},
364
373
  centreDraggedOnCursor: false,
374
+ useCursorForDetection: false,
365
375
  dropAnimationDisabled: false,
366
376
  delayTouchStartMs: 0
367
377
  };
@@ -489,17 +499,23 @@ export function dndzone(node, options) {
489
499
  /** @type {ShadowRoot | HTMLDocument | Element } */
490
500
  const rootNode = originDropZone.closest("dialog") || originDropZone.closest("[popover]") || originDropZone.getRootNode();
491
501
  const originDropZoneRoot = rootNode.body || rootNode;
492
- const {items: originalItems, type, centreDraggedOnCursor} = config;
502
+ const {items: originalItems, type, centreDraggedOnCursor, useCursorForDetection} = config;
493
503
  const items = [...originalItems];
494
504
  draggedElData = items[currentIdx];
495
505
  draggedElType = type;
496
506
  shadowElData = createShadowElData(draggedElData);
507
+ useCursorForDetectionActive = useCursorForDetection;
497
508
 
498
509
  // creating the draggable element
499
510
  draggedEl = createDraggedElementFrom(originalDragTarget, centreDraggedOnCursor && currentMousePosition);
500
511
  originDropZoneRoot.appendChild(draggedEl);
501
512
  // We will keep the original dom node in the dom because touch events keep firing on it, we want to re-add it after the framework removes it
502
513
  function keepOriginalElementInDom() {
514
+ // if the drag was already finalized (cleanupPostDrop cleared draggedEl), this rAF loop
515
+ // must not re-arm the watcher: the original element might leave the DOM long after the
516
+ // drop (ex: its component unmounts), which would call watchDraggedElement() with an
517
+ // undefined draggedEl and crash inside observe() (getBoundingClientRect of undefined)
518
+ if (!draggedEl) return;
503
519
  if (!originalDragTarget.parentElement) {
504
520
  originalDragTarget.setAttribute(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, true);
505
521
  originDropZoneRoot.appendChild(originalDragTarget);
@@ -546,6 +562,7 @@ export function dndzone(node, options) {
546
562
  dropTargetClasses = [],
547
563
  transformDraggedElement = () => {},
548
564
  centreDraggedOnCursor = false,
565
+ useCursorForDetection = false,
549
566
  dropAnimationDisabled = false,
550
567
  delayTouchStart: delayTouchStartOpt = false
551
568
  }) {
@@ -568,6 +585,7 @@ export function dndzone(node, options) {
568
585
  config.morphDisabled = morphDisabled;
569
586
  config.transformDraggedElement = transformDraggedElement;
570
587
  config.centreDraggedOnCursor = centreDraggedOnCursor;
588
+ config.useCursorForDetection = useCursorForDetection;
571
589
  config.dropAnimationDisabled = dropAnimationDisabled;
572
590
 
573
591
  // realtime update for dropTargetStyle