svelte-dnd-action 0.9.70 → 0.9.72

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/dist/index.js CHANGED
@@ -1413,6 +1413,8 @@
1413
1413
  var touchDragHoldTimer;
1414
1414
  var touchHoldElapsed = false;
1415
1415
  var useCursorForDetectionActive = false;
1416
+ var pendingDragOwner;
1417
+ var watchedDropZones = new Set();
1416
1418
 
1417
1419
  // a map from type to a set of drop-zones
1418
1420
  var typeToDropZones$1 = new Map();
@@ -1435,9 +1437,10 @@
1435
1437
  }
1436
1438
  }
1437
1439
  function unregisterDropZone$1(dropZoneEl, type) {
1438
- typeToDropZones$1.get(type)["delete"](dropZoneEl);
1440
+ var dropZones = typeToDropZones$1.get(type);
1441
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
1439
1442
  decrementActiveDropZoneCount();
1440
- if (typeToDropZones$1.get(type).size === 0) {
1443
+ if (dropZones.size === 0) {
1441
1444
  typeToDropZones$1["delete"](type);
1442
1445
  }
1443
1446
  }
@@ -1448,7 +1451,10 @@
1448
1451
  return "watching dragged element";
1449
1452
  });
1450
1453
  var dropZones = typeToDropZones$1.get(draggedElType);
1451
- var _iterator = _createForOfIteratorHelper(dropZones),
1454
+ if (!dropZones || dropZones.size === 0) return;
1455
+ if (watchedDropZones.size > 0) unWatchDraggedElement();
1456
+ watchedDropZones = new Set(dropZones);
1457
+ var _iterator = _createForOfIteratorHelper(watchedDropZones),
1452
1458
  _step;
1453
1459
  try {
1454
1460
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -1465,11 +1471,11 @@
1465
1471
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1466
1472
 
1467
1473
  // it is important that we don't have an interval that is faster than the flip duration because it can cause elements to jump bach and forth
1468
- var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(dropZones.keys()).map(function (dz) {
1474
+ var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(watchedDropZones).map(function (dz) {
1469
1475
  return dzToConfig$1.get(dz).dropAnimationDurationMs;
1470
1476
  })));
1471
1477
  var 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.
1472
- multiScroller = createMultiScroller(dropZones, function () {
1478
+ multiScroller = createMultiScroller(watchedDropZones, function () {
1473
1479
  return currentMousePosition;
1474
1480
  });
1475
1481
  // Returns reference point in document coordinates - either cursor position or element center
@@ -1481,14 +1487,13 @@
1481
1487
  } : function () {
1482
1488
  return findCenterOfElement(draggedEl);
1483
1489
  };
1484
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1490
+ observe(draggedEl, watchedDropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1485
1491
  }
1486
1492
  function unWatchDraggedElement() {
1487
1493
  printDebug(function () {
1488
1494
  return "unwatching dragged element";
1489
1495
  });
1490
- var dropZones = typeToDropZones$1.get(draggedElType);
1491
- var _iterator2 = _createForOfIteratorHelper(dropZones),
1496
+ var _iterator2 = _createForOfIteratorHelper(watchedDropZones),
1492
1497
  _step2;
1493
1498
  try {
1494
1499
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
@@ -1502,6 +1507,7 @@
1502
1507
  } finally {
1503
1508
  _iterator2.f();
1504
1509
  }
1510
+ watchedDropZones = new Set();
1505
1511
  window.removeEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1506
1512
  // ensuring multiScroller is not already destroyed before destroying
1507
1513
  if (multiScroller) {
@@ -1687,6 +1693,9 @@
1687
1693
  return item[SHADOW_ITEM_MARKER_PROPERTY_NAME] ? draggedElData : item;
1688
1694
  });
1689
1695
  function finalizeWithinZone() {
1696
+ // The original-element rAF loop may have re-armed observation during the settle animation.
1697
+ // Cancel again before dispatching events that can update or destroy drop zones.
1698
+ unWatchDraggedElement();
1690
1699
  unlockOriginDzMinDimensions();
1691
1700
  dispatchFinalizeEvent(shadowElDropZone, items, {
1692
1701
  trigger: isDraggedOutsideOfAnyDz ? TRIGGERS.DROPPED_OUTSIDE_OF_ANY : TRIGGERS.DROPPED_INTO_ZONE,
@@ -1730,11 +1739,13 @@
1730
1739
  window.setTimeout(callback, dropAnimationDurationMs);
1731
1740
  }
1732
1741
  function scheduleDZForRemovalAfterDrop(dz, destroy) {
1733
- scheduledForRemovalAfterDrop.push({
1742
+ var scheduledRemoval = {
1734
1743
  dz: dz,
1735
1744
  destroy: destroy
1736
- });
1745
+ };
1746
+ scheduledForRemovalAfterDrop.push(scheduledRemoval);
1737
1747
  window.requestAnimationFrame(function () {
1748
+ if (!scheduledForRemovalAfterDrop.includes(scheduledRemoval)) return;
1738
1749
  hideElement(dz);
1739
1750
  document.body.appendChild(dz);
1740
1751
  });
@@ -1768,6 +1779,7 @@
1768
1779
  touchDragHoldTimer = undefined;
1769
1780
  touchHoldElapsed = false;
1770
1781
  useCursorForDetectionActive = false;
1782
+ pendingDragOwner = undefined;
1771
1783
  if (scheduledForRemovalAfterDrop.length) {
1772
1784
  printDebug(function () {
1773
1785
  return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
@@ -1783,6 +1795,7 @@
1783
1795
  }
1784
1796
  function dndzone$2(node, options) {
1785
1797
  var initialized = false;
1798
+ var destroyed = false;
1786
1799
  var config = {
1787
1800
  items: undefined,
1788
1801
  type: undefined,
@@ -1830,11 +1843,17 @@
1830
1843
  touchHoldElapsed = false;
1831
1844
  }
1832
1845
  }
1833
- function handleFalseAlarm(e) {
1846
+ function cancelPendingDrag() {
1847
+ if (pendingDragOwner !== node) return;
1834
1848
  removeMaybeListeners();
1849
+ pendingDragOwner = undefined;
1835
1850
  originalDragTarget = undefined;
1836
1851
  dragStartMousePosition = undefined;
1837
1852
  currentMousePosition = undefined;
1853
+ }
1854
+ function handleFalseAlarm(e) {
1855
+ if (pendingDragOwner !== node) return;
1856
+ cancelPendingDrag();
1838
1857
 
1839
1858
  // dragging initiated by touch events prevents onclick from initially firing
1840
1859
  if (e.type === "touchend") {
@@ -1847,6 +1866,7 @@
1847
1866
  }
1848
1867
  }
1849
1868
  function handleMouseMoveMaybeDragStart(e) {
1869
+ if (destroyed || pendingDragOwner !== node) return;
1850
1870
  var isTouch = !!e.touches;
1851
1871
  var c = isTouch ? e.touches[0] : e;
1852
1872
  // If touch drag delay is configured and not elapsed yet, allow scrolling until either
@@ -1875,7 +1895,6 @@
1875
1895
  y: c.clientY
1876
1896
  };
1877
1897
  if (Math.abs(currentMousePosition.x - dragStartMousePosition.x) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX || Math.abs(currentMousePosition.y - dragStartMousePosition.y) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX) {
1878
- removeMaybeListeners();
1879
1898
  handleDragStart();
1880
1899
  }
1881
1900
  }
@@ -1894,9 +1913,9 @@
1894
1913
  });
1895
1914
  return;
1896
1915
  }
1897
- if (isWorkingOnPreviousDrag) {
1916
+ if (destroyed || isWorkingOnPreviousDrag || pendingDragOwner) {
1898
1917
  printDebug(function () {
1899
- return "cannot start a new drag before finalizing previous one";
1918
+ return "cannot start a new drag before finalizing the current drag or pending gesture";
1900
1919
  });
1901
1920
  return;
1902
1921
  }
@@ -1906,6 +1925,7 @@
1906
1925
  e.preventDefault();
1907
1926
  }
1908
1927
  e.stopPropagation();
1928
+ pendingDragOwner = node;
1909
1929
  var c = isTouchStart ? e.touches[0] : e;
1910
1930
  dragStartMousePosition = {
1911
1931
  x: c.clientX,
@@ -1914,18 +1934,25 @@
1914
1934
  currentMousePosition = _objectSpread2({}, dragStartMousePosition);
1915
1935
  originalDragTarget = e.currentTarget;
1916
1936
  if (useDelay) {
1937
+ var pendingTarget = originalDragTarget;
1917
1938
  touchHoldElapsed = false;
1918
1939
  touchDragHoldTimer = window.setTimeout(function () {
1919
- // If the finger is still down and no false-alarm happened
1920
- if (!originalDragTarget) return;
1940
+ // If this action still owns the same pending gesture, transition it to a drag.
1941
+ if (destroyed || pendingDragOwner !== node || originalDragTarget !== pendingTarget) return;
1921
1942
  touchHoldElapsed = true;
1922
- removeMaybeListeners();
1923
1943
  handleDragStart();
1924
1944
  }, config.delayTouchStartMs);
1925
1945
  }
1926
1946
  addMaybeListeners();
1927
1947
  }
1928
1948
  function handleDragStart() {
1949
+ var _originalDragTarget;
1950
+ if (destroyed || pendingDragOwner !== node || isWorkingOnPreviousDrag || !((_originalDragTarget = originalDragTarget) !== null && _originalDragTarget !== void 0 && _originalDragTarget.parentElement)) {
1951
+ cancelPendingDrag();
1952
+ return;
1953
+ }
1954
+ removeMaybeListeners();
1955
+ pendingDragOwner = undefined;
1929
1956
  printDebug(function () {
1930
1957
  return ["drag start config: ".concat(toString(config)), originalDragTarget];
1931
1958
  });
@@ -1953,11 +1980,10 @@
1953
1980
  originDropZoneRoot.appendChild(draggedEl);
1954
1981
  // 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
1955
1982
  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;
1983
+ // Once the drop starts finalizing, handleDrop has stopped observation and this loop
1984
+ // must not re-arm it during the settle animation. After cleanup, draggedEl is cleared
1985
+ // as an additional guard against the original element leaving the DOM much later.
1986
+ if (!draggedEl || finalizingPreviousDrag) return;
1961
1987
  if (!originalDragTarget.parentElement) {
1962
1988
  originalDragTarget.setAttribute(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, true);
1963
1989
  originDropZoneRoot.appendChild(originalDragTarget);
@@ -2125,11 +2151,29 @@
2125
2151
  configure(newOptions);
2126
2152
  },
2127
2153
  destroy: function destroy() {
2154
+ if (destroyed) return;
2155
+ destroyed = true;
2156
+ cancelPendingDrag();
2157
+ var _iterator3 = _createForOfIteratorHelper(node.children),
2158
+ _step3;
2159
+ try {
2160
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2161
+ var draggableEl = _step3.value;
2162
+ draggableEl.removeEventListener("mousedown", elToMouseDownListener.get(draggableEl));
2163
+ draggableEl.removeEventListener("touchstart", elToMouseDownListener.get(draggableEl));
2164
+ }
2165
+ } catch (err) {
2166
+ _iterator3.e(err);
2167
+ } finally {
2168
+ _iterator3.f();
2169
+ }
2128
2170
  function destroyDz() {
2129
2171
  printDebug(function () {
2130
2172
  return "pointer dndzone will destroy";
2131
2173
  });
2132
- unregisterDropZone$1(node, dzToConfig$1.get(node).type);
2174
+ var registeredConfig = dzToConfig$1.get(node);
2175
+ if (!registeredConfig) return;
2176
+ unregisterDropZone$1(node, registeredConfig.type);
2133
2177
  dzToConfig$1["delete"](node);
2134
2178
  }
2135
2179
  if (isWorkingOnPreviousDrag && !node.closest("[".concat(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, "]"))) {
@@ -2286,12 +2330,13 @@
2286
2330
  printDebug(function () {
2287
2331
  return "unregistering drop-zone";
2288
2332
  });
2289
- if (focusedDz === dropZoneEl) {
2333
+ if (isDragging && focusedDz === dropZoneEl) {
2290
2334
  handleDrop();
2291
2335
  }
2292
- typeToDropZones.get(type)["delete"](dropZoneEl);
2336
+ var dropZones = typeToDropZones.get(type);
2337
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
2293
2338
  decrementActiveDropZoneCount();
2294
- if (typeToDropZones.get(type).size === 0) {
2339
+ if (dropZones.size === 0) {
2295
2340
  typeToDropZones["delete"](type);
2296
2341
  }
2297
2342
  if (typeToDropZones.size === 0) {
@@ -2353,17 +2398,20 @@
2353
2398
  }
2354
2399
  }
2355
2400
  var dzFrom = focusedDz;
2401
+ var movedItemId = focusedItemId;
2402
+ focusedDz = newlyFocusedDz;
2356
2403
  dispatchFinalizeEvent(dzFrom, originItems, {
2357
2404
  trigger: TRIGGERS.DROPPED_INTO_ANOTHER,
2358
- id: focusedItemId,
2359
- source: SOURCES.KEYBOARD
2360
- });
2361
- dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2362
- trigger: TRIGGERS.DROPPED_INTO_ZONE,
2363
- id: focusedItemId,
2405
+ id: movedItemId,
2364
2406
  source: SOURCES.KEYBOARD
2365
2407
  });
2366
- focusedDz = newlyFocusedDz;
2408
+ if (dzToConfig.has(newlyFocusedDz)) {
2409
+ dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2410
+ trigger: TRIGGERS.DROPPED_INTO_ZONE,
2411
+ id: movedItemId,
2412
+ source: SOURCES.KEYBOARD
2413
+ });
2414
+ }
2367
2415
  }
2368
2416
  function triggerAllDzsUpdate() {
2369
2417
  dzToHandles.forEach(function (_ref, dz) {
@@ -2373,27 +2421,23 @@
2373
2421
  }
2374
2422
  function handleDrop() {
2375
2423
  var dispatchConsider = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
2424
+ if (!isDragging || !focusedDz) return;
2376
2425
  printDebug(function () {
2377
2426
  return "drop";
2378
2427
  });
2379
- if (!dzToConfig.get(focusedDz).autoAriaDisabled) {
2428
+ var droppedDz = focusedDz;
2429
+ var droppedConfig = dzToConfig.get(droppedDz);
2430
+ var droppedItemId = focusedItemId;
2431
+ var droppedItemType = draggedItemType;
2432
+ if (!droppedConfig) return;
2433
+ if (!droppedConfig.autoAriaDisabled) {
2380
2434
  alertToScreenReader("Stopped dragging item ".concat(focusedItemLabel));
2381
2435
  }
2382
2436
  if (allDragTargets.has(document.activeElement)) {
2383
2437
  document.activeElement.blur();
2384
2438
  }
2385
- if (dispatchConsider) {
2386
- dispatchConsiderEvent(focusedDz, dzToConfig.get(focusedDz).items, {
2387
- trigger: TRIGGERS.DRAG_STOPPED,
2388
- id: focusedItemId,
2389
- source: SOURCES.KEYBOARD
2390
- });
2391
- }
2392
- styleInactiveDropZones(typeToDropZones.get(draggedItemType), function (dz) {
2393
- return dzToConfig.get(dz).dropTargetStyle;
2394
- }, function (dz) {
2395
- return dzToConfig.get(dz).dropTargetClasses;
2396
- });
2439
+ // Clear global drag state before dispatching. A synchronous handler may destroy the
2440
+ // focused zone, and unregisterDropZone must not recursively enter handleDrop.
2397
2441
  focusedItem = null;
2398
2442
  focusedItemId = null;
2399
2443
  focusedItemLabel = "";
@@ -2401,10 +2445,26 @@
2401
2445
  focusedDz = null;
2402
2446
  focusedDzLabel = "";
2403
2447
  isDragging = false;
2448
+ if (dispatchConsider) {
2449
+ dispatchConsiderEvent(droppedDz, droppedConfig.items, {
2450
+ trigger: TRIGGERS.DRAG_STOPPED,
2451
+ id: droppedItemId,
2452
+ source: SOURCES.KEYBOARD
2453
+ });
2454
+ }
2455
+ var dropZones = typeToDropZones.get(droppedItemType);
2456
+ if (dropZones) {
2457
+ styleInactiveDropZones(dropZones, function (dz) {
2458
+ return dzToConfig.get(dz).dropTargetStyle;
2459
+ }, function (dz) {
2460
+ return dzToConfig.get(dz).dropTargetClasses;
2461
+ });
2462
+ }
2404
2463
  triggerAllDzsUpdate();
2405
2464
  }
2406
2465
  //////
2407
2466
  function dndzone$1(node, options) {
2467
+ var destroyed = false;
2408
2468
  var config = {
2409
2469
  items: undefined,
2410
2470
  type: undefined,
@@ -2629,9 +2689,25 @@
2629
2689
  configure(newOptions);
2630
2690
  },
2631
2691
  destroy: function destroy() {
2692
+ if (destroyed) return;
2693
+ destroyed = true;
2632
2694
  printDebug(function () {
2633
2695
  return "keyboard dndzone will destroy";
2634
2696
  });
2697
+ node.removeEventListener("focus", handleZoneFocus);
2698
+ var _iterator = _createForOfIteratorHelper(node.children),
2699
+ _step;
2700
+ try {
2701
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2702
+ var draggableEl = _step.value;
2703
+ draggableEl.removeEventListener("keydown", elToKeyDownListeners.get(draggableEl));
2704
+ draggableEl.removeEventListener("click", elToFocusListeners.get(draggableEl));
2705
+ }
2706
+ } catch (err) {
2707
+ _iterator.e(err);
2708
+ } finally {
2709
+ _iterator.f();
2710
+ }
2635
2711
  unregisterDropZone(node, config.type);
2636
2712
  dzToConfig["delete"](node);
2637
2713
  dzToHandles["delete"](node);
@@ -2840,6 +2916,8 @@
2840
2916
  updateZone();
2841
2917
  },
2842
2918
  destroy: function destroy() {
2919
+ isItemsDragDisabled.set(true);
2920
+ zone.destroy();
2843
2921
  node.removeEventListener("consider", consider);
2844
2922
  node.removeEventListener("finalize", finalize);
2845
2923
  isItemsDragDisabled.unsubscribe(updateZone);
package/dist/index.mjs CHANGED
@@ -1407,6 +1407,8 @@ var multiScroller;
1407
1407
  var touchDragHoldTimer;
1408
1408
  var touchHoldElapsed = false;
1409
1409
  var useCursorForDetectionActive = false;
1410
+ var pendingDragOwner;
1411
+ var watchedDropZones = new Set();
1410
1412
 
1411
1413
  // a map from type to a set of drop-zones
1412
1414
  var typeToDropZones$1 = new Map();
@@ -1429,9 +1431,10 @@ function registerDropZone$1(dropZoneEl, type) {
1429
1431
  }
1430
1432
  }
1431
1433
  function unregisterDropZone$1(dropZoneEl, type) {
1432
- typeToDropZones$1.get(type)["delete"](dropZoneEl);
1434
+ var dropZones = typeToDropZones$1.get(type);
1435
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
1433
1436
  decrementActiveDropZoneCount();
1434
- if (typeToDropZones$1.get(type).size === 0) {
1437
+ if (dropZones.size === 0) {
1435
1438
  typeToDropZones$1["delete"](type);
1436
1439
  }
1437
1440
  }
@@ -1442,7 +1445,10 @@ function watchDraggedElement() {
1442
1445
  return "watching dragged element";
1443
1446
  });
1444
1447
  var dropZones = typeToDropZones$1.get(draggedElType);
1445
- var _iterator = _createForOfIteratorHelper(dropZones),
1448
+ if (!dropZones || dropZones.size === 0) return;
1449
+ if (watchedDropZones.size > 0) unWatchDraggedElement();
1450
+ watchedDropZones = new Set(dropZones);
1451
+ var _iterator = _createForOfIteratorHelper(watchedDropZones),
1446
1452
  _step;
1447
1453
  try {
1448
1454
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -1459,11 +1465,11 @@ function watchDraggedElement() {
1459
1465
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1460
1466
 
1461
1467
  // it is important that we don't have an interval that is faster than the flip duration because it can cause elements to jump bach and forth
1462
- var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(dropZones.keys()).map(function (dz) {
1468
+ var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(watchedDropZones).map(function (dz) {
1463
1469
  return dzToConfig$1.get(dz).dropAnimationDurationMs;
1464
1470
  })));
1465
1471
  var 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.
1466
- multiScroller = createMultiScroller(dropZones, function () {
1472
+ multiScroller = createMultiScroller(watchedDropZones, function () {
1467
1473
  return currentMousePosition;
1468
1474
  });
1469
1475
  // Returns reference point in document coordinates - either cursor position or element center
@@ -1475,14 +1481,13 @@ function watchDraggedElement() {
1475
1481
  } : function () {
1476
1482
  return findCenterOfElement(draggedEl);
1477
1483
  };
1478
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1484
+ observe(draggedEl, watchedDropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1479
1485
  }
1480
1486
  function unWatchDraggedElement() {
1481
1487
  printDebug(function () {
1482
1488
  return "unwatching dragged element";
1483
1489
  });
1484
- var dropZones = typeToDropZones$1.get(draggedElType);
1485
- var _iterator2 = _createForOfIteratorHelper(dropZones),
1490
+ var _iterator2 = _createForOfIteratorHelper(watchedDropZones),
1486
1491
  _step2;
1487
1492
  try {
1488
1493
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
@@ -1496,6 +1501,7 @@ function unWatchDraggedElement() {
1496
1501
  } finally {
1497
1502
  _iterator2.f();
1498
1503
  }
1504
+ watchedDropZones = new Set();
1499
1505
  window.removeEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1500
1506
  // ensuring multiScroller is not already destroyed before destroying
1501
1507
  if (multiScroller) {
@@ -1681,6 +1687,9 @@ function handleDrop$1() {
1681
1687
  return item[SHADOW_ITEM_MARKER_PROPERTY_NAME] ? draggedElData : item;
1682
1688
  });
1683
1689
  function finalizeWithinZone() {
1690
+ // The original-element rAF loop may have re-armed observation during the settle animation.
1691
+ // Cancel again before dispatching events that can update or destroy drop zones.
1692
+ unWatchDraggedElement();
1684
1693
  unlockOriginDzMinDimensions();
1685
1694
  dispatchFinalizeEvent(shadowElDropZone, items, {
1686
1695
  trigger: isDraggedOutsideOfAnyDz ? TRIGGERS.DROPPED_OUTSIDE_OF_ANY : TRIGGERS.DROPPED_INTO_ZONE,
@@ -1724,11 +1733,13 @@ function animateDraggedToFinalPosition(shadowElIdx, callback) {
1724
1733
  window.setTimeout(callback, dropAnimationDurationMs);
1725
1734
  }
1726
1735
  function scheduleDZForRemovalAfterDrop(dz, destroy) {
1727
- scheduledForRemovalAfterDrop.push({
1736
+ var scheduledRemoval = {
1728
1737
  dz: dz,
1729
1738
  destroy: destroy
1730
- });
1739
+ };
1740
+ scheduledForRemovalAfterDrop.push(scheduledRemoval);
1731
1741
  window.requestAnimationFrame(function () {
1742
+ if (!scheduledForRemovalAfterDrop.includes(scheduledRemoval)) return;
1732
1743
  hideElement(dz);
1733
1744
  document.body.appendChild(dz);
1734
1745
  });
@@ -1762,6 +1773,7 @@ function cleanupPostDrop() {
1762
1773
  touchDragHoldTimer = undefined;
1763
1774
  touchHoldElapsed = false;
1764
1775
  useCursorForDetectionActive = false;
1776
+ pendingDragOwner = undefined;
1765
1777
  if (scheduledForRemovalAfterDrop.length) {
1766
1778
  printDebug(function () {
1767
1779
  return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
@@ -1777,6 +1789,7 @@ function cleanupPostDrop() {
1777
1789
  }
1778
1790
  function dndzone$2(node, options) {
1779
1791
  var initialized = false;
1792
+ var destroyed = false;
1780
1793
  var config = {
1781
1794
  items: undefined,
1782
1795
  type: undefined,
@@ -1824,11 +1837,17 @@ function dndzone$2(node, options) {
1824
1837
  touchHoldElapsed = false;
1825
1838
  }
1826
1839
  }
1827
- function handleFalseAlarm(e) {
1840
+ function cancelPendingDrag() {
1841
+ if (pendingDragOwner !== node) return;
1828
1842
  removeMaybeListeners();
1843
+ pendingDragOwner = undefined;
1829
1844
  originalDragTarget = undefined;
1830
1845
  dragStartMousePosition = undefined;
1831
1846
  currentMousePosition = undefined;
1847
+ }
1848
+ function handleFalseAlarm(e) {
1849
+ if (pendingDragOwner !== node) return;
1850
+ cancelPendingDrag();
1832
1851
 
1833
1852
  // dragging initiated by touch events prevents onclick from initially firing
1834
1853
  if (e.type === "touchend") {
@@ -1841,6 +1860,7 @@ function dndzone$2(node, options) {
1841
1860
  }
1842
1861
  }
1843
1862
  function handleMouseMoveMaybeDragStart(e) {
1863
+ if (destroyed || pendingDragOwner !== node) return;
1844
1864
  var isTouch = !!e.touches;
1845
1865
  var c = isTouch ? e.touches[0] : e;
1846
1866
  // If touch drag delay is configured and not elapsed yet, allow scrolling until either
@@ -1869,7 +1889,6 @@ function dndzone$2(node, options) {
1869
1889
  y: c.clientY
1870
1890
  };
1871
1891
  if (Math.abs(currentMousePosition.x - dragStartMousePosition.x) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX || Math.abs(currentMousePosition.y - dragStartMousePosition.y) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX) {
1872
- removeMaybeListeners();
1873
1892
  handleDragStart();
1874
1893
  }
1875
1894
  }
@@ -1888,9 +1907,9 @@ function dndzone$2(node, options) {
1888
1907
  });
1889
1908
  return;
1890
1909
  }
1891
- if (isWorkingOnPreviousDrag) {
1910
+ if (destroyed || isWorkingOnPreviousDrag || pendingDragOwner) {
1892
1911
  printDebug(function () {
1893
- return "cannot start a new drag before finalizing previous one";
1912
+ return "cannot start a new drag before finalizing the current drag or pending gesture";
1894
1913
  });
1895
1914
  return;
1896
1915
  }
@@ -1900,6 +1919,7 @@ function dndzone$2(node, options) {
1900
1919
  e.preventDefault();
1901
1920
  }
1902
1921
  e.stopPropagation();
1922
+ pendingDragOwner = node;
1903
1923
  var c = isTouchStart ? e.touches[0] : e;
1904
1924
  dragStartMousePosition = {
1905
1925
  x: c.clientX,
@@ -1908,18 +1928,25 @@ function dndzone$2(node, options) {
1908
1928
  currentMousePosition = _objectSpread2({}, dragStartMousePosition);
1909
1929
  originalDragTarget = e.currentTarget;
1910
1930
  if (useDelay) {
1931
+ var pendingTarget = originalDragTarget;
1911
1932
  touchHoldElapsed = false;
1912
1933
  touchDragHoldTimer = window.setTimeout(function () {
1913
- // If the finger is still down and no false-alarm happened
1914
- if (!originalDragTarget) return;
1934
+ // If this action still owns the same pending gesture, transition it to a drag.
1935
+ if (destroyed || pendingDragOwner !== node || originalDragTarget !== pendingTarget) return;
1915
1936
  touchHoldElapsed = true;
1916
- removeMaybeListeners();
1917
1937
  handleDragStart();
1918
1938
  }, config.delayTouchStartMs);
1919
1939
  }
1920
1940
  addMaybeListeners();
1921
1941
  }
1922
1942
  function handleDragStart() {
1943
+ var _originalDragTarget;
1944
+ if (destroyed || pendingDragOwner !== node || isWorkingOnPreviousDrag || !((_originalDragTarget = originalDragTarget) !== null && _originalDragTarget !== void 0 && _originalDragTarget.parentElement)) {
1945
+ cancelPendingDrag();
1946
+ return;
1947
+ }
1948
+ removeMaybeListeners();
1949
+ pendingDragOwner = undefined;
1923
1950
  printDebug(function () {
1924
1951
  return ["drag start config: ".concat(toString(config)), originalDragTarget];
1925
1952
  });
@@ -1947,11 +1974,10 @@ function dndzone$2(node, options) {
1947
1974
  originDropZoneRoot.appendChild(draggedEl);
1948
1975
  // 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
1949
1976
  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;
1977
+ // Once the drop starts finalizing, handleDrop has stopped observation and this loop
1978
+ // must not re-arm it during the settle animation. After cleanup, draggedEl is cleared
1979
+ // as an additional guard against the original element leaving the DOM much later.
1980
+ if (!draggedEl || finalizingPreviousDrag) return;
1955
1981
  if (!originalDragTarget.parentElement) {
1956
1982
  originalDragTarget.setAttribute(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, true);
1957
1983
  originDropZoneRoot.appendChild(originalDragTarget);
@@ -2119,11 +2145,29 @@ function dndzone$2(node, options) {
2119
2145
  configure(newOptions);
2120
2146
  },
2121
2147
  destroy: function destroy() {
2148
+ if (destroyed) return;
2149
+ destroyed = true;
2150
+ cancelPendingDrag();
2151
+ var _iterator3 = _createForOfIteratorHelper(node.children),
2152
+ _step3;
2153
+ try {
2154
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2155
+ var draggableEl = _step3.value;
2156
+ draggableEl.removeEventListener("mousedown", elToMouseDownListener.get(draggableEl));
2157
+ draggableEl.removeEventListener("touchstart", elToMouseDownListener.get(draggableEl));
2158
+ }
2159
+ } catch (err) {
2160
+ _iterator3.e(err);
2161
+ } finally {
2162
+ _iterator3.f();
2163
+ }
2122
2164
  function destroyDz() {
2123
2165
  printDebug(function () {
2124
2166
  return "pointer dndzone will destroy";
2125
2167
  });
2126
- unregisterDropZone$1(node, dzToConfig$1.get(node).type);
2168
+ var registeredConfig = dzToConfig$1.get(node);
2169
+ if (!registeredConfig) return;
2170
+ unregisterDropZone$1(node, registeredConfig.type);
2127
2171
  dzToConfig$1["delete"](node);
2128
2172
  }
2129
2173
  if (isWorkingOnPreviousDrag && !node.closest("[".concat(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, "]"))) {
@@ -2280,12 +2324,13 @@ function unregisterDropZone(dropZoneEl, type) {
2280
2324
  printDebug(function () {
2281
2325
  return "unregistering drop-zone";
2282
2326
  });
2283
- if (focusedDz === dropZoneEl) {
2327
+ if (isDragging && focusedDz === dropZoneEl) {
2284
2328
  handleDrop();
2285
2329
  }
2286
- typeToDropZones.get(type)["delete"](dropZoneEl);
2330
+ var dropZones = typeToDropZones.get(type);
2331
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
2287
2332
  decrementActiveDropZoneCount();
2288
- if (typeToDropZones.get(type).size === 0) {
2333
+ if (dropZones.size === 0) {
2289
2334
  typeToDropZones["delete"](type);
2290
2335
  }
2291
2336
  if (typeToDropZones.size === 0) {
@@ -2347,17 +2392,20 @@ function handleZoneFocus(e) {
2347
2392
  }
2348
2393
  }
2349
2394
  var dzFrom = focusedDz;
2395
+ var movedItemId = focusedItemId;
2396
+ focusedDz = newlyFocusedDz;
2350
2397
  dispatchFinalizeEvent(dzFrom, originItems, {
2351
2398
  trigger: TRIGGERS.DROPPED_INTO_ANOTHER,
2352
- id: focusedItemId,
2353
- source: SOURCES.KEYBOARD
2354
- });
2355
- dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2356
- trigger: TRIGGERS.DROPPED_INTO_ZONE,
2357
- id: focusedItemId,
2399
+ id: movedItemId,
2358
2400
  source: SOURCES.KEYBOARD
2359
2401
  });
2360
- focusedDz = newlyFocusedDz;
2402
+ if (dzToConfig.has(newlyFocusedDz)) {
2403
+ dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2404
+ trigger: TRIGGERS.DROPPED_INTO_ZONE,
2405
+ id: movedItemId,
2406
+ source: SOURCES.KEYBOARD
2407
+ });
2408
+ }
2361
2409
  }
2362
2410
  function triggerAllDzsUpdate() {
2363
2411
  dzToHandles.forEach(function (_ref, dz) {
@@ -2367,27 +2415,23 @@ function triggerAllDzsUpdate() {
2367
2415
  }
2368
2416
  function handleDrop() {
2369
2417
  var dispatchConsider = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
2418
+ if (!isDragging || !focusedDz) return;
2370
2419
  printDebug(function () {
2371
2420
  return "drop";
2372
2421
  });
2373
- if (!dzToConfig.get(focusedDz).autoAriaDisabled) {
2422
+ var droppedDz = focusedDz;
2423
+ var droppedConfig = dzToConfig.get(droppedDz);
2424
+ var droppedItemId = focusedItemId;
2425
+ var droppedItemType = draggedItemType;
2426
+ if (!droppedConfig) return;
2427
+ if (!droppedConfig.autoAriaDisabled) {
2374
2428
  alertToScreenReader("Stopped dragging item ".concat(focusedItemLabel));
2375
2429
  }
2376
2430
  if (allDragTargets.has(document.activeElement)) {
2377
2431
  document.activeElement.blur();
2378
2432
  }
2379
- if (dispatchConsider) {
2380
- dispatchConsiderEvent(focusedDz, dzToConfig.get(focusedDz).items, {
2381
- trigger: TRIGGERS.DRAG_STOPPED,
2382
- id: focusedItemId,
2383
- source: SOURCES.KEYBOARD
2384
- });
2385
- }
2386
- styleInactiveDropZones(typeToDropZones.get(draggedItemType), function (dz) {
2387
- return dzToConfig.get(dz).dropTargetStyle;
2388
- }, function (dz) {
2389
- return dzToConfig.get(dz).dropTargetClasses;
2390
- });
2433
+ // Clear global drag state before dispatching. A synchronous handler may destroy the
2434
+ // focused zone, and unregisterDropZone must not recursively enter handleDrop.
2391
2435
  focusedItem = null;
2392
2436
  focusedItemId = null;
2393
2437
  focusedItemLabel = "";
@@ -2395,10 +2439,26 @@ function handleDrop() {
2395
2439
  focusedDz = null;
2396
2440
  focusedDzLabel = "";
2397
2441
  isDragging = false;
2442
+ if (dispatchConsider) {
2443
+ dispatchConsiderEvent(droppedDz, droppedConfig.items, {
2444
+ trigger: TRIGGERS.DRAG_STOPPED,
2445
+ id: droppedItemId,
2446
+ source: SOURCES.KEYBOARD
2447
+ });
2448
+ }
2449
+ var dropZones = typeToDropZones.get(droppedItemType);
2450
+ if (dropZones) {
2451
+ styleInactiveDropZones(dropZones, function (dz) {
2452
+ return dzToConfig.get(dz).dropTargetStyle;
2453
+ }, function (dz) {
2454
+ return dzToConfig.get(dz).dropTargetClasses;
2455
+ });
2456
+ }
2398
2457
  triggerAllDzsUpdate();
2399
2458
  }
2400
2459
  //////
2401
2460
  function dndzone$1(node, options) {
2461
+ var destroyed = false;
2402
2462
  var config = {
2403
2463
  items: undefined,
2404
2464
  type: undefined,
@@ -2623,9 +2683,25 @@ function dndzone$1(node, options) {
2623
2683
  configure(newOptions);
2624
2684
  },
2625
2685
  destroy: function destroy() {
2686
+ if (destroyed) return;
2687
+ destroyed = true;
2626
2688
  printDebug(function () {
2627
2689
  return "keyboard dndzone will destroy";
2628
2690
  });
2691
+ node.removeEventListener("focus", handleZoneFocus);
2692
+ var _iterator = _createForOfIteratorHelper(node.children),
2693
+ _step;
2694
+ try {
2695
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2696
+ var draggableEl = _step.value;
2697
+ draggableEl.removeEventListener("keydown", elToKeyDownListeners.get(draggableEl));
2698
+ draggableEl.removeEventListener("click", elToFocusListeners.get(draggableEl));
2699
+ }
2700
+ } catch (err) {
2701
+ _iterator.e(err);
2702
+ } finally {
2703
+ _iterator.f();
2704
+ }
2629
2705
  unregisterDropZone(node, config.type);
2630
2706
  dzToConfig["delete"](node);
2631
2707
  dzToHandles["delete"](node);
@@ -2834,6 +2910,8 @@ function dragHandleZone(node, options) {
2834
2910
  updateZone();
2835
2911
  },
2836
2912
  destroy: function destroy() {
2913
+ isItemsDragDisabled.set(true);
2914
+ zone.destroy();
2837
2915
  node.removeEventListener("consider", consider);
2838
2916
  node.removeEventListener("finalize", finalize);
2839
2917
  isItemsDragDisabled.unsubscribe(updateZone);
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.70",
4
+ "version": "0.9.72",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
@@ -50,12 +50,13 @@ function registerDropZone(dropZoneEl, type) {
50
50
  }
51
51
  function unregisterDropZone(dropZoneEl, type) {
52
52
  printDebug(() => "unregistering drop-zone");
53
- if (focusedDz === dropZoneEl) {
53
+ if (isDragging && focusedDz === dropZoneEl) {
54
54
  handleDrop();
55
55
  }
56
- typeToDropZones.get(type).delete(dropZoneEl);
56
+ const dropZones = typeToDropZones.get(type);
57
+ if (!dropZones || !dropZones.delete(dropZoneEl)) return;
57
58
  decrementActiveDropZoneCount();
58
- if (typeToDropZones.get(type).size === 0) {
59
+ if (dropZones.size === 0) {
59
60
  typeToDropZones.delete(type);
60
61
  }
61
62
  if (typeToDropZones.size === 0) {
@@ -112,9 +113,12 @@ function handleZoneFocus(e) {
112
113
  }
113
114
  }
114
115
  const dzFrom = focusedDz;
115
- dispatchFinalizeEvent(dzFrom, originItems, {trigger: TRIGGERS.DROPPED_INTO_ANOTHER, id: focusedItemId, source: SOURCES.KEYBOARD});
116
- dispatchFinalizeEvent(newlyFocusedDz, targetItems, {trigger: TRIGGERS.DROPPED_INTO_ZONE, id: focusedItemId, source: SOURCES.KEYBOARD});
116
+ const movedItemId = focusedItemId;
117
117
  focusedDz = newlyFocusedDz;
118
+ dispatchFinalizeEvent(dzFrom, originItems, {trigger: TRIGGERS.DROPPED_INTO_ANOTHER, id: movedItemId, source: SOURCES.KEYBOARD});
119
+ if (dzToConfig.has(newlyFocusedDz)) {
120
+ dispatchFinalizeEvent(newlyFocusedDz, targetItems, {trigger: TRIGGERS.DROPPED_INTO_ZONE, id: movedItemId, source: SOURCES.KEYBOARD});
121
+ }
118
122
  }
119
123
 
120
124
  function triggerAllDzsUpdate() {
@@ -122,25 +126,22 @@ function triggerAllDzsUpdate() {
122
126
  }
123
127
 
124
128
  function handleDrop(dispatchConsider = true) {
129
+ if (!isDragging || !focusedDz) return;
125
130
  printDebug(() => "drop");
126
- if (!dzToConfig.get(focusedDz).autoAriaDisabled) {
131
+ const droppedDz = focusedDz;
132
+ const droppedConfig = dzToConfig.get(droppedDz);
133
+ const droppedItemId = focusedItemId;
134
+ const droppedItemType = draggedItemType;
135
+ if (!droppedConfig) return;
136
+
137
+ if (!droppedConfig.autoAriaDisabled) {
127
138
  alertToScreenReader(`Stopped dragging item ${focusedItemLabel}`);
128
139
  }
129
140
  if (allDragTargets.has(document.activeElement)) {
130
141
  document.activeElement.blur();
131
142
  }
132
- if (dispatchConsider) {
133
- dispatchConsiderEvent(focusedDz, dzToConfig.get(focusedDz).items, {
134
- trigger: TRIGGERS.DRAG_STOPPED,
135
- id: focusedItemId,
136
- source: SOURCES.KEYBOARD
137
- });
138
- }
139
- styleInactiveDropZones(
140
- typeToDropZones.get(draggedItemType),
141
- dz => dzToConfig.get(dz).dropTargetStyle,
142
- dz => dzToConfig.get(dz).dropTargetClasses
143
- );
143
+ // Clear global drag state before dispatching. A synchronous handler may destroy the
144
+ // focused zone, and unregisterDropZone must not recursively enter handleDrop.
144
145
  focusedItem = null;
145
146
  focusedItemId = null;
146
147
  focusedItemLabel = "";
@@ -148,10 +149,27 @@ function handleDrop(dispatchConsider = true) {
148
149
  focusedDz = null;
149
150
  focusedDzLabel = "";
150
151
  isDragging = false;
152
+
153
+ if (dispatchConsider) {
154
+ dispatchConsiderEvent(droppedDz, droppedConfig.items, {
155
+ trigger: TRIGGERS.DRAG_STOPPED,
156
+ id: droppedItemId,
157
+ source: SOURCES.KEYBOARD
158
+ });
159
+ }
160
+ const dropZones = typeToDropZones.get(droppedItemType);
161
+ if (dropZones) {
162
+ styleInactiveDropZones(
163
+ dropZones,
164
+ dz => dzToConfig.get(dz).dropTargetStyle,
165
+ dz => dzToConfig.get(dz).dropTargetClasses
166
+ );
167
+ }
151
168
  triggerAllDzsUpdate();
152
169
  }
153
170
  //////
154
171
  export function dndzone(node, options) {
172
+ let destroyed = false;
155
173
  const config = {
156
174
  items: undefined,
157
175
  type: undefined,
@@ -344,7 +362,14 @@ export function dndzone(node, options) {
344
362
  configure(newOptions);
345
363
  },
346
364
  destroy: () => {
365
+ if (destroyed) return;
366
+ destroyed = true;
347
367
  printDebug(() => "keyboard dndzone will destroy");
368
+ node.removeEventListener("focus", handleZoneFocus);
369
+ for (const draggableEl of node.children) {
370
+ draggableEl.removeEventListener("keydown", elToKeyDownListeners.get(draggableEl));
371
+ draggableEl.removeEventListener("click", elToFocusListeners.get(draggableEl));
372
+ }
348
373
  unregisterDropZone(node, config.type);
349
374
  dzToConfig.delete(node);
350
375
  dzToHandles.delete(node);
@@ -64,6 +64,8 @@ let multiScroller;
64
64
  let touchDragHoldTimer;
65
65
  let touchHoldElapsed = false;
66
66
  let useCursorForDetectionActive = false;
67
+ let pendingDragOwner;
68
+ let watchedDropZones = new Set();
67
69
 
68
70
  // a map from type to a set of drop-zones
69
71
  const typeToDropZones = new Map();
@@ -84,9 +86,10 @@ function registerDropZone(dropZoneEl, type) {
84
86
  }
85
87
  }
86
88
  function unregisterDropZone(dropZoneEl, type) {
87
- typeToDropZones.get(type).delete(dropZoneEl);
89
+ const dropZones = typeToDropZones.get(type);
90
+ if (!dropZones || !dropZones.delete(dropZoneEl)) return;
88
91
  decrementActiveDropZoneCount();
89
- if (typeToDropZones.get(type).size === 0) {
92
+ if (dropZones.size === 0) {
90
93
  typeToDropZones.delete(type);
91
94
  }
92
95
  }
@@ -95,8 +98,11 @@ function unregisterDropZone(dropZoneEl, type) {
95
98
  function watchDraggedElement() {
96
99
  printDebug(() => "watching dragged element");
97
100
  const dropZones = typeToDropZones.get(draggedElType);
101
+ if (!dropZones || dropZones.size === 0) return;
102
+ if (watchedDropZones.size > 0) unWatchDraggedElement();
103
+ watchedDropZones = new Set(dropZones);
98
104
 
99
- for (const dz of dropZones) {
105
+ for (const dz of watchedDropZones) {
100
106
  dz.addEventListener(DRAGGED_ENTERED_EVENT_NAME, handleDraggedEntered);
101
107
  dz.addEventListener(DRAGGED_LEFT_EVENT_NAME, handleDraggedLeft);
102
108
  dz.addEventListener(DRAGGED_OVER_INDEX_EVENT_NAME, handleDraggedIsOverIndex);
@@ -104,9 +110,9 @@ function watchDraggedElement() {
104
110
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop);
105
111
 
106
112
  // it is important that we don't have an interval that is faster than the flip duration because it can cause elements to jump bach and forth
107
- const setIntervalMs = Math.max(...Array.from(dropZones.keys()).map(dz => dzToConfig.get(dz).dropAnimationDurationMs));
113
+ const setIntervalMs = Math.max(...Array.from(watchedDropZones).map(dz => dzToConfig.get(dz).dropAnimationDurationMs));
108
114
  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.
109
- multiScroller = createMultiScroller(dropZones, () => currentMousePosition);
115
+ multiScroller = createMultiScroller(watchedDropZones, () => currentMousePosition);
110
116
  // Returns reference point in document coordinates - either cursor position or element center
111
117
  const getReferencePoint = useCursorForDetectionActive
112
118
  ? () => ({
@@ -114,16 +120,16 @@ function watchDraggedElement() {
114
120
  y: currentMousePosition.y + window.scrollY
115
121
  })
116
122
  : () => findCenterOfElement(draggedEl);
117
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
123
+ observe(draggedEl, watchedDropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
118
124
  }
119
125
  function unWatchDraggedElement() {
120
126
  printDebug(() => "unwatching dragged element");
121
- const dropZones = typeToDropZones.get(draggedElType);
122
- for (const dz of dropZones) {
127
+ for (const dz of watchedDropZones) {
123
128
  dz.removeEventListener(DRAGGED_ENTERED_EVENT_NAME, handleDraggedEntered);
124
129
  dz.removeEventListener(DRAGGED_LEFT_EVENT_NAME, handleDraggedLeft);
125
130
  dz.removeEventListener(DRAGGED_OVER_INDEX_EVENT_NAME, handleDraggedIsOverIndex);
126
131
  }
132
+ watchedDropZones = new Set();
127
133
  window.removeEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop);
128
134
  // ensuring multiScroller is not already destroyed before destroying
129
135
  if (multiScroller) {
@@ -268,6 +274,9 @@ function handleDrop() {
268
274
 
269
275
  items = items.map(item => (item[SHADOW_ITEM_MARKER_PROPERTY_NAME] ? draggedElData : item));
270
276
  function finalizeWithinZone() {
277
+ // The original-element rAF loop may have re-armed observation during the settle animation.
278
+ // Cancel again before dispatching events that can update or destroy drop zones.
279
+ unWatchDraggedElement();
271
280
  unlockOriginDzMinDimensions();
272
281
  dispatchFinalizeEvent(shadowElDropZone, items, {
273
282
  trigger: isDraggedOutsideOfAnyDz ? TRIGGERS.DROPPED_OUTSIDE_OF_ANY : TRIGGERS.DROPPED_INTO_ZONE,
@@ -312,8 +321,10 @@ function animateDraggedToFinalPosition(shadowElIdx, callback) {
312
321
  }
313
322
 
314
323
  function scheduleDZForRemovalAfterDrop(dz, destroy) {
315
- scheduledForRemovalAfterDrop.push({dz, destroy});
324
+ const scheduledRemoval = {dz, destroy};
325
+ scheduledForRemovalAfterDrop.push(scheduledRemoval);
316
326
  window.requestAnimationFrame(() => {
327
+ if (!scheduledForRemovalAfterDrop.includes(scheduledRemoval)) return;
317
328
  hideElement(dz);
318
329
  document.body.appendChild(dz);
319
330
  });
@@ -348,6 +359,7 @@ function cleanupPostDrop() {
348
359
  touchDragHoldTimer = undefined;
349
360
  touchHoldElapsed = false;
350
361
  useCursorForDetectionActive = false;
362
+ pendingDragOwner = undefined;
351
363
  if (scheduledForRemovalAfterDrop.length) {
352
364
  printDebug(() => ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop]);
353
365
  scheduledForRemovalAfterDrop.forEach(({dz, destroy}) => {
@@ -360,6 +372,7 @@ function cleanupPostDrop() {
360
372
 
361
373
  export function dndzone(node, options) {
362
374
  let initialized = false;
375
+ let destroyed = false;
363
376
  const config = {
364
377
  items: undefined,
365
378
  type: undefined,
@@ -395,11 +408,17 @@ export function dndzone(node, options) {
395
408
  touchHoldElapsed = false;
396
409
  }
397
410
  }
398
- function handleFalseAlarm(e) {
411
+ function cancelPendingDrag() {
412
+ if (pendingDragOwner !== node) return;
399
413
  removeMaybeListeners();
414
+ pendingDragOwner = undefined;
400
415
  originalDragTarget = undefined;
401
416
  dragStartMousePosition = undefined;
402
417
  currentMousePosition = undefined;
418
+ }
419
+ function handleFalseAlarm(e) {
420
+ if (pendingDragOwner !== node) return;
421
+ cancelPendingDrag();
403
422
 
404
423
  // dragging initiated by touch events prevents onclick from initially firing
405
424
  if (e.type === "touchend") {
@@ -413,6 +432,7 @@ export function dndzone(node, options) {
413
432
  }
414
433
 
415
434
  function handleMouseMoveMaybeDragStart(e) {
435
+ if (destroyed || pendingDragOwner !== node) return;
416
436
  const isTouch = !!e.touches;
417
437
  const c = isTouch ? e.touches[0] : e;
418
438
  // If touch drag delay is configured and not elapsed yet, allow scrolling until either
@@ -442,7 +462,6 @@ export function dndzone(node, options) {
442
462
  Math.abs(currentMousePosition.x - dragStartMousePosition.x) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX ||
443
463
  Math.abs(currentMousePosition.y - dragStartMousePosition.y) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX
444
464
  ) {
445
- removeMaybeListeners();
446
465
  handleDragStart();
447
466
  }
448
467
  }
@@ -457,8 +476,8 @@ export function dndzone(node, options) {
457
476
  printDebug(() => `ignoring none left click button: ${e.button}`);
458
477
  return;
459
478
  }
460
- if (isWorkingOnPreviousDrag) {
461
- printDebug(() => "cannot start a new drag before finalizing previous one");
479
+ if (destroyed || isWorkingOnPreviousDrag || pendingDragOwner) {
480
+ printDebug(() => "cannot start a new drag before finalizing the current drag or pending gesture");
462
481
  return;
463
482
  }
464
483
  const isTouchStart = !!e.touches;
@@ -468,6 +487,7 @@ export function dndzone(node, options) {
468
487
  e.preventDefault();
469
488
  }
470
489
  e.stopPropagation();
490
+ pendingDragOwner = node;
471
491
 
472
492
  const c = isTouchStart ? e.touches[0] : e;
473
493
  dragStartMousePosition = {x: c.clientX, y: c.clientY};
@@ -475,12 +495,12 @@ export function dndzone(node, options) {
475
495
  originalDragTarget = e.currentTarget;
476
496
 
477
497
  if (useDelay) {
498
+ const pendingTarget = originalDragTarget;
478
499
  touchHoldElapsed = false;
479
500
  touchDragHoldTimer = window.setTimeout(() => {
480
- // If the finger is still down and no false-alarm happened
481
- if (!originalDragTarget) return;
501
+ // If this action still owns the same pending gesture, transition it to a drag.
502
+ if (destroyed || pendingDragOwner !== node || originalDragTarget !== pendingTarget) return;
482
503
  touchHoldElapsed = true;
483
- removeMaybeListeners();
484
504
  handleDragStart();
485
505
  }, config.delayTouchStartMs);
486
506
  }
@@ -489,6 +509,12 @@ export function dndzone(node, options) {
489
509
  }
490
510
 
491
511
  function handleDragStart() {
512
+ if (destroyed || pendingDragOwner !== node || isWorkingOnPreviousDrag || !originalDragTarget?.parentElement) {
513
+ cancelPendingDrag();
514
+ return;
515
+ }
516
+ removeMaybeListeners();
517
+ pendingDragOwner = undefined;
492
518
  printDebug(() => [`drag start config: ${toString(config)}`, originalDragTarget]);
493
519
  isWorkingOnPreviousDrag = true;
494
520
 
@@ -511,11 +537,10 @@ export function dndzone(node, options) {
511
537
  originDropZoneRoot.appendChild(draggedEl);
512
538
  // 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
513
539
  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;
540
+ // Once the drop starts finalizing, handleDrop has stopped observation and this loop
541
+ // must not re-arm it during the settle animation. After cleanup, draggedEl is cleared
542
+ // as an additional guard against the original element leaving the DOM much later.
543
+ if (!draggedEl || finalizingPreviousDrag) return;
519
544
  if (!originalDragTarget.parentElement) {
520
545
  originalDragTarget.setAttribute(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, true);
521
546
  originDropZoneRoot.appendChild(originalDragTarget);
@@ -668,9 +693,18 @@ export function dndzone(node, options) {
668
693
  configure(newOptions);
669
694
  },
670
695
  destroy: () => {
696
+ if (destroyed) return;
697
+ destroyed = true;
698
+ cancelPendingDrag();
699
+ for (const draggableEl of node.children) {
700
+ draggableEl.removeEventListener("mousedown", elToMouseDownListener.get(draggableEl));
701
+ draggableEl.removeEventListener("touchstart", elToMouseDownListener.get(draggableEl));
702
+ }
671
703
  function destroyDz() {
672
704
  printDebug(() => "pointer dndzone will destroy");
673
- unregisterDropZone(node, dzToConfig.get(node).type);
705
+ const registeredConfig = dzToConfig.get(node);
706
+ if (!registeredConfig) return;
707
+ unregisterDropZone(node, registeredConfig.type);
674
708
  dzToConfig.delete(node);
675
709
  }
676
710
  if (isWorkingOnPreviousDrag && !node.closest(`[${ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE}]`)) {
@@ -77,6 +77,8 @@ export function dragHandleZone(node, options) {
77
77
  updateZone();
78
78
  },
79
79
  destroy: () => {
80
+ isItemsDragDisabled.set(true);
81
+ zone.destroy();
80
82
  node.removeEventListener("consider", consider);
81
83
  node.removeEventListener("finalize", finalize);
82
84
  isItemsDragDisabled.unsubscribe(updateZone);