svelte-dnd-action 0.9.71 → 0.9.73

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
@@ -1186,6 +1186,21 @@
1186
1186
  function trs(property) {
1187
1187
  return "".concat(property, " ").concat(TRANSITION_DURATION_SECONDS, "s ease");
1188
1188
  }
1189
+
1190
+ /**
1191
+ * Adds the size transitions after the dragged element's initial styles have been applied.
1192
+ * This normally happens in a timeout to avoid a browser rendering bug, but a synchronous
1193
+ * first morph can happen before that timeout and needs the transitions immediately.
1194
+ * @param {HTMLElement} draggedEl
1195
+ */
1196
+ function ensureMorphSizeTransitions(draggedEl) {
1197
+ var transitionProperties = draggedEl.style.transitionProperty.split(",").map(function (property) {
1198
+ return property.trim();
1199
+ });
1200
+ if (transitionProperties.includes("all")) return;
1201
+ if (!transitionProperties.includes("width")) draggedEl.style.transition += ", ".concat(trs("width"));
1202
+ if (!transitionProperties.includes("height")) draggedEl.style.transition += ", ".concat(trs("height"));
1203
+ }
1189
1204
  /**
1190
1205
  * clones the given element and applies proper styles and transitions to the dragged element
1191
1206
  * @param {HTMLElement} originalElement
@@ -1219,7 +1234,7 @@
1219
1234
  draggedEl.style.transition = "".concat(trs("top"), ", ").concat(trs("left"), ", ").concat(trs("background-color"), ", ").concat(trs("opacity"), ", ").concat(trs("color"), " ");
1220
1235
  // this is a workaround for a strange browser bug that causes the right border to disappear when all the transitions are added at the same time
1221
1236
  window.setTimeout(function () {
1222
- return draggedEl.style.transition += ", ".concat(trs("width"), ", ").concat(trs("height"));
1237
+ return ensureMorphSizeTransitions(draggedEl);
1223
1238
  }, 0);
1224
1239
  draggedEl.style.zIndex = "9999";
1225
1240
  draggedEl.style.cursor = "grabbing";
@@ -1242,22 +1257,26 @@
1242
1257
  * @param {number} currentMouseY
1243
1258
  */
1244
1259
  function morphDraggedElementToBeLike(draggedEl, copyFromEl, currentMouseX, currentMouseY) {
1260
+ ensureMorphSizeTransitions(draggedEl);
1245
1261
  copyStylesFromTo(copyFromEl, draggedEl);
1246
1262
  var newRect = copyFromEl.getBoundingClientRect();
1247
1263
  var draggedElRect = draggedEl.getBoundingClientRect();
1264
+ var draggedElComputedStyle = window.getComputedStyle(draggedEl);
1265
+ var currentLeft = parseFloat(draggedElComputedStyle.left);
1266
+ var currentTop = parseFloat(draggedElComputedStyle.top);
1248
1267
  var widthChange = newRect.width - draggedElRect.width;
1249
1268
  var heightChange = newRect.height - draggedElRect.height;
1250
1269
  if (widthChange || heightChange) {
1251
1270
  var relativeDistanceOfMousePointerFromDraggedSides = {
1252
- left: (currentMouseX - draggedElRect.left) / draggedElRect.width,
1253
- top: (currentMouseY - draggedElRect.top) / draggedElRect.height
1271
+ left: draggedElRect.width ? (currentMouseX - draggedElRect.left) / draggedElRect.width : 0,
1272
+ top: draggedElRect.height ? (currentMouseY - draggedElRect.top) / draggedElRect.height : 0
1254
1273
  };
1255
1274
  if (!getFeatureFlag(FEATURE_FLAG_NAMES.USE_COMPUTED_STYLE_INSTEAD_OF_BOUNDING_RECT)) {
1256
1275
  draggedEl.style.height = "".concat(newRect.height, "px");
1257
1276
  draggedEl.style.width = "".concat(newRect.width, "px");
1258
1277
  }
1259
- draggedEl.style.left = "".concat(parseFloat(draggedEl.style.left) - relativeDistanceOfMousePointerFromDraggedSides.left * widthChange, "px");
1260
- draggedEl.style.top = "".concat(parseFloat(draggedEl.style.top) - relativeDistanceOfMousePointerFromDraggedSides.top * heightChange, "px");
1278
+ draggedEl.style.left = "".concat(currentLeft - relativeDistanceOfMousePointerFromDraggedSides.left * widthChange, "px");
1279
+ draggedEl.style.top = "".concat(currentTop - relativeDistanceOfMousePointerFromDraggedSides.top * heightChange, "px");
1261
1280
  }
1262
1281
  }
1263
1282
 
@@ -1413,6 +1432,8 @@
1413
1432
  var touchDragHoldTimer;
1414
1433
  var touchHoldElapsed = false;
1415
1434
  var useCursorForDetectionActive = false;
1435
+ var pendingDragOwner;
1436
+ var watchedDropZones = new Set();
1416
1437
 
1417
1438
  // a map from type to a set of drop-zones
1418
1439
  var typeToDropZones$1 = new Map();
@@ -1435,9 +1456,10 @@
1435
1456
  }
1436
1457
  }
1437
1458
  function unregisterDropZone$1(dropZoneEl, type) {
1438
- typeToDropZones$1.get(type)["delete"](dropZoneEl);
1459
+ var dropZones = typeToDropZones$1.get(type);
1460
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
1439
1461
  decrementActiveDropZoneCount();
1440
- if (typeToDropZones$1.get(type).size === 0) {
1462
+ if (dropZones.size === 0) {
1441
1463
  typeToDropZones$1["delete"](type);
1442
1464
  }
1443
1465
  }
@@ -1448,7 +1470,10 @@
1448
1470
  return "watching dragged element";
1449
1471
  });
1450
1472
  var dropZones = typeToDropZones$1.get(draggedElType);
1451
- var _iterator = _createForOfIteratorHelper(dropZones),
1473
+ if (!dropZones || dropZones.size === 0) return;
1474
+ if (watchedDropZones.size > 0) unWatchDraggedElement();
1475
+ watchedDropZones = new Set(dropZones);
1476
+ var _iterator = _createForOfIteratorHelper(watchedDropZones),
1452
1477
  _step;
1453
1478
  try {
1454
1479
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -1465,11 +1490,11 @@
1465
1490
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1466
1491
 
1467
1492
  // 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) {
1493
+ var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(watchedDropZones).map(function (dz) {
1469
1494
  return dzToConfig$1.get(dz).dropAnimationDurationMs;
1470
1495
  })));
1471
1496
  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 () {
1497
+ multiScroller = createMultiScroller(watchedDropZones, function () {
1473
1498
  return currentMousePosition;
1474
1499
  });
1475
1500
  // Returns reference point in document coordinates - either cursor position or element center
@@ -1481,14 +1506,13 @@
1481
1506
  } : function () {
1482
1507
  return findCenterOfElement(draggedEl);
1483
1508
  };
1484
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1509
+ observe(draggedEl, watchedDropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1485
1510
  }
1486
1511
  function unWatchDraggedElement() {
1487
1512
  printDebug(function () {
1488
1513
  return "unwatching dragged element";
1489
1514
  });
1490
- var dropZones = typeToDropZones$1.get(draggedElType);
1491
- var _iterator2 = _createForOfIteratorHelper(dropZones),
1515
+ var _iterator2 = _createForOfIteratorHelper(watchedDropZones),
1492
1516
  _step2;
1493
1517
  try {
1494
1518
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
@@ -1502,6 +1526,7 @@
1502
1526
  } finally {
1503
1527
  _iterator2.f();
1504
1528
  }
1529
+ watchedDropZones = new Set();
1505
1530
  window.removeEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1506
1531
  // ensuring multiScroller is not already destroyed before destroying
1507
1532
  if (multiScroller) {
@@ -1733,11 +1758,13 @@
1733
1758
  window.setTimeout(callback, dropAnimationDurationMs);
1734
1759
  }
1735
1760
  function scheduleDZForRemovalAfterDrop(dz, destroy) {
1736
- scheduledForRemovalAfterDrop.push({
1761
+ var scheduledRemoval = {
1737
1762
  dz: dz,
1738
1763
  destroy: destroy
1739
- });
1764
+ };
1765
+ scheduledForRemovalAfterDrop.push(scheduledRemoval);
1740
1766
  window.requestAnimationFrame(function () {
1767
+ if (!scheduledForRemovalAfterDrop.includes(scheduledRemoval)) return;
1741
1768
  hideElement(dz);
1742
1769
  document.body.appendChild(dz);
1743
1770
  });
@@ -1771,6 +1798,7 @@
1771
1798
  touchDragHoldTimer = undefined;
1772
1799
  touchHoldElapsed = false;
1773
1800
  useCursorForDetectionActive = false;
1801
+ pendingDragOwner = undefined;
1774
1802
  if (scheduledForRemovalAfterDrop.length) {
1775
1803
  printDebug(function () {
1776
1804
  return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
@@ -1786,6 +1814,7 @@
1786
1814
  }
1787
1815
  function dndzone$2(node, options) {
1788
1816
  var initialized = false;
1817
+ var destroyed = false;
1789
1818
  var config = {
1790
1819
  items: undefined,
1791
1820
  type: undefined,
@@ -1833,11 +1862,17 @@
1833
1862
  touchHoldElapsed = false;
1834
1863
  }
1835
1864
  }
1836
- function handleFalseAlarm(e) {
1865
+ function cancelPendingDrag() {
1866
+ if (pendingDragOwner !== node) return;
1837
1867
  removeMaybeListeners();
1868
+ pendingDragOwner = undefined;
1838
1869
  originalDragTarget = undefined;
1839
1870
  dragStartMousePosition = undefined;
1840
1871
  currentMousePosition = undefined;
1872
+ }
1873
+ function handleFalseAlarm(e) {
1874
+ if (pendingDragOwner !== node) return;
1875
+ cancelPendingDrag();
1841
1876
 
1842
1877
  // dragging initiated by touch events prevents onclick from initially firing
1843
1878
  if (e.type === "touchend") {
@@ -1850,6 +1885,7 @@
1850
1885
  }
1851
1886
  }
1852
1887
  function handleMouseMoveMaybeDragStart(e) {
1888
+ if (destroyed || pendingDragOwner !== node) return;
1853
1889
  var isTouch = !!e.touches;
1854
1890
  var c = isTouch ? e.touches[0] : e;
1855
1891
  // If touch drag delay is configured and not elapsed yet, allow scrolling until either
@@ -1878,7 +1914,6 @@
1878
1914
  y: c.clientY
1879
1915
  };
1880
1916
  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) {
1881
- removeMaybeListeners();
1882
1917
  handleDragStart();
1883
1918
  }
1884
1919
  }
@@ -1897,9 +1932,9 @@
1897
1932
  });
1898
1933
  return;
1899
1934
  }
1900
- if (isWorkingOnPreviousDrag) {
1935
+ if (destroyed || isWorkingOnPreviousDrag || pendingDragOwner) {
1901
1936
  printDebug(function () {
1902
- return "cannot start a new drag before finalizing previous one";
1937
+ return "cannot start a new drag before finalizing the current drag or pending gesture";
1903
1938
  });
1904
1939
  return;
1905
1940
  }
@@ -1909,6 +1944,7 @@
1909
1944
  e.preventDefault();
1910
1945
  }
1911
1946
  e.stopPropagation();
1947
+ pendingDragOwner = node;
1912
1948
  var c = isTouchStart ? e.touches[0] : e;
1913
1949
  dragStartMousePosition = {
1914
1950
  x: c.clientX,
@@ -1917,18 +1953,25 @@
1917
1953
  currentMousePosition = _objectSpread2({}, dragStartMousePosition);
1918
1954
  originalDragTarget = e.currentTarget;
1919
1955
  if (useDelay) {
1956
+ var pendingTarget = originalDragTarget;
1920
1957
  touchHoldElapsed = false;
1921
1958
  touchDragHoldTimer = window.setTimeout(function () {
1922
- // If the finger is still down and no false-alarm happened
1923
- if (!originalDragTarget) return;
1959
+ // If this action still owns the same pending gesture, transition it to a drag.
1960
+ if (destroyed || pendingDragOwner !== node || originalDragTarget !== pendingTarget) return;
1924
1961
  touchHoldElapsed = true;
1925
- removeMaybeListeners();
1926
1962
  handleDragStart();
1927
1963
  }, config.delayTouchStartMs);
1928
1964
  }
1929
1965
  addMaybeListeners();
1930
1966
  }
1931
1967
  function handleDragStart() {
1968
+ var _originalDragTarget;
1969
+ if (destroyed || pendingDragOwner !== node || isWorkingOnPreviousDrag || !((_originalDragTarget = originalDragTarget) !== null && _originalDragTarget !== void 0 && _originalDragTarget.parentElement)) {
1970
+ cancelPendingDrag();
1971
+ return;
1972
+ }
1973
+ removeMaybeListeners();
1974
+ pendingDragOwner = undefined;
1932
1975
  printDebug(function () {
1933
1976
  return ["drag start config: ".concat(toString(config)), originalDragTarget];
1934
1977
  });
@@ -2127,11 +2170,29 @@
2127
2170
  configure(newOptions);
2128
2171
  },
2129
2172
  destroy: function destroy() {
2173
+ if (destroyed) return;
2174
+ destroyed = true;
2175
+ cancelPendingDrag();
2176
+ var _iterator3 = _createForOfIteratorHelper(node.children),
2177
+ _step3;
2178
+ try {
2179
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2180
+ var draggableEl = _step3.value;
2181
+ draggableEl.removeEventListener("mousedown", elToMouseDownListener.get(draggableEl));
2182
+ draggableEl.removeEventListener("touchstart", elToMouseDownListener.get(draggableEl));
2183
+ }
2184
+ } catch (err) {
2185
+ _iterator3.e(err);
2186
+ } finally {
2187
+ _iterator3.f();
2188
+ }
2130
2189
  function destroyDz() {
2131
2190
  printDebug(function () {
2132
2191
  return "pointer dndzone will destroy";
2133
2192
  });
2134
- unregisterDropZone$1(node, dzToConfig$1.get(node).type);
2193
+ var registeredConfig = dzToConfig$1.get(node);
2194
+ if (!registeredConfig) return;
2195
+ unregisterDropZone$1(node, registeredConfig.type);
2135
2196
  dzToConfig$1["delete"](node);
2136
2197
  }
2137
2198
  if (isWorkingOnPreviousDrag && !node.closest("[".concat(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, "]"))) {
@@ -2288,12 +2349,13 @@
2288
2349
  printDebug(function () {
2289
2350
  return "unregistering drop-zone";
2290
2351
  });
2291
- if (focusedDz === dropZoneEl) {
2352
+ if (isDragging && focusedDz === dropZoneEl) {
2292
2353
  handleDrop();
2293
2354
  }
2294
- typeToDropZones.get(type)["delete"](dropZoneEl);
2355
+ var dropZones = typeToDropZones.get(type);
2356
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
2295
2357
  decrementActiveDropZoneCount();
2296
- if (typeToDropZones.get(type).size === 0) {
2358
+ if (dropZones.size === 0) {
2297
2359
  typeToDropZones["delete"](type);
2298
2360
  }
2299
2361
  if (typeToDropZones.size === 0) {
@@ -2355,17 +2417,20 @@
2355
2417
  }
2356
2418
  }
2357
2419
  var dzFrom = focusedDz;
2420
+ var movedItemId = focusedItemId;
2421
+ focusedDz = newlyFocusedDz;
2358
2422
  dispatchFinalizeEvent(dzFrom, originItems, {
2359
2423
  trigger: TRIGGERS.DROPPED_INTO_ANOTHER,
2360
- id: focusedItemId,
2361
- source: SOURCES.KEYBOARD
2362
- });
2363
- dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2364
- trigger: TRIGGERS.DROPPED_INTO_ZONE,
2365
- id: focusedItemId,
2424
+ id: movedItemId,
2366
2425
  source: SOURCES.KEYBOARD
2367
2426
  });
2368
- focusedDz = newlyFocusedDz;
2427
+ if (dzToConfig.has(newlyFocusedDz)) {
2428
+ dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2429
+ trigger: TRIGGERS.DROPPED_INTO_ZONE,
2430
+ id: movedItemId,
2431
+ source: SOURCES.KEYBOARD
2432
+ });
2433
+ }
2369
2434
  }
2370
2435
  function triggerAllDzsUpdate() {
2371
2436
  dzToHandles.forEach(function (_ref, dz) {
@@ -2375,27 +2440,23 @@
2375
2440
  }
2376
2441
  function handleDrop() {
2377
2442
  var dispatchConsider = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
2443
+ if (!isDragging || !focusedDz) return;
2378
2444
  printDebug(function () {
2379
2445
  return "drop";
2380
2446
  });
2381
- if (!dzToConfig.get(focusedDz).autoAriaDisabled) {
2447
+ var droppedDz = focusedDz;
2448
+ var droppedConfig = dzToConfig.get(droppedDz);
2449
+ var droppedItemId = focusedItemId;
2450
+ var droppedItemType = draggedItemType;
2451
+ if (!droppedConfig) return;
2452
+ if (!droppedConfig.autoAriaDisabled) {
2382
2453
  alertToScreenReader("Stopped dragging item ".concat(focusedItemLabel));
2383
2454
  }
2384
2455
  if (allDragTargets.has(document.activeElement)) {
2385
2456
  document.activeElement.blur();
2386
2457
  }
2387
- if (dispatchConsider) {
2388
- dispatchConsiderEvent(focusedDz, dzToConfig.get(focusedDz).items, {
2389
- trigger: TRIGGERS.DRAG_STOPPED,
2390
- id: focusedItemId,
2391
- source: SOURCES.KEYBOARD
2392
- });
2393
- }
2394
- styleInactiveDropZones(typeToDropZones.get(draggedItemType), function (dz) {
2395
- return dzToConfig.get(dz).dropTargetStyle;
2396
- }, function (dz) {
2397
- return dzToConfig.get(dz).dropTargetClasses;
2398
- });
2458
+ // Clear global drag state before dispatching. A synchronous handler may destroy the
2459
+ // focused zone, and unregisterDropZone must not recursively enter handleDrop.
2399
2460
  focusedItem = null;
2400
2461
  focusedItemId = null;
2401
2462
  focusedItemLabel = "";
@@ -2403,10 +2464,26 @@
2403
2464
  focusedDz = null;
2404
2465
  focusedDzLabel = "";
2405
2466
  isDragging = false;
2467
+ if (dispatchConsider) {
2468
+ dispatchConsiderEvent(droppedDz, droppedConfig.items, {
2469
+ trigger: TRIGGERS.DRAG_STOPPED,
2470
+ id: droppedItemId,
2471
+ source: SOURCES.KEYBOARD
2472
+ });
2473
+ }
2474
+ var dropZones = typeToDropZones.get(droppedItemType);
2475
+ if (dropZones) {
2476
+ styleInactiveDropZones(dropZones, function (dz) {
2477
+ return dzToConfig.get(dz).dropTargetStyle;
2478
+ }, function (dz) {
2479
+ return dzToConfig.get(dz).dropTargetClasses;
2480
+ });
2481
+ }
2406
2482
  triggerAllDzsUpdate();
2407
2483
  }
2408
2484
  //////
2409
2485
  function dndzone$1(node, options) {
2486
+ var destroyed = false;
2410
2487
  var config = {
2411
2488
  items: undefined,
2412
2489
  type: undefined,
@@ -2631,9 +2708,25 @@
2631
2708
  configure(newOptions);
2632
2709
  },
2633
2710
  destroy: function destroy() {
2711
+ if (destroyed) return;
2712
+ destroyed = true;
2634
2713
  printDebug(function () {
2635
2714
  return "keyboard dndzone will destroy";
2636
2715
  });
2716
+ node.removeEventListener("focus", handleZoneFocus);
2717
+ var _iterator = _createForOfIteratorHelper(node.children),
2718
+ _step;
2719
+ try {
2720
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2721
+ var draggableEl = _step.value;
2722
+ draggableEl.removeEventListener("keydown", elToKeyDownListeners.get(draggableEl));
2723
+ draggableEl.removeEventListener("click", elToFocusListeners.get(draggableEl));
2724
+ }
2725
+ } catch (err) {
2726
+ _iterator.e(err);
2727
+ } finally {
2728
+ _iterator.f();
2729
+ }
2637
2730
  unregisterDropZone(node, config.type);
2638
2731
  dzToConfig["delete"](node);
2639
2732
  dzToHandles["delete"](node);
@@ -2842,6 +2935,8 @@
2842
2935
  updateZone();
2843
2936
  },
2844
2937
  destroy: function destroy() {
2938
+ isItemsDragDisabled.set(true);
2939
+ zone.destroy();
2845
2940
  node.removeEventListener("consider", consider);
2846
2941
  node.removeEventListener("finalize", finalize);
2847
2942
  isItemsDragDisabled.unsubscribe(updateZone);
package/dist/index.mjs CHANGED
@@ -1180,6 +1180,21 @@ var TRANSITION_DURATION_SECONDS = 0.2;
1180
1180
  function trs(property) {
1181
1181
  return "".concat(property, " ").concat(TRANSITION_DURATION_SECONDS, "s ease");
1182
1182
  }
1183
+
1184
+ /**
1185
+ * Adds the size transitions after the dragged element's initial styles have been applied.
1186
+ * This normally happens in a timeout to avoid a browser rendering bug, but a synchronous
1187
+ * first morph can happen before that timeout and needs the transitions immediately.
1188
+ * @param {HTMLElement} draggedEl
1189
+ */
1190
+ function ensureMorphSizeTransitions(draggedEl) {
1191
+ var transitionProperties = draggedEl.style.transitionProperty.split(",").map(function (property) {
1192
+ return property.trim();
1193
+ });
1194
+ if (transitionProperties.includes("all")) return;
1195
+ if (!transitionProperties.includes("width")) draggedEl.style.transition += ", ".concat(trs("width"));
1196
+ if (!transitionProperties.includes("height")) draggedEl.style.transition += ", ".concat(trs("height"));
1197
+ }
1183
1198
  /**
1184
1199
  * clones the given element and applies proper styles and transitions to the dragged element
1185
1200
  * @param {HTMLElement} originalElement
@@ -1213,7 +1228,7 @@ function createDraggedElementFrom(originalElement, positionCenterOnXY) {
1213
1228
  draggedEl.style.transition = "".concat(trs("top"), ", ").concat(trs("left"), ", ").concat(trs("background-color"), ", ").concat(trs("opacity"), ", ").concat(trs("color"), " ");
1214
1229
  // this is a workaround for a strange browser bug that causes the right border to disappear when all the transitions are added at the same time
1215
1230
  window.setTimeout(function () {
1216
- return draggedEl.style.transition += ", ".concat(trs("width"), ", ").concat(trs("height"));
1231
+ return ensureMorphSizeTransitions(draggedEl);
1217
1232
  }, 0);
1218
1233
  draggedEl.style.zIndex = "9999";
1219
1234
  draggedEl.style.cursor = "grabbing";
@@ -1236,22 +1251,26 @@ function moveDraggedElementToWasDroppedState(draggedEl) {
1236
1251
  * @param {number} currentMouseY
1237
1252
  */
1238
1253
  function morphDraggedElementToBeLike(draggedEl, copyFromEl, currentMouseX, currentMouseY) {
1254
+ ensureMorphSizeTransitions(draggedEl);
1239
1255
  copyStylesFromTo(copyFromEl, draggedEl);
1240
1256
  var newRect = copyFromEl.getBoundingClientRect();
1241
1257
  var draggedElRect = draggedEl.getBoundingClientRect();
1258
+ var draggedElComputedStyle = window.getComputedStyle(draggedEl);
1259
+ var currentLeft = parseFloat(draggedElComputedStyle.left);
1260
+ var currentTop = parseFloat(draggedElComputedStyle.top);
1242
1261
  var widthChange = newRect.width - draggedElRect.width;
1243
1262
  var heightChange = newRect.height - draggedElRect.height;
1244
1263
  if (widthChange || heightChange) {
1245
1264
  var relativeDistanceOfMousePointerFromDraggedSides = {
1246
- left: (currentMouseX - draggedElRect.left) / draggedElRect.width,
1247
- top: (currentMouseY - draggedElRect.top) / draggedElRect.height
1265
+ left: draggedElRect.width ? (currentMouseX - draggedElRect.left) / draggedElRect.width : 0,
1266
+ top: draggedElRect.height ? (currentMouseY - draggedElRect.top) / draggedElRect.height : 0
1248
1267
  };
1249
1268
  if (!getFeatureFlag(FEATURE_FLAG_NAMES.USE_COMPUTED_STYLE_INSTEAD_OF_BOUNDING_RECT)) {
1250
1269
  draggedEl.style.height = "".concat(newRect.height, "px");
1251
1270
  draggedEl.style.width = "".concat(newRect.width, "px");
1252
1271
  }
1253
- draggedEl.style.left = "".concat(parseFloat(draggedEl.style.left) - relativeDistanceOfMousePointerFromDraggedSides.left * widthChange, "px");
1254
- draggedEl.style.top = "".concat(parseFloat(draggedEl.style.top) - relativeDistanceOfMousePointerFromDraggedSides.top * heightChange, "px");
1272
+ draggedEl.style.left = "".concat(currentLeft - relativeDistanceOfMousePointerFromDraggedSides.left * widthChange, "px");
1273
+ draggedEl.style.top = "".concat(currentTop - relativeDistanceOfMousePointerFromDraggedSides.top * heightChange, "px");
1255
1274
  }
1256
1275
  }
1257
1276
 
@@ -1407,6 +1426,8 @@ var multiScroller;
1407
1426
  var touchDragHoldTimer;
1408
1427
  var touchHoldElapsed = false;
1409
1428
  var useCursorForDetectionActive = false;
1429
+ var pendingDragOwner;
1430
+ var watchedDropZones = new Set();
1410
1431
 
1411
1432
  // a map from type to a set of drop-zones
1412
1433
  var typeToDropZones$1 = new Map();
@@ -1429,9 +1450,10 @@ function registerDropZone$1(dropZoneEl, type) {
1429
1450
  }
1430
1451
  }
1431
1452
  function unregisterDropZone$1(dropZoneEl, type) {
1432
- typeToDropZones$1.get(type)["delete"](dropZoneEl);
1453
+ var dropZones = typeToDropZones$1.get(type);
1454
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
1433
1455
  decrementActiveDropZoneCount();
1434
- if (typeToDropZones$1.get(type).size === 0) {
1456
+ if (dropZones.size === 0) {
1435
1457
  typeToDropZones$1["delete"](type);
1436
1458
  }
1437
1459
  }
@@ -1442,7 +1464,10 @@ function watchDraggedElement() {
1442
1464
  return "watching dragged element";
1443
1465
  });
1444
1466
  var dropZones = typeToDropZones$1.get(draggedElType);
1445
- var _iterator = _createForOfIteratorHelper(dropZones),
1467
+ if (!dropZones || dropZones.size === 0) return;
1468
+ if (watchedDropZones.size > 0) unWatchDraggedElement();
1469
+ watchedDropZones = new Set(dropZones);
1470
+ var _iterator = _createForOfIteratorHelper(watchedDropZones),
1446
1471
  _step;
1447
1472
  try {
1448
1473
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -1459,11 +1484,11 @@ function watchDraggedElement() {
1459
1484
  window.addEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1460
1485
 
1461
1486
  // 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) {
1487
+ var setIntervalMs = Math.max.apply(Math, _toConsumableArray(Array.from(watchedDropZones).map(function (dz) {
1463
1488
  return dzToConfig$1.get(dz).dropAnimationDurationMs;
1464
1489
  })));
1465
1490
  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 () {
1491
+ multiScroller = createMultiScroller(watchedDropZones, function () {
1467
1492
  return currentMousePosition;
1468
1493
  });
1469
1494
  // Returns reference point in document coordinates - either cursor position or element center
@@ -1475,14 +1500,13 @@ function watchDraggedElement() {
1475
1500
  } : function () {
1476
1501
  return findCenterOfElement(draggedEl);
1477
1502
  };
1478
- observe(draggedEl, dropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1503
+ observe(draggedEl, watchedDropZones, observationIntervalMs * 1.07, multiScroller, getReferencePoint);
1479
1504
  }
1480
1505
  function unWatchDraggedElement() {
1481
1506
  printDebug(function () {
1482
1507
  return "unwatching dragged element";
1483
1508
  });
1484
- var dropZones = typeToDropZones$1.get(draggedElType);
1485
- var _iterator2 = _createForOfIteratorHelper(dropZones),
1509
+ var _iterator2 = _createForOfIteratorHelper(watchedDropZones),
1486
1510
  _step2;
1487
1511
  try {
1488
1512
  for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
@@ -1496,6 +1520,7 @@ function unWatchDraggedElement() {
1496
1520
  } finally {
1497
1521
  _iterator2.f();
1498
1522
  }
1523
+ watchedDropZones = new Set();
1499
1524
  window.removeEventListener(DRAGGED_LEFT_DOCUMENT_EVENT_NAME, handleDrop$1);
1500
1525
  // ensuring multiScroller is not already destroyed before destroying
1501
1526
  if (multiScroller) {
@@ -1727,11 +1752,13 @@ function animateDraggedToFinalPosition(shadowElIdx, callback) {
1727
1752
  window.setTimeout(callback, dropAnimationDurationMs);
1728
1753
  }
1729
1754
  function scheduleDZForRemovalAfterDrop(dz, destroy) {
1730
- scheduledForRemovalAfterDrop.push({
1755
+ var scheduledRemoval = {
1731
1756
  dz: dz,
1732
1757
  destroy: destroy
1733
- });
1758
+ };
1759
+ scheduledForRemovalAfterDrop.push(scheduledRemoval);
1734
1760
  window.requestAnimationFrame(function () {
1761
+ if (!scheduledForRemovalAfterDrop.includes(scheduledRemoval)) return;
1735
1762
  hideElement(dz);
1736
1763
  document.body.appendChild(dz);
1737
1764
  });
@@ -1765,6 +1792,7 @@ function cleanupPostDrop() {
1765
1792
  touchDragHoldTimer = undefined;
1766
1793
  touchHoldElapsed = false;
1767
1794
  useCursorForDetectionActive = false;
1795
+ pendingDragOwner = undefined;
1768
1796
  if (scheduledForRemovalAfterDrop.length) {
1769
1797
  printDebug(function () {
1770
1798
  return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
@@ -1780,6 +1808,7 @@ function cleanupPostDrop() {
1780
1808
  }
1781
1809
  function dndzone$2(node, options) {
1782
1810
  var initialized = false;
1811
+ var destroyed = false;
1783
1812
  var config = {
1784
1813
  items: undefined,
1785
1814
  type: undefined,
@@ -1827,11 +1856,17 @@ function dndzone$2(node, options) {
1827
1856
  touchHoldElapsed = false;
1828
1857
  }
1829
1858
  }
1830
- function handleFalseAlarm(e) {
1859
+ function cancelPendingDrag() {
1860
+ if (pendingDragOwner !== node) return;
1831
1861
  removeMaybeListeners();
1862
+ pendingDragOwner = undefined;
1832
1863
  originalDragTarget = undefined;
1833
1864
  dragStartMousePosition = undefined;
1834
1865
  currentMousePosition = undefined;
1866
+ }
1867
+ function handleFalseAlarm(e) {
1868
+ if (pendingDragOwner !== node) return;
1869
+ cancelPendingDrag();
1835
1870
 
1836
1871
  // dragging initiated by touch events prevents onclick from initially firing
1837
1872
  if (e.type === "touchend") {
@@ -1844,6 +1879,7 @@ function dndzone$2(node, options) {
1844
1879
  }
1845
1880
  }
1846
1881
  function handleMouseMoveMaybeDragStart(e) {
1882
+ if (destroyed || pendingDragOwner !== node) return;
1847
1883
  var isTouch = !!e.touches;
1848
1884
  var c = isTouch ? e.touches[0] : e;
1849
1885
  // If touch drag delay is configured and not elapsed yet, allow scrolling until either
@@ -1872,7 +1908,6 @@ function dndzone$2(node, options) {
1872
1908
  y: c.clientY
1873
1909
  };
1874
1910
  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) {
1875
- removeMaybeListeners();
1876
1911
  handleDragStart();
1877
1912
  }
1878
1913
  }
@@ -1891,9 +1926,9 @@ function dndzone$2(node, options) {
1891
1926
  });
1892
1927
  return;
1893
1928
  }
1894
- if (isWorkingOnPreviousDrag) {
1929
+ if (destroyed || isWorkingOnPreviousDrag || pendingDragOwner) {
1895
1930
  printDebug(function () {
1896
- return "cannot start a new drag before finalizing previous one";
1931
+ return "cannot start a new drag before finalizing the current drag or pending gesture";
1897
1932
  });
1898
1933
  return;
1899
1934
  }
@@ -1903,6 +1938,7 @@ function dndzone$2(node, options) {
1903
1938
  e.preventDefault();
1904
1939
  }
1905
1940
  e.stopPropagation();
1941
+ pendingDragOwner = node;
1906
1942
  var c = isTouchStart ? e.touches[0] : e;
1907
1943
  dragStartMousePosition = {
1908
1944
  x: c.clientX,
@@ -1911,18 +1947,25 @@ function dndzone$2(node, options) {
1911
1947
  currentMousePosition = _objectSpread2({}, dragStartMousePosition);
1912
1948
  originalDragTarget = e.currentTarget;
1913
1949
  if (useDelay) {
1950
+ var pendingTarget = originalDragTarget;
1914
1951
  touchHoldElapsed = false;
1915
1952
  touchDragHoldTimer = window.setTimeout(function () {
1916
- // If the finger is still down and no false-alarm happened
1917
- if (!originalDragTarget) return;
1953
+ // If this action still owns the same pending gesture, transition it to a drag.
1954
+ if (destroyed || pendingDragOwner !== node || originalDragTarget !== pendingTarget) return;
1918
1955
  touchHoldElapsed = true;
1919
- removeMaybeListeners();
1920
1956
  handleDragStart();
1921
1957
  }, config.delayTouchStartMs);
1922
1958
  }
1923
1959
  addMaybeListeners();
1924
1960
  }
1925
1961
  function handleDragStart() {
1962
+ var _originalDragTarget;
1963
+ if (destroyed || pendingDragOwner !== node || isWorkingOnPreviousDrag || !((_originalDragTarget = originalDragTarget) !== null && _originalDragTarget !== void 0 && _originalDragTarget.parentElement)) {
1964
+ cancelPendingDrag();
1965
+ return;
1966
+ }
1967
+ removeMaybeListeners();
1968
+ pendingDragOwner = undefined;
1926
1969
  printDebug(function () {
1927
1970
  return ["drag start config: ".concat(toString(config)), originalDragTarget];
1928
1971
  });
@@ -2121,11 +2164,29 @@ function dndzone$2(node, options) {
2121
2164
  configure(newOptions);
2122
2165
  },
2123
2166
  destroy: function destroy() {
2167
+ if (destroyed) return;
2168
+ destroyed = true;
2169
+ cancelPendingDrag();
2170
+ var _iterator3 = _createForOfIteratorHelper(node.children),
2171
+ _step3;
2172
+ try {
2173
+ for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
2174
+ var draggableEl = _step3.value;
2175
+ draggableEl.removeEventListener("mousedown", elToMouseDownListener.get(draggableEl));
2176
+ draggableEl.removeEventListener("touchstart", elToMouseDownListener.get(draggableEl));
2177
+ }
2178
+ } catch (err) {
2179
+ _iterator3.e(err);
2180
+ } finally {
2181
+ _iterator3.f();
2182
+ }
2124
2183
  function destroyDz() {
2125
2184
  printDebug(function () {
2126
2185
  return "pointer dndzone will destroy";
2127
2186
  });
2128
- unregisterDropZone$1(node, dzToConfig$1.get(node).type);
2187
+ var registeredConfig = dzToConfig$1.get(node);
2188
+ if (!registeredConfig) return;
2189
+ unregisterDropZone$1(node, registeredConfig.type);
2129
2190
  dzToConfig$1["delete"](node);
2130
2191
  }
2131
2192
  if (isWorkingOnPreviousDrag && !node.closest("[".concat(ORIGINAL_DRAGGED_ITEM_MARKER_ATTRIBUTE, "]"))) {
@@ -2282,12 +2343,13 @@ function unregisterDropZone(dropZoneEl, type) {
2282
2343
  printDebug(function () {
2283
2344
  return "unregistering drop-zone";
2284
2345
  });
2285
- if (focusedDz === dropZoneEl) {
2346
+ if (isDragging && focusedDz === dropZoneEl) {
2286
2347
  handleDrop();
2287
2348
  }
2288
- typeToDropZones.get(type)["delete"](dropZoneEl);
2349
+ var dropZones = typeToDropZones.get(type);
2350
+ if (!dropZones || !dropZones["delete"](dropZoneEl)) return;
2289
2351
  decrementActiveDropZoneCount();
2290
- if (typeToDropZones.get(type).size === 0) {
2352
+ if (dropZones.size === 0) {
2291
2353
  typeToDropZones["delete"](type);
2292
2354
  }
2293
2355
  if (typeToDropZones.size === 0) {
@@ -2349,17 +2411,20 @@ function handleZoneFocus(e) {
2349
2411
  }
2350
2412
  }
2351
2413
  var dzFrom = focusedDz;
2414
+ var movedItemId = focusedItemId;
2415
+ focusedDz = newlyFocusedDz;
2352
2416
  dispatchFinalizeEvent(dzFrom, originItems, {
2353
2417
  trigger: TRIGGERS.DROPPED_INTO_ANOTHER,
2354
- id: focusedItemId,
2355
- source: SOURCES.KEYBOARD
2356
- });
2357
- dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2358
- trigger: TRIGGERS.DROPPED_INTO_ZONE,
2359
- id: focusedItemId,
2418
+ id: movedItemId,
2360
2419
  source: SOURCES.KEYBOARD
2361
2420
  });
2362
- focusedDz = newlyFocusedDz;
2421
+ if (dzToConfig.has(newlyFocusedDz)) {
2422
+ dispatchFinalizeEvent(newlyFocusedDz, targetItems, {
2423
+ trigger: TRIGGERS.DROPPED_INTO_ZONE,
2424
+ id: movedItemId,
2425
+ source: SOURCES.KEYBOARD
2426
+ });
2427
+ }
2363
2428
  }
2364
2429
  function triggerAllDzsUpdate() {
2365
2430
  dzToHandles.forEach(function (_ref, dz) {
@@ -2369,27 +2434,23 @@ function triggerAllDzsUpdate() {
2369
2434
  }
2370
2435
  function handleDrop() {
2371
2436
  var dispatchConsider = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
2437
+ if (!isDragging || !focusedDz) return;
2372
2438
  printDebug(function () {
2373
2439
  return "drop";
2374
2440
  });
2375
- if (!dzToConfig.get(focusedDz).autoAriaDisabled) {
2441
+ var droppedDz = focusedDz;
2442
+ var droppedConfig = dzToConfig.get(droppedDz);
2443
+ var droppedItemId = focusedItemId;
2444
+ var droppedItemType = draggedItemType;
2445
+ if (!droppedConfig) return;
2446
+ if (!droppedConfig.autoAriaDisabled) {
2376
2447
  alertToScreenReader("Stopped dragging item ".concat(focusedItemLabel));
2377
2448
  }
2378
2449
  if (allDragTargets.has(document.activeElement)) {
2379
2450
  document.activeElement.blur();
2380
2451
  }
2381
- if (dispatchConsider) {
2382
- dispatchConsiderEvent(focusedDz, dzToConfig.get(focusedDz).items, {
2383
- trigger: TRIGGERS.DRAG_STOPPED,
2384
- id: focusedItemId,
2385
- source: SOURCES.KEYBOARD
2386
- });
2387
- }
2388
- styleInactiveDropZones(typeToDropZones.get(draggedItemType), function (dz) {
2389
- return dzToConfig.get(dz).dropTargetStyle;
2390
- }, function (dz) {
2391
- return dzToConfig.get(dz).dropTargetClasses;
2392
- });
2452
+ // Clear global drag state before dispatching. A synchronous handler may destroy the
2453
+ // focused zone, and unregisterDropZone must not recursively enter handleDrop.
2393
2454
  focusedItem = null;
2394
2455
  focusedItemId = null;
2395
2456
  focusedItemLabel = "";
@@ -2397,10 +2458,26 @@ function handleDrop() {
2397
2458
  focusedDz = null;
2398
2459
  focusedDzLabel = "";
2399
2460
  isDragging = false;
2461
+ if (dispatchConsider) {
2462
+ dispatchConsiderEvent(droppedDz, droppedConfig.items, {
2463
+ trigger: TRIGGERS.DRAG_STOPPED,
2464
+ id: droppedItemId,
2465
+ source: SOURCES.KEYBOARD
2466
+ });
2467
+ }
2468
+ var dropZones = typeToDropZones.get(droppedItemType);
2469
+ if (dropZones) {
2470
+ styleInactiveDropZones(dropZones, function (dz) {
2471
+ return dzToConfig.get(dz).dropTargetStyle;
2472
+ }, function (dz) {
2473
+ return dzToConfig.get(dz).dropTargetClasses;
2474
+ });
2475
+ }
2400
2476
  triggerAllDzsUpdate();
2401
2477
  }
2402
2478
  //////
2403
2479
  function dndzone$1(node, options) {
2480
+ var destroyed = false;
2404
2481
  var config = {
2405
2482
  items: undefined,
2406
2483
  type: undefined,
@@ -2625,9 +2702,25 @@ function dndzone$1(node, options) {
2625
2702
  configure(newOptions);
2626
2703
  },
2627
2704
  destroy: function destroy() {
2705
+ if (destroyed) return;
2706
+ destroyed = true;
2628
2707
  printDebug(function () {
2629
2708
  return "keyboard dndzone will destroy";
2630
2709
  });
2710
+ node.removeEventListener("focus", handleZoneFocus);
2711
+ var _iterator = _createForOfIteratorHelper(node.children),
2712
+ _step;
2713
+ try {
2714
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
2715
+ var draggableEl = _step.value;
2716
+ draggableEl.removeEventListener("keydown", elToKeyDownListeners.get(draggableEl));
2717
+ draggableEl.removeEventListener("click", elToFocusListeners.get(draggableEl));
2718
+ }
2719
+ } catch (err) {
2720
+ _iterator.e(err);
2721
+ } finally {
2722
+ _iterator.f();
2723
+ }
2631
2724
  unregisterDropZone(node, config.type);
2632
2725
  dzToConfig["delete"](node);
2633
2726
  dzToHandles["delete"](node);
@@ -2836,6 +2929,8 @@ function dragHandleZone(node, options) {
2836
2929
  updateZone();
2837
2930
  },
2838
2931
  destroy: function destroy() {
2932
+ isItemsDragDisabled.set(true);
2933
+ zone.destroy();
2839
2934
  node.removeEventListener("consider", consider);
2840
2935
  node.removeEventListener("finalize", finalize);
2841
2936
  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.71",
4
+ "version": "0.9.73",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
@@ -13,6 +13,19 @@ const TRANSITION_DURATION_SECONDS = 0.2;
13
13
  function trs(property) {
14
14
  return `${property} ${TRANSITION_DURATION_SECONDS}s ease`;
15
15
  }
16
+
17
+ /**
18
+ * Adds the size transitions after the dragged element's initial styles have been applied.
19
+ * This normally happens in a timeout to avoid a browser rendering bug, but a synchronous
20
+ * first morph can happen before that timeout and needs the transitions immediately.
21
+ * @param {HTMLElement} draggedEl
22
+ */
23
+ function ensureMorphSizeTransitions(draggedEl) {
24
+ const transitionProperties = draggedEl.style.transitionProperty.split(",").map(property => property.trim());
25
+ if (transitionProperties.includes("all")) return;
26
+ if (!transitionProperties.includes("width")) draggedEl.style.transition += `, ${trs("width")}`;
27
+ if (!transitionProperties.includes("height")) draggedEl.style.transition += `, ${trs("height")}`;
28
+ }
16
29
  /**
17
30
  * clones the given element and applies proper styles and transitions to the dragged element
18
31
  * @param {HTMLElement} originalElement
@@ -45,7 +58,7 @@ export function createDraggedElementFrom(originalElement, positionCenterOnXY) {
45
58
  draggedEl.style.width = `${rect.width}px`;
46
59
  draggedEl.style.transition = `${trs("top")}, ${trs("left")}, ${trs("background-color")}, ${trs("opacity")}, ${trs("color")} `;
47
60
  // this is a workaround for a strange browser bug that causes the right border to disappear when all the transitions are added at the same time
48
- window.setTimeout(() => (draggedEl.style.transition += `, ${trs("width")}, ${trs("height")}`), 0);
61
+ window.setTimeout(() => ensureMorphSizeTransitions(draggedEl), 0);
49
62
  draggedEl.style.zIndex = "9999";
50
63
  draggedEl.style.cursor = "grabbing";
51
64
 
@@ -68,22 +81,26 @@ export function moveDraggedElementToWasDroppedState(draggedEl) {
68
81
  * @param {number} currentMouseY
69
82
  */
70
83
  export function morphDraggedElementToBeLike(draggedEl, copyFromEl, currentMouseX, currentMouseY) {
84
+ ensureMorphSizeTransitions(draggedEl);
71
85
  copyStylesFromTo(copyFromEl, draggedEl);
72
86
  const newRect = copyFromEl.getBoundingClientRect();
73
87
  const draggedElRect = draggedEl.getBoundingClientRect();
88
+ const draggedElComputedStyle = window.getComputedStyle(draggedEl);
89
+ const currentLeft = parseFloat(draggedElComputedStyle.left);
90
+ const currentTop = parseFloat(draggedElComputedStyle.top);
74
91
  const widthChange = newRect.width - draggedElRect.width;
75
92
  const heightChange = newRect.height - draggedElRect.height;
76
93
  if (widthChange || heightChange) {
77
94
  const relativeDistanceOfMousePointerFromDraggedSides = {
78
- left: (currentMouseX - draggedElRect.left) / draggedElRect.width,
79
- top: (currentMouseY - draggedElRect.top) / draggedElRect.height
95
+ left: draggedElRect.width ? (currentMouseX - draggedElRect.left) / draggedElRect.width : 0,
96
+ top: draggedElRect.height ? (currentMouseY - draggedElRect.top) / draggedElRect.height : 0
80
97
  };
81
98
  if (!getFeatureFlag(FEATURE_FLAG_NAMES.USE_COMPUTED_STYLE_INSTEAD_OF_BOUNDING_RECT)) {
82
99
  draggedEl.style.height = `${newRect.height}px`;
83
100
  draggedEl.style.width = `${newRect.width}px`;
84
101
  }
85
- draggedEl.style.left = `${parseFloat(draggedEl.style.left) - relativeDistanceOfMousePointerFromDraggedSides.left * widthChange}px`;
86
- draggedEl.style.top = `${parseFloat(draggedEl.style.top) - relativeDistanceOfMousePointerFromDraggedSides.top * heightChange}px`;
102
+ draggedEl.style.left = `${currentLeft - relativeDistanceOfMousePointerFromDraggedSides.left * widthChange}px`;
103
+ draggedEl.style.top = `${currentTop - relativeDistanceOfMousePointerFromDraggedSides.top * heightChange}px`;
87
104
  }
88
105
  }
89
106
 
@@ -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) {
@@ -315,8 +321,10 @@ function animateDraggedToFinalPosition(shadowElIdx, callback) {
315
321
  }
316
322
 
317
323
  function scheduleDZForRemovalAfterDrop(dz, destroy) {
318
- scheduledForRemovalAfterDrop.push({dz, destroy});
324
+ const scheduledRemoval = {dz, destroy};
325
+ scheduledForRemovalAfterDrop.push(scheduledRemoval);
319
326
  window.requestAnimationFrame(() => {
327
+ if (!scheduledForRemovalAfterDrop.includes(scheduledRemoval)) return;
320
328
  hideElement(dz);
321
329
  document.body.appendChild(dz);
322
330
  });
@@ -351,6 +359,7 @@ function cleanupPostDrop() {
351
359
  touchDragHoldTimer = undefined;
352
360
  touchHoldElapsed = false;
353
361
  useCursorForDetectionActive = false;
362
+ pendingDragOwner = undefined;
354
363
  if (scheduledForRemovalAfterDrop.length) {
355
364
  printDebug(() => ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop]);
356
365
  scheduledForRemovalAfterDrop.forEach(({dz, destroy}) => {
@@ -363,6 +372,7 @@ function cleanupPostDrop() {
363
372
 
364
373
  export function dndzone(node, options) {
365
374
  let initialized = false;
375
+ let destroyed = false;
366
376
  const config = {
367
377
  items: undefined,
368
378
  type: undefined,
@@ -398,11 +408,17 @@ export function dndzone(node, options) {
398
408
  touchHoldElapsed = false;
399
409
  }
400
410
  }
401
- function handleFalseAlarm(e) {
411
+ function cancelPendingDrag() {
412
+ if (pendingDragOwner !== node) return;
402
413
  removeMaybeListeners();
414
+ pendingDragOwner = undefined;
403
415
  originalDragTarget = undefined;
404
416
  dragStartMousePosition = undefined;
405
417
  currentMousePosition = undefined;
418
+ }
419
+ function handleFalseAlarm(e) {
420
+ if (pendingDragOwner !== node) return;
421
+ cancelPendingDrag();
406
422
 
407
423
  // dragging initiated by touch events prevents onclick from initially firing
408
424
  if (e.type === "touchend") {
@@ -416,6 +432,7 @@ export function dndzone(node, options) {
416
432
  }
417
433
 
418
434
  function handleMouseMoveMaybeDragStart(e) {
435
+ if (destroyed || pendingDragOwner !== node) return;
419
436
  const isTouch = !!e.touches;
420
437
  const c = isTouch ? e.touches[0] : e;
421
438
  // If touch drag delay is configured and not elapsed yet, allow scrolling until either
@@ -445,7 +462,6 @@ export function dndzone(node, options) {
445
462
  Math.abs(currentMousePosition.x - dragStartMousePosition.x) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX ||
446
463
  Math.abs(currentMousePosition.y - dragStartMousePosition.y) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX
447
464
  ) {
448
- removeMaybeListeners();
449
465
  handleDragStart();
450
466
  }
451
467
  }
@@ -460,8 +476,8 @@ export function dndzone(node, options) {
460
476
  printDebug(() => `ignoring none left click button: ${e.button}`);
461
477
  return;
462
478
  }
463
- if (isWorkingOnPreviousDrag) {
464
- 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");
465
481
  return;
466
482
  }
467
483
  const isTouchStart = !!e.touches;
@@ -471,6 +487,7 @@ export function dndzone(node, options) {
471
487
  e.preventDefault();
472
488
  }
473
489
  e.stopPropagation();
490
+ pendingDragOwner = node;
474
491
 
475
492
  const c = isTouchStart ? e.touches[0] : e;
476
493
  dragStartMousePosition = {x: c.clientX, y: c.clientY};
@@ -478,12 +495,12 @@ export function dndzone(node, options) {
478
495
  originalDragTarget = e.currentTarget;
479
496
 
480
497
  if (useDelay) {
498
+ const pendingTarget = originalDragTarget;
481
499
  touchHoldElapsed = false;
482
500
  touchDragHoldTimer = window.setTimeout(() => {
483
- // If the finger is still down and no false-alarm happened
484
- 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;
485
503
  touchHoldElapsed = true;
486
- removeMaybeListeners();
487
504
  handleDragStart();
488
505
  }, config.delayTouchStartMs);
489
506
  }
@@ -492,6 +509,12 @@ export function dndzone(node, options) {
492
509
  }
493
510
 
494
511
  function handleDragStart() {
512
+ if (destroyed || pendingDragOwner !== node || isWorkingOnPreviousDrag || !originalDragTarget?.parentElement) {
513
+ cancelPendingDrag();
514
+ return;
515
+ }
516
+ removeMaybeListeners();
517
+ pendingDragOwner = undefined;
495
518
  printDebug(() => [`drag start config: ${toString(config)}`, originalDragTarget]);
496
519
  isWorkingOnPreviousDrag = true;
497
520
 
@@ -670,9 +693,18 @@ export function dndzone(node, options) {
670
693
  configure(newOptions);
671
694
  },
672
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
+ }
673
703
  function destroyDz() {
674
704
  printDebug(() => "pointer dndzone will destroy");
675
- unregisterDropZone(node, dzToConfig.get(node).type);
705
+ const registeredConfig = dzToConfig.get(node);
706
+ if (!registeredConfig) return;
707
+ unregisterDropZone(node, registeredConfig.type);
676
708
  dzToConfig.delete(node);
677
709
  }
678
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);