svelte-dnd-action 0.9.62 → 0.9.64
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +119 -23
- package/dist/index.mjs +119 -23
- package/package.json +1 -1
- package/src/action.js +14 -0
- package/src/helpers/listUtil.js +28 -0
- package/src/pointerAction.js +85 -16
package/README.md
CHANGED
|
@@ -119,6 +119,10 @@ An options-object with the following attributes:
|
|
|
119
119
|
| `autoAriaDisabled` | Boolean | No | `false` | Setting it to true will disable all the automatically added aria attributes and aria alerts (for example when the user starts/ stops dragging using the keyboard).<br /> **Use it only if you intend to implement your own custom instructions, roles and alerts.** In such a case, you might find the exported function `alertToScreenReader(string)` useful. |
|
|
120
120
|
| `centreDraggedOnCursor` | Boolean | No | `false` | Setting it to true will cause elements from this dnd-zone to position their center on the cursor on drag start, effectively turning the cursor to the focal point that triggers all the dnd events (ex: entering another zone). Useful for dnd-zones with large items that can be dragged over small items. |
|
|
121
121
|
| `dropAnimationDisabled` | Boolean | No | `false` | Setting it to true will disable the animation of the dropped element to its final place. |
|
|
122
|
+
| `delayTouchStart` | Boolean \| Number | No | `false` | Prevents accidental drags on touch devices that should have been scrolls.
|
|
123
|
+
• `true` – enable with sensible default (80 ms).
|
|
124
|
+
• `number` – custom delay in milliseconds.
|
|
125
|
+
• `false` / omitted – legacy behaviour (drag starts immediately). |
|
|
122
126
|
|
|
123
127
|
##### Output:
|
|
124
128
|
|
|
@@ -477,3 +481,15 @@ Starting with version 0.9.42. if you use the hint make sure to include it in the
|
|
|
477
481
|
There is still quite a lot to do. If you'd like to contribute please get in touch (raise an issue or comment on an existing one).
|
|
478
482
|
Ideally, be specific about which area you'd like to help with.
|
|
479
483
|
Thank you for reading :)
|
|
484
|
+
|
|
485
|
+
If your page is scrollable and you found that quick swipes on touch devices sometimes start a drag instead of scrolling, enable the new `delayTouchStart` option:
|
|
486
|
+
|
|
487
|
+
```sveltehtml
|
|
488
|
+
<section use:dndzone={{ items, flipDurationMs, delayTouchStart: true }}>
|
|
489
|
+
…
|
|
490
|
+
</section>
|
|
491
|
+
```
|
|
492
|
+
|
|
493
|
+
Setting it to `true` waits 80 ms before hijacking the gesture; passing a number lets you tune the threshold.
|
|
494
|
+
|
|
495
|
+
If you want screen-readers to tell the user which item is being dragged and which container it interacts with, **please add `aria-label` on the container and on every draggable item**. The library will take care of the rest.
|
package/dist/index.d.ts
CHANGED
|
@@ -59,6 +59,11 @@ export interface Options<T extends Item = Item> {
|
|
|
59
59
|
autoAriaDisabled?: boolean;
|
|
60
60
|
centreDraggedOnCursor?: boolean;
|
|
61
61
|
dropAnimationDisabled?: boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Improve touch UX: true => default 80 ms delay, number => custom delay in ms.
|
|
64
|
+
* false/undefined => drag starts instantly (legacy behaviour)
|
|
65
|
+
*/
|
|
66
|
+
delayTouchStart?: boolean | number;
|
|
62
67
|
}
|
|
63
68
|
|
|
64
69
|
export interface DndZoneAttributes<T> {
|
package/dist/index.js
CHANGED
|
@@ -647,6 +647,32 @@
|
|
|
647
647
|
indexOfMin = _i;
|
|
648
648
|
}
|
|
649
649
|
}
|
|
650
|
+
|
|
651
|
+
// -------- Phantom slot check --------
|
|
652
|
+
// Regardless of layout (simple vertical list, flex-wrap, grid, floats …) the
|
|
653
|
+
// visually closest drop target can be *after* the current last **real** child.
|
|
654
|
+
// In simple layouts the would be index from the existing children would always be the last index
|
|
655
|
+
// but in more complex layouts (flex-wrap, grid, floats …) it can be any index.
|
|
656
|
+
// The problem is we can't predict where an additional element would be rendered in the general case,
|
|
657
|
+
// We therefore create a temporary, invisible clone of that last element, let
|
|
658
|
+
// the browser position it, measure the distance, and remove it immediately
|
|
659
|
+
// (same task → no paint). This leaves `children` back in its original state
|
|
660
|
+
// before we exit the function, so existing index-caching logic and shadow-
|
|
661
|
+
// element bookkeeping continue to work unchanged.
|
|
662
|
+
if (children.length > 0) {
|
|
663
|
+
var originalLen = children.length; // before we append the phantom
|
|
664
|
+
var template = children[originalLen - 1];
|
|
665
|
+
var phantom = template.cloneNode(false); // shallow clone is enough for size
|
|
666
|
+
phantom.style.visibility = "hidden";
|
|
667
|
+
phantom.style.pointerEvents = "none";
|
|
668
|
+
collectionBelowEl.appendChild(phantom);
|
|
669
|
+
var phantomDistance = calcDistanceBetweenCenters(floatingAboveEl, phantom);
|
|
670
|
+
if (phantomDistance < minDistanceSoFar) {
|
|
671
|
+
indexOfMin = originalLen; // index of phantom slot in original list
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
collectionBelowEl.removeChild(phantom);
|
|
675
|
+
}
|
|
650
676
|
return {
|
|
651
677
|
index: indexOfMin,
|
|
652
678
|
isProximityBased: true
|
|
@@ -1331,6 +1357,7 @@
|
|
|
1331
1357
|
var MIN_OBSERVATION_INTERVAL_MS = 100;
|
|
1332
1358
|
var DISABLED_OBSERVATION_INTERVAL_MS = 20;
|
|
1333
1359
|
var MIN_MOVEMENT_BEFORE_DRAG_START_PX = 3;
|
|
1360
|
+
var DEFAULT_TOUCH_DELAY_MS = 80;
|
|
1334
1361
|
var DEFAULT_DROP_TARGET_STYLE$1 = {
|
|
1335
1362
|
outline: "rgba(255, 255, 102, 0.7) solid 2px"
|
|
1336
1363
|
};
|
|
@@ -1351,6 +1378,8 @@
|
|
|
1351
1378
|
var isDraggedOutsideOfAnyDz = false;
|
|
1352
1379
|
var scheduledForRemovalAfterDrop = [];
|
|
1353
1380
|
var multiScroller;
|
|
1381
|
+
var touchDragHoldTimer;
|
|
1382
|
+
var touchHoldElapsed = false;
|
|
1354
1383
|
|
|
1355
1384
|
// a map from type to a set of drop-zones
|
|
1356
1385
|
var typeToDropZones$1 = new Map();
|
|
@@ -1482,10 +1511,7 @@
|
|
|
1482
1511
|
source: SOURCES.POINTER
|
|
1483
1512
|
});
|
|
1484
1513
|
}
|
|
1485
|
-
var
|
|
1486
|
-
index = _e$detail$indexObj.index,
|
|
1487
|
-
isProximityBased = _e$detail$indexObj.isProximityBased;
|
|
1488
|
-
var shadowElIdx = isProximityBased && index === e.currentTarget.children.length - 1 ? index + 1 : index;
|
|
1514
|
+
var shadowElIdx = e.detail.indexObj.index;
|
|
1489
1515
|
shadowElDropZone = e.currentTarget;
|
|
1490
1516
|
items.splice(shadowElIdx, 0, shadowElData);
|
|
1491
1517
|
dispatchConsiderEvent(e.currentTarget, items, {
|
|
@@ -1673,19 +1699,12 @@
|
|
|
1673
1699
|
}
|
|
1674
1700
|
/* cleanup */
|
|
1675
1701
|
function cleanupPostDrop() {
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
scheduledForRemovalAfterDrop.forEach(function (_ref) {
|
|
1683
|
-
var dz = _ref.dz,
|
|
1684
|
-
destroy = _ref.destroy;
|
|
1685
|
-
destroy();
|
|
1686
|
-
dz.remove();
|
|
1687
|
-
});
|
|
1688
|
-
scheduledForRemovalAfterDrop = [];
|
|
1702
|
+
// Remove the temporary elements that were kept in the DOM during the drag
|
|
1703
|
+
if (draggedEl && draggedEl.remove) {
|
|
1704
|
+
draggedEl.remove();
|
|
1705
|
+
}
|
|
1706
|
+
if (originalDragTarget && originalDragTarget.remove) {
|
|
1707
|
+
originalDragTarget.remove();
|
|
1689
1708
|
}
|
|
1690
1709
|
draggedEl = undefined;
|
|
1691
1710
|
originalDragTarget = undefined;
|
|
@@ -1701,6 +1720,23 @@
|
|
|
1701
1720
|
finalizingPreviousDrag = false;
|
|
1702
1721
|
unlockOriginDzMinDimensions = undefined;
|
|
1703
1722
|
isDraggedOutsideOfAnyDz = false;
|
|
1723
|
+
if (touchDragHoldTimer) {
|
|
1724
|
+
clearTimeout(touchDragHoldTimer);
|
|
1725
|
+
}
|
|
1726
|
+
touchDragHoldTimer = undefined;
|
|
1727
|
+
touchHoldElapsed = false;
|
|
1728
|
+
if (scheduledForRemovalAfterDrop.length) {
|
|
1729
|
+
printDebug(function () {
|
|
1730
|
+
return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
|
|
1731
|
+
});
|
|
1732
|
+
scheduledForRemovalAfterDrop.forEach(function (_ref) {
|
|
1733
|
+
var dz = _ref.dz,
|
|
1734
|
+
destroy = _ref.destroy;
|
|
1735
|
+
destroy();
|
|
1736
|
+
dz.remove();
|
|
1737
|
+
});
|
|
1738
|
+
scheduledForRemovalAfterDrop = [];
|
|
1739
|
+
}
|
|
1704
1740
|
}
|
|
1705
1741
|
function dndzone$2(node, options) {
|
|
1706
1742
|
var initialized = false;
|
|
@@ -1715,7 +1751,8 @@
|
|
|
1715
1751
|
dropTargetClasses: [],
|
|
1716
1752
|
transformDraggedElement: function transformDraggedElement() {},
|
|
1717
1753
|
centreDraggedOnCursor: false,
|
|
1718
|
-
dropAnimationDisabled: false
|
|
1754
|
+
dropAnimationDisabled: false,
|
|
1755
|
+
delayTouchStartMs: 0
|
|
1719
1756
|
};
|
|
1720
1757
|
printDebug(function () {
|
|
1721
1758
|
return ["dndzone good to go options: ".concat(toString(options), ", config: ").concat(toString(config)), {
|
|
@@ -1743,6 +1780,11 @@
|
|
|
1743
1780
|
window.removeEventListener("touchmove", handleMouseMoveMaybeDragStart);
|
|
1744
1781
|
window.removeEventListener("mouseup", handleFalseAlarm);
|
|
1745
1782
|
window.removeEventListener("touchend", handleFalseAlarm);
|
|
1783
|
+
if (touchDragHoldTimer) {
|
|
1784
|
+
clearTimeout(touchDragHoldTimer);
|
|
1785
|
+
touchDragHoldTimer = undefined;
|
|
1786
|
+
touchHoldElapsed = false;
|
|
1787
|
+
}
|
|
1746
1788
|
}
|
|
1747
1789
|
function handleFalseAlarm(e) {
|
|
1748
1790
|
removeMaybeListeners();
|
|
@@ -1761,8 +1803,29 @@
|
|
|
1761
1803
|
}
|
|
1762
1804
|
}
|
|
1763
1805
|
function handleMouseMoveMaybeDragStart(e) {
|
|
1806
|
+
var isTouch = !!e.touches;
|
|
1807
|
+
var c = isTouch ? e.touches[0] : e;
|
|
1808
|
+
// If touch drag delay is configured and not elapsed yet, allow scrolling until either
|
|
1809
|
+
// the delay elapses (timer will call handleDragStart) or the user moves significantly,
|
|
1810
|
+
// in which case we cancel the potential drag and let the interaction be a scroll.
|
|
1811
|
+
if (isTouch && config.delayTouchStartMs > 0 && !touchHoldElapsed) {
|
|
1812
|
+
currentMousePosition = {
|
|
1813
|
+
x: c.clientX,
|
|
1814
|
+
y: c.clientY
|
|
1815
|
+
};
|
|
1816
|
+
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) {
|
|
1817
|
+
// User started scrolling, cancel drag attempt.
|
|
1818
|
+
if (touchDragHoldTimer) {
|
|
1819
|
+
clearTimeout(touchDragHoldTimer);
|
|
1820
|
+
touchDragHoldTimer = undefined;
|
|
1821
|
+
}
|
|
1822
|
+
handleFalseAlarm(e);
|
|
1823
|
+
}
|
|
1824
|
+
return; // Do not preventDefault so scrolling works.
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
// legacy / post-delay path – block scrolling and maybe start drag
|
|
1764
1828
|
e.preventDefault();
|
|
1765
|
-
var c = e.touches ? e.touches[0] : e;
|
|
1766
1829
|
currentMousePosition = {
|
|
1767
1830
|
x: c.clientX,
|
|
1768
1831
|
y: c.clientY
|
|
@@ -1793,15 +1856,29 @@
|
|
|
1793
1856
|
});
|
|
1794
1857
|
return;
|
|
1795
1858
|
}
|
|
1796
|
-
e.
|
|
1859
|
+
var isTouchStart = !!e.touches;
|
|
1860
|
+
var useDelay = isTouchStart && config.delayTouchStartMs > 0;
|
|
1861
|
+
if (!useDelay) {
|
|
1862
|
+
e.preventDefault();
|
|
1863
|
+
}
|
|
1797
1864
|
e.stopPropagation();
|
|
1798
|
-
var c =
|
|
1865
|
+
var c = isTouchStart ? e.touches[0] : e;
|
|
1799
1866
|
dragStartMousePosition = {
|
|
1800
1867
|
x: c.clientX,
|
|
1801
1868
|
y: c.clientY
|
|
1802
1869
|
};
|
|
1803
1870
|
currentMousePosition = _objectSpread2({}, dragStartMousePosition);
|
|
1804
1871
|
originalDragTarget = e.currentTarget;
|
|
1872
|
+
if (useDelay) {
|
|
1873
|
+
touchHoldElapsed = false;
|
|
1874
|
+
touchDragHoldTimer = window.setTimeout(function () {
|
|
1875
|
+
// If the finger is still down and no false-alarm happened
|
|
1876
|
+
if (!originalDragTarget) return;
|
|
1877
|
+
touchHoldElapsed = true;
|
|
1878
|
+
removeMaybeListeners();
|
|
1879
|
+
handleDragStart();
|
|
1880
|
+
}, config.delayTouchStartMs);
|
|
1881
|
+
}
|
|
1805
1882
|
addMaybeListeners();
|
|
1806
1883
|
}
|
|
1807
1884
|
function handleDragStart() {
|
|
@@ -1899,8 +1976,17 @@
|
|
|
1899
1976
|
_ref2$centreDraggedOn = _ref2.centreDraggedOnCursor,
|
|
1900
1977
|
centreDraggedOnCursor = _ref2$centreDraggedOn === void 0 ? false : _ref2$centreDraggedOn,
|
|
1901
1978
|
_ref2$dropAnimationDi = _ref2.dropAnimationDisabled,
|
|
1902
|
-
dropAnimationDisabled = _ref2$dropAnimationDi === void 0 ? false : _ref2$dropAnimationDi
|
|
1979
|
+
dropAnimationDisabled = _ref2$dropAnimationDi === void 0 ? false : _ref2$dropAnimationDi,
|
|
1980
|
+
_ref2$delayTouchStart = _ref2.delayTouchStart,
|
|
1981
|
+
delayTouchStartOpt = _ref2$delayTouchStart === void 0 ? false : _ref2$delayTouchStart;
|
|
1903
1982
|
config.dropAnimationDurationMs = dropAnimationDurationMs;
|
|
1983
|
+
var effectiveDelayMs = 0;
|
|
1984
|
+
if (delayTouchStartOpt === true) {
|
|
1985
|
+
effectiveDelayMs = DEFAULT_TOUCH_DELAY_MS;
|
|
1986
|
+
} else if (typeof delayTouchStartOpt === "number" && isFinite(delayTouchStartOpt) && delayTouchStartOpt >= 0) {
|
|
1987
|
+
effectiveDelayMs = delayTouchStartOpt;
|
|
1988
|
+
}
|
|
1989
|
+
config.delayTouchStartMs = effectiveDelayMs;
|
|
1904
1990
|
if (config.type && newType !== config.type) {
|
|
1905
1991
|
unregisterDropZone$1(node, config.type);
|
|
1906
1992
|
}
|
|
@@ -2502,7 +2588,7 @@
|
|
|
2502
2588
|
return handles;
|
|
2503
2589
|
}
|
|
2504
2590
|
|
|
2505
|
-
var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "dropAnimationDisabled"];
|
|
2591
|
+
var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "delayTouchStart", "dropAnimationDisabled"];
|
|
2506
2592
|
|
|
2507
2593
|
/**
|
|
2508
2594
|
* A custom action to turn any container to a dnd zone and all of its direct children to draggables
|
|
@@ -2521,6 +2607,8 @@
|
|
|
2521
2607
|
* @property {number} [zoneItemTabIndex] - set the tabindex of the list container items when not dragging
|
|
2522
2608
|
* @property {object} [dropTargetStyle]
|
|
2523
2609
|
* @property {string[]} [dropTargetClasses]
|
|
2610
|
+
* @property {boolean|number} [delayTouchStart] - On touch devices, wait this long before converting the gesture to a drag.
|
|
2611
|
+
* `true` uses the built-in default (80 ms).
|
|
2524
2612
|
* @property {boolean} [dropAnimationDisabled] - cancels the drop animation to place
|
|
2525
2613
|
* @property {function} [transformDraggedElement]
|
|
2526
2614
|
* @param {HTMLElement} node - the element to enhance
|
|
@@ -2574,6 +2662,7 @@
|
|
|
2574
2662
|
options.transformDraggedElement;
|
|
2575
2663
|
options.autoAriaDisabled;
|
|
2576
2664
|
options.centreDraggedOnCursor;
|
|
2665
|
+
var delayTouchStart = options.delayTouchStart;
|
|
2577
2666
|
options.dropAnimationDisabled;
|
|
2578
2667
|
var rest = _objectWithoutProperties(options, _excluded);
|
|
2579
2668
|
/*eslint-enable*/
|
|
@@ -2598,6 +2687,13 @@
|
|
|
2598
2687
|
if (zoneItemTabIndex && !isInt(zoneItemTabIndex)) {
|
|
2599
2688
|
throw new Error("zoneItemTabIndex should be a number but instead it is a ".concat(_typeof(zoneItemTabIndex), ", ").concat(toString(zoneItemTabIndex)));
|
|
2600
2689
|
}
|
|
2690
|
+
if (delayTouchStart !== undefined && delayTouchStart !== false) {
|
|
2691
|
+
var validBoolean = delayTouchStart === true;
|
|
2692
|
+
var validNumber = typeof delayTouchStart === "number" && isFinite(delayTouchStart) && delayTouchStart >= 0;
|
|
2693
|
+
if (!validBoolean && !validNumber) {
|
|
2694
|
+
throw new Error("delayTouchStart should be a boolean (true/false) or a non-negative number but instead it is a ".concat(_typeof(delayTouchStart), ", ").concat(toString(delayTouchStart)));
|
|
2695
|
+
}
|
|
2696
|
+
}
|
|
2601
2697
|
}
|
|
2602
2698
|
function isInt(value) {
|
|
2603
2699
|
return !isNaN(value) && function (x) {
|
package/dist/index.mjs
CHANGED
|
@@ -641,6 +641,32 @@ function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
|
|
|
641
641
|
indexOfMin = _i;
|
|
642
642
|
}
|
|
643
643
|
}
|
|
644
|
+
|
|
645
|
+
// -------- Phantom slot check --------
|
|
646
|
+
// Regardless of layout (simple vertical list, flex-wrap, grid, floats …) the
|
|
647
|
+
// visually closest drop target can be *after* the current last **real** child.
|
|
648
|
+
// In simple layouts the would be index from the existing children would always be the last index
|
|
649
|
+
// but in more complex layouts (flex-wrap, grid, floats …) it can be any index.
|
|
650
|
+
// The problem is we can't predict where an additional element would be rendered in the general case,
|
|
651
|
+
// We therefore create a temporary, invisible clone of that last element, let
|
|
652
|
+
// the browser position it, measure the distance, and remove it immediately
|
|
653
|
+
// (same task → no paint). This leaves `children` back in its original state
|
|
654
|
+
// before we exit the function, so existing index-caching logic and shadow-
|
|
655
|
+
// element bookkeeping continue to work unchanged.
|
|
656
|
+
if (children.length > 0) {
|
|
657
|
+
var originalLen = children.length; // before we append the phantom
|
|
658
|
+
var template = children[originalLen - 1];
|
|
659
|
+
var phantom = template.cloneNode(false); // shallow clone is enough for size
|
|
660
|
+
phantom.style.visibility = "hidden";
|
|
661
|
+
phantom.style.pointerEvents = "none";
|
|
662
|
+
collectionBelowEl.appendChild(phantom);
|
|
663
|
+
var phantomDistance = calcDistanceBetweenCenters(floatingAboveEl, phantom);
|
|
664
|
+
if (phantomDistance < minDistanceSoFar) {
|
|
665
|
+
indexOfMin = originalLen; // index of phantom slot in original list
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
collectionBelowEl.removeChild(phantom);
|
|
669
|
+
}
|
|
644
670
|
return {
|
|
645
671
|
index: indexOfMin,
|
|
646
672
|
isProximityBased: true
|
|
@@ -1325,6 +1351,7 @@ var DEFAULT_DROP_ZONE_TYPE$1 = "--any--";
|
|
|
1325
1351
|
var MIN_OBSERVATION_INTERVAL_MS = 100;
|
|
1326
1352
|
var DISABLED_OBSERVATION_INTERVAL_MS = 20;
|
|
1327
1353
|
var MIN_MOVEMENT_BEFORE_DRAG_START_PX = 3;
|
|
1354
|
+
var DEFAULT_TOUCH_DELAY_MS = 80;
|
|
1328
1355
|
var DEFAULT_DROP_TARGET_STYLE$1 = {
|
|
1329
1356
|
outline: "rgba(255, 255, 102, 0.7) solid 2px"
|
|
1330
1357
|
};
|
|
@@ -1345,6 +1372,8 @@ var unlockOriginDzMinDimensions;
|
|
|
1345
1372
|
var isDraggedOutsideOfAnyDz = false;
|
|
1346
1373
|
var scheduledForRemovalAfterDrop = [];
|
|
1347
1374
|
var multiScroller;
|
|
1375
|
+
var touchDragHoldTimer;
|
|
1376
|
+
var touchHoldElapsed = false;
|
|
1348
1377
|
|
|
1349
1378
|
// a map from type to a set of drop-zones
|
|
1350
1379
|
var typeToDropZones$1 = new Map();
|
|
@@ -1476,10 +1505,7 @@ function handleDraggedEntered(e) {
|
|
|
1476
1505
|
source: SOURCES.POINTER
|
|
1477
1506
|
});
|
|
1478
1507
|
}
|
|
1479
|
-
var
|
|
1480
|
-
index = _e$detail$indexObj.index,
|
|
1481
|
-
isProximityBased = _e$detail$indexObj.isProximityBased;
|
|
1482
|
-
var shadowElIdx = isProximityBased && index === e.currentTarget.children.length - 1 ? index + 1 : index;
|
|
1508
|
+
var shadowElIdx = e.detail.indexObj.index;
|
|
1483
1509
|
shadowElDropZone = e.currentTarget;
|
|
1484
1510
|
items.splice(shadowElIdx, 0, shadowElData);
|
|
1485
1511
|
dispatchConsiderEvent(e.currentTarget, items, {
|
|
@@ -1667,19 +1693,12 @@ function scheduleDZForRemovalAfterDrop(dz, destroy) {
|
|
|
1667
1693
|
}
|
|
1668
1694
|
/* cleanup */
|
|
1669
1695
|
function cleanupPostDrop() {
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
scheduledForRemovalAfterDrop.forEach(function (_ref) {
|
|
1677
|
-
var dz = _ref.dz,
|
|
1678
|
-
destroy = _ref.destroy;
|
|
1679
|
-
destroy();
|
|
1680
|
-
dz.remove();
|
|
1681
|
-
});
|
|
1682
|
-
scheduledForRemovalAfterDrop = [];
|
|
1696
|
+
// Remove the temporary elements that were kept in the DOM during the drag
|
|
1697
|
+
if (draggedEl && draggedEl.remove) {
|
|
1698
|
+
draggedEl.remove();
|
|
1699
|
+
}
|
|
1700
|
+
if (originalDragTarget && originalDragTarget.remove) {
|
|
1701
|
+
originalDragTarget.remove();
|
|
1683
1702
|
}
|
|
1684
1703
|
draggedEl = undefined;
|
|
1685
1704
|
originalDragTarget = undefined;
|
|
@@ -1695,6 +1714,23 @@ function cleanupPostDrop() {
|
|
|
1695
1714
|
finalizingPreviousDrag = false;
|
|
1696
1715
|
unlockOriginDzMinDimensions = undefined;
|
|
1697
1716
|
isDraggedOutsideOfAnyDz = false;
|
|
1717
|
+
if (touchDragHoldTimer) {
|
|
1718
|
+
clearTimeout(touchDragHoldTimer);
|
|
1719
|
+
}
|
|
1720
|
+
touchDragHoldTimer = undefined;
|
|
1721
|
+
touchHoldElapsed = false;
|
|
1722
|
+
if (scheduledForRemovalAfterDrop.length) {
|
|
1723
|
+
printDebug(function () {
|
|
1724
|
+
return ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop];
|
|
1725
|
+
});
|
|
1726
|
+
scheduledForRemovalAfterDrop.forEach(function (_ref) {
|
|
1727
|
+
var dz = _ref.dz,
|
|
1728
|
+
destroy = _ref.destroy;
|
|
1729
|
+
destroy();
|
|
1730
|
+
dz.remove();
|
|
1731
|
+
});
|
|
1732
|
+
scheduledForRemovalAfterDrop = [];
|
|
1733
|
+
}
|
|
1698
1734
|
}
|
|
1699
1735
|
function dndzone$2(node, options) {
|
|
1700
1736
|
var initialized = false;
|
|
@@ -1709,7 +1745,8 @@ function dndzone$2(node, options) {
|
|
|
1709
1745
|
dropTargetClasses: [],
|
|
1710
1746
|
transformDraggedElement: function transformDraggedElement() {},
|
|
1711
1747
|
centreDraggedOnCursor: false,
|
|
1712
|
-
dropAnimationDisabled: false
|
|
1748
|
+
dropAnimationDisabled: false,
|
|
1749
|
+
delayTouchStartMs: 0
|
|
1713
1750
|
};
|
|
1714
1751
|
printDebug(function () {
|
|
1715
1752
|
return ["dndzone good to go options: ".concat(toString(options), ", config: ").concat(toString(config)), {
|
|
@@ -1737,6 +1774,11 @@ function dndzone$2(node, options) {
|
|
|
1737
1774
|
window.removeEventListener("touchmove", handleMouseMoveMaybeDragStart);
|
|
1738
1775
|
window.removeEventListener("mouseup", handleFalseAlarm);
|
|
1739
1776
|
window.removeEventListener("touchend", handleFalseAlarm);
|
|
1777
|
+
if (touchDragHoldTimer) {
|
|
1778
|
+
clearTimeout(touchDragHoldTimer);
|
|
1779
|
+
touchDragHoldTimer = undefined;
|
|
1780
|
+
touchHoldElapsed = false;
|
|
1781
|
+
}
|
|
1740
1782
|
}
|
|
1741
1783
|
function handleFalseAlarm(e) {
|
|
1742
1784
|
removeMaybeListeners();
|
|
@@ -1755,8 +1797,29 @@ function dndzone$2(node, options) {
|
|
|
1755
1797
|
}
|
|
1756
1798
|
}
|
|
1757
1799
|
function handleMouseMoveMaybeDragStart(e) {
|
|
1800
|
+
var isTouch = !!e.touches;
|
|
1801
|
+
var c = isTouch ? e.touches[0] : e;
|
|
1802
|
+
// If touch drag delay is configured and not elapsed yet, allow scrolling until either
|
|
1803
|
+
// the delay elapses (timer will call handleDragStart) or the user moves significantly,
|
|
1804
|
+
// in which case we cancel the potential drag and let the interaction be a scroll.
|
|
1805
|
+
if (isTouch && config.delayTouchStartMs > 0 && !touchHoldElapsed) {
|
|
1806
|
+
currentMousePosition = {
|
|
1807
|
+
x: c.clientX,
|
|
1808
|
+
y: c.clientY
|
|
1809
|
+
};
|
|
1810
|
+
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) {
|
|
1811
|
+
// User started scrolling, cancel drag attempt.
|
|
1812
|
+
if (touchDragHoldTimer) {
|
|
1813
|
+
clearTimeout(touchDragHoldTimer);
|
|
1814
|
+
touchDragHoldTimer = undefined;
|
|
1815
|
+
}
|
|
1816
|
+
handleFalseAlarm(e);
|
|
1817
|
+
}
|
|
1818
|
+
return; // Do not preventDefault so scrolling works.
|
|
1819
|
+
}
|
|
1820
|
+
|
|
1821
|
+
// legacy / post-delay path – block scrolling and maybe start drag
|
|
1758
1822
|
e.preventDefault();
|
|
1759
|
-
var c = e.touches ? e.touches[0] : e;
|
|
1760
1823
|
currentMousePosition = {
|
|
1761
1824
|
x: c.clientX,
|
|
1762
1825
|
y: c.clientY
|
|
@@ -1787,15 +1850,29 @@ function dndzone$2(node, options) {
|
|
|
1787
1850
|
});
|
|
1788
1851
|
return;
|
|
1789
1852
|
}
|
|
1790
|
-
e.
|
|
1853
|
+
var isTouchStart = !!e.touches;
|
|
1854
|
+
var useDelay = isTouchStart && config.delayTouchStartMs > 0;
|
|
1855
|
+
if (!useDelay) {
|
|
1856
|
+
e.preventDefault();
|
|
1857
|
+
}
|
|
1791
1858
|
e.stopPropagation();
|
|
1792
|
-
var c =
|
|
1859
|
+
var c = isTouchStart ? e.touches[0] : e;
|
|
1793
1860
|
dragStartMousePosition = {
|
|
1794
1861
|
x: c.clientX,
|
|
1795
1862
|
y: c.clientY
|
|
1796
1863
|
};
|
|
1797
1864
|
currentMousePosition = _objectSpread2({}, dragStartMousePosition);
|
|
1798
1865
|
originalDragTarget = e.currentTarget;
|
|
1866
|
+
if (useDelay) {
|
|
1867
|
+
touchHoldElapsed = false;
|
|
1868
|
+
touchDragHoldTimer = window.setTimeout(function () {
|
|
1869
|
+
// If the finger is still down and no false-alarm happened
|
|
1870
|
+
if (!originalDragTarget) return;
|
|
1871
|
+
touchHoldElapsed = true;
|
|
1872
|
+
removeMaybeListeners();
|
|
1873
|
+
handleDragStart();
|
|
1874
|
+
}, config.delayTouchStartMs);
|
|
1875
|
+
}
|
|
1799
1876
|
addMaybeListeners();
|
|
1800
1877
|
}
|
|
1801
1878
|
function handleDragStart() {
|
|
@@ -1893,8 +1970,17 @@ function dndzone$2(node, options) {
|
|
|
1893
1970
|
_ref2$centreDraggedOn = _ref2.centreDraggedOnCursor,
|
|
1894
1971
|
centreDraggedOnCursor = _ref2$centreDraggedOn === void 0 ? false : _ref2$centreDraggedOn,
|
|
1895
1972
|
_ref2$dropAnimationDi = _ref2.dropAnimationDisabled,
|
|
1896
|
-
dropAnimationDisabled = _ref2$dropAnimationDi === void 0 ? false : _ref2$dropAnimationDi
|
|
1973
|
+
dropAnimationDisabled = _ref2$dropAnimationDi === void 0 ? false : _ref2$dropAnimationDi,
|
|
1974
|
+
_ref2$delayTouchStart = _ref2.delayTouchStart,
|
|
1975
|
+
delayTouchStartOpt = _ref2$delayTouchStart === void 0 ? false : _ref2$delayTouchStart;
|
|
1897
1976
|
config.dropAnimationDurationMs = dropAnimationDurationMs;
|
|
1977
|
+
var effectiveDelayMs = 0;
|
|
1978
|
+
if (delayTouchStartOpt === true) {
|
|
1979
|
+
effectiveDelayMs = DEFAULT_TOUCH_DELAY_MS;
|
|
1980
|
+
} else if (typeof delayTouchStartOpt === "number" && isFinite(delayTouchStartOpt) && delayTouchStartOpt >= 0) {
|
|
1981
|
+
effectiveDelayMs = delayTouchStartOpt;
|
|
1982
|
+
}
|
|
1983
|
+
config.delayTouchStartMs = effectiveDelayMs;
|
|
1898
1984
|
if (config.type && newType !== config.type) {
|
|
1899
1985
|
unregisterDropZone$1(node, config.type);
|
|
1900
1986
|
}
|
|
@@ -2496,7 +2582,7 @@ function dndzone$1(node, options) {
|
|
|
2496
2582
|
return handles;
|
|
2497
2583
|
}
|
|
2498
2584
|
|
|
2499
|
-
var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "dropAnimationDisabled"];
|
|
2585
|
+
var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabled", "dropFromOthersDisabled", "zoneTabIndex", "zoneItemTabIndex", "dropTargetStyle", "dropTargetClasses", "transformDraggedElement", "autoAriaDisabled", "centreDraggedOnCursor", "delayTouchStart", "dropAnimationDisabled"];
|
|
2500
2586
|
|
|
2501
2587
|
/**
|
|
2502
2588
|
* A custom action to turn any container to a dnd zone and all of its direct children to draggables
|
|
@@ -2515,6 +2601,8 @@ var _excluded = ["items", "flipDurationMs", "type", "dragDisabled", "morphDisabl
|
|
|
2515
2601
|
* @property {number} [zoneItemTabIndex] - set the tabindex of the list container items when not dragging
|
|
2516
2602
|
* @property {object} [dropTargetStyle]
|
|
2517
2603
|
* @property {string[]} [dropTargetClasses]
|
|
2604
|
+
* @property {boolean|number} [delayTouchStart] - On touch devices, wait this long before converting the gesture to a drag.
|
|
2605
|
+
* `true` uses the built-in default (80 ms).
|
|
2518
2606
|
* @property {boolean} [dropAnimationDisabled] - cancels the drop animation to place
|
|
2519
2607
|
* @property {function} [transformDraggedElement]
|
|
2520
2608
|
* @param {HTMLElement} node - the element to enhance
|
|
@@ -2568,6 +2656,7 @@ function validateOptions(options) {
|
|
|
2568
2656
|
options.transformDraggedElement;
|
|
2569
2657
|
options.autoAriaDisabled;
|
|
2570
2658
|
options.centreDraggedOnCursor;
|
|
2659
|
+
var delayTouchStart = options.delayTouchStart;
|
|
2571
2660
|
options.dropAnimationDisabled;
|
|
2572
2661
|
var rest = _objectWithoutProperties(options, _excluded);
|
|
2573
2662
|
/*eslint-enable*/
|
|
@@ -2592,6 +2681,13 @@ function validateOptions(options) {
|
|
|
2592
2681
|
if (zoneItemTabIndex && !isInt(zoneItemTabIndex)) {
|
|
2593
2682
|
throw new Error("zoneItemTabIndex should be a number but instead it is a ".concat(_typeof(zoneItemTabIndex), ", ").concat(toString(zoneItemTabIndex)));
|
|
2594
2683
|
}
|
|
2684
|
+
if (delayTouchStart !== undefined && delayTouchStart !== false) {
|
|
2685
|
+
var validBoolean = delayTouchStart === true;
|
|
2686
|
+
var validNumber = typeof delayTouchStart === "number" && isFinite(delayTouchStart) && delayTouchStart >= 0;
|
|
2687
|
+
if (!validBoolean && !validNumber) {
|
|
2688
|
+
throw new Error("delayTouchStart should be a boolean (true/false) or a non-negative number but instead it is a ".concat(_typeof(delayTouchStart), ", ").concat(toString(delayTouchStart)));
|
|
2689
|
+
}
|
|
2690
|
+
}
|
|
2595
2691
|
}
|
|
2596
2692
|
function isInt(value) {
|
|
2597
2693
|
return !isNaN(value) && function (x) {
|
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.
|
|
4
|
+
"version": "0.9.64",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/isaacHagoel/svelte-dnd-action.git"
|
package/src/action.js
CHANGED
|
@@ -20,6 +20,8 @@ import {toString} from "./helpers/util";
|
|
|
20
20
|
* @property {number} [zoneItemTabIndex] - set the tabindex of the list container items when not dragging
|
|
21
21
|
* @property {object} [dropTargetStyle]
|
|
22
22
|
* @property {string[]} [dropTargetClasses]
|
|
23
|
+
* @property {boolean|number} [delayTouchStart] - On touch devices, wait this long before converting the gesture to a drag.
|
|
24
|
+
* `true` uses the built-in default (80 ms).
|
|
23
25
|
* @property {boolean} [dropAnimationDisabled] - cancels the drop animation to place
|
|
24
26
|
* @property {function} [transformDraggedElement]
|
|
25
27
|
* @param {HTMLElement} node - the element to enhance
|
|
@@ -75,6 +77,7 @@ function validateOptions(options) {
|
|
|
75
77
|
transformDraggedElement,
|
|
76
78
|
autoAriaDisabled,
|
|
77
79
|
centreDraggedOnCursor,
|
|
80
|
+
delayTouchStart,
|
|
78
81
|
dropAnimationDisabled,
|
|
79
82
|
...rest
|
|
80
83
|
} = options;
|
|
@@ -98,6 +101,17 @@ function validateOptions(options) {
|
|
|
98
101
|
if (zoneItemTabIndex && !isInt(zoneItemTabIndex)) {
|
|
99
102
|
throw new Error(`zoneItemTabIndex should be a number but instead it is a ${typeof zoneItemTabIndex}, ${toString(zoneItemTabIndex)}`);
|
|
100
103
|
}
|
|
104
|
+
if (delayTouchStart !== undefined && delayTouchStart !== false) {
|
|
105
|
+
const validBoolean = delayTouchStart === true;
|
|
106
|
+
const validNumber = typeof delayTouchStart === "number" && isFinite(delayTouchStart) && delayTouchStart >= 0;
|
|
107
|
+
if (!validBoolean && !validNumber) {
|
|
108
|
+
throw new Error(
|
|
109
|
+
`delayTouchStart should be a boolean (true/false) or a non-negative number but instead it is a ${typeof delayTouchStart}, ${toString(
|
|
110
|
+
delayTouchStart
|
|
111
|
+
)}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
101
115
|
}
|
|
102
116
|
|
|
103
117
|
function isInt(value) {
|
package/src/helpers/listUtil.js
CHANGED
|
@@ -77,5 +77,33 @@ export function findWouldBeIndex(floatingAboveEl, collectionBelowEl) {
|
|
|
77
77
|
indexOfMin = i;
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
|
+
|
|
81
|
+
// -------- Phantom slot check --------
|
|
82
|
+
// Regardless of layout (simple vertical list, flex-wrap, grid, floats …) the
|
|
83
|
+
// visually closest drop target can be *after* the current last **real** child.
|
|
84
|
+
// In simple layouts the would be index from the existing children would always be the last index
|
|
85
|
+
// but in more complex layouts (flex-wrap, grid, floats …) it can be any index.
|
|
86
|
+
// The problem is we can't predict where an additional element would be rendered in the general case,
|
|
87
|
+
// We therefore create a temporary, invisible clone of that last element, let
|
|
88
|
+
// the browser position it, measure the distance, and remove it immediately
|
|
89
|
+
// (same task → no paint). This leaves `children` back in its original state
|
|
90
|
+
// before we exit the function, so existing index-caching logic and shadow-
|
|
91
|
+
// element bookkeeping continue to work unchanged.
|
|
92
|
+
if (children.length > 0) {
|
|
93
|
+
const originalLen = children.length; // before we append the phantom
|
|
94
|
+
const template = children[originalLen - 1];
|
|
95
|
+
const phantom = template.cloneNode(false); // shallow clone is enough for size
|
|
96
|
+
phantom.style.visibility = "hidden";
|
|
97
|
+
phantom.style.pointerEvents = "none";
|
|
98
|
+
collectionBelowEl.appendChild(phantom);
|
|
99
|
+
|
|
100
|
+
const phantomDistance = calcDistanceBetweenCenters(floatingAboveEl, phantom);
|
|
101
|
+
if (phantomDistance < minDistanceSoFar) {
|
|
102
|
+
indexOfMin = originalLen; // index of phantom slot in original list
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
collectionBelowEl.removeChild(phantom);
|
|
106
|
+
}
|
|
107
|
+
|
|
80
108
|
return {index: indexOfMin, isProximityBased: true};
|
|
81
109
|
}
|
package/src/pointerAction.js
CHANGED
|
@@ -39,6 +39,7 @@ const DEFAULT_DROP_ZONE_TYPE = "--any--";
|
|
|
39
39
|
const MIN_OBSERVATION_INTERVAL_MS = 100;
|
|
40
40
|
const DISABLED_OBSERVATION_INTERVAL_MS = 20;
|
|
41
41
|
const MIN_MOVEMENT_BEFORE_DRAG_START_PX = 3;
|
|
42
|
+
const DEFAULT_TOUCH_DELAY_MS = 80;
|
|
42
43
|
const DEFAULT_DROP_TARGET_STYLE = {
|
|
43
44
|
outline: "rgba(255, 255, 102, 0.7) solid 2px"
|
|
44
45
|
};
|
|
@@ -60,6 +61,8 @@ let unlockOriginDzMinDimensions;
|
|
|
60
61
|
let isDraggedOutsideOfAnyDz = false;
|
|
61
62
|
let scheduledForRemovalAfterDrop = [];
|
|
62
63
|
let multiScroller;
|
|
64
|
+
let touchDragHoldTimer;
|
|
65
|
+
let touchHoldElapsed = false;
|
|
63
66
|
|
|
64
67
|
// a map from type to a set of drop-zones
|
|
65
68
|
const typeToDropZones = new Map();
|
|
@@ -151,8 +154,7 @@ function handleDraggedEntered(e) {
|
|
|
151
154
|
source: SOURCES.POINTER
|
|
152
155
|
});
|
|
153
156
|
}
|
|
154
|
-
const {index
|
|
155
|
-
const shadowElIdx = isProximityBased && index === e.currentTarget.children.length - 1 ? index + 1 : index;
|
|
157
|
+
const {index: shadowElIdx} = e.detail.indexObj;
|
|
156
158
|
shadowElDropZone = e.currentTarget;
|
|
157
159
|
items.splice(shadowElIdx, 0, shadowElData);
|
|
158
160
|
dispatchConsiderEvent(e.currentTarget, items, {trigger: TRIGGERS.DRAGGED_ENTERED, id: draggedElData[ITEM_ID_KEY], source: SOURCES.POINTER});
|
|
@@ -310,16 +312,14 @@ function scheduleDZForRemovalAfterDrop(dz, destroy) {
|
|
|
310
312
|
}
|
|
311
313
|
/* cleanup */
|
|
312
314
|
function cleanupPostDrop() {
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
printDebug(() => ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop]);
|
|
317
|
-
scheduledForRemovalAfterDrop.forEach(({dz, destroy}) => {
|
|
318
|
-
destroy();
|
|
319
|
-
dz.remove();
|
|
320
|
-
});
|
|
321
|
-
scheduledForRemovalAfterDrop = [];
|
|
315
|
+
// Remove the temporary elements that were kept in the DOM during the drag
|
|
316
|
+
if (draggedEl && draggedEl.remove) {
|
|
317
|
+
draggedEl.remove();
|
|
322
318
|
}
|
|
319
|
+
if (originalDragTarget && originalDragTarget.remove) {
|
|
320
|
+
originalDragTarget.remove();
|
|
321
|
+
}
|
|
322
|
+
|
|
323
323
|
draggedEl = undefined;
|
|
324
324
|
originalDragTarget = undefined;
|
|
325
325
|
draggedElData = undefined;
|
|
@@ -334,6 +334,19 @@ function cleanupPostDrop() {
|
|
|
334
334
|
finalizingPreviousDrag = false;
|
|
335
335
|
unlockOriginDzMinDimensions = undefined;
|
|
336
336
|
isDraggedOutsideOfAnyDz = false;
|
|
337
|
+
if (touchDragHoldTimer) {
|
|
338
|
+
clearTimeout(touchDragHoldTimer);
|
|
339
|
+
}
|
|
340
|
+
touchDragHoldTimer = undefined;
|
|
341
|
+
touchHoldElapsed = false;
|
|
342
|
+
if (scheduledForRemovalAfterDrop.length) {
|
|
343
|
+
printDebug(() => ["will destroy zones that were removed during drag", scheduledForRemovalAfterDrop]);
|
|
344
|
+
scheduledForRemovalAfterDrop.forEach(({dz, destroy}) => {
|
|
345
|
+
destroy();
|
|
346
|
+
dz.remove();
|
|
347
|
+
});
|
|
348
|
+
scheduledForRemovalAfterDrop = [];
|
|
349
|
+
}
|
|
337
350
|
}
|
|
338
351
|
|
|
339
352
|
export function dndzone(node, options) {
|
|
@@ -349,7 +362,8 @@ export function dndzone(node, options) {
|
|
|
349
362
|
dropTargetClasses: [],
|
|
350
363
|
transformDraggedElement: () => {},
|
|
351
364
|
centreDraggedOnCursor: false,
|
|
352
|
-
dropAnimationDisabled: false
|
|
365
|
+
dropAnimationDisabled: false,
|
|
366
|
+
delayTouchStartMs: 0
|
|
353
367
|
};
|
|
354
368
|
printDebug(() => [`dndzone good to go options: ${toString(options)}, config: ${toString(config)}`, {node}]);
|
|
355
369
|
let elToIdx = new Map();
|
|
@@ -365,6 +379,11 @@ export function dndzone(node, options) {
|
|
|
365
379
|
window.removeEventListener("touchmove", handleMouseMoveMaybeDragStart);
|
|
366
380
|
window.removeEventListener("mouseup", handleFalseAlarm);
|
|
367
381
|
window.removeEventListener("touchend", handleFalseAlarm);
|
|
382
|
+
if (touchDragHoldTimer) {
|
|
383
|
+
clearTimeout(touchDragHoldTimer);
|
|
384
|
+
touchDragHoldTimer = undefined;
|
|
385
|
+
touchHoldElapsed = false;
|
|
386
|
+
}
|
|
368
387
|
}
|
|
369
388
|
function handleFalseAlarm(e) {
|
|
370
389
|
removeMaybeListeners();
|
|
@@ -384,8 +403,30 @@ export function dndzone(node, options) {
|
|
|
384
403
|
}
|
|
385
404
|
|
|
386
405
|
function handleMouseMoveMaybeDragStart(e) {
|
|
406
|
+
const isTouch = !!e.touches;
|
|
407
|
+
const c = isTouch ? e.touches[0] : e;
|
|
408
|
+
// If touch drag delay is configured and not elapsed yet, allow scrolling until either
|
|
409
|
+
// the delay elapses (timer will call handleDragStart) or the user moves significantly,
|
|
410
|
+
// in which case we cancel the potential drag and let the interaction be a scroll.
|
|
411
|
+
if (isTouch && config.delayTouchStartMs > 0 && !touchHoldElapsed) {
|
|
412
|
+
currentMousePosition = {x: c.clientX, y: c.clientY};
|
|
413
|
+
if (
|
|
414
|
+
Math.abs(currentMousePosition.x - dragStartMousePosition.x) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX ||
|
|
415
|
+
Math.abs(currentMousePosition.y - dragStartMousePosition.y) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX
|
|
416
|
+
) {
|
|
417
|
+
// User started scrolling, cancel drag attempt.
|
|
418
|
+
if (touchDragHoldTimer) {
|
|
419
|
+
clearTimeout(touchDragHoldTimer);
|
|
420
|
+
touchDragHoldTimer = undefined;
|
|
421
|
+
}
|
|
422
|
+
handleFalseAlarm(e);
|
|
423
|
+
}
|
|
424
|
+
return; // Do not preventDefault so scrolling works.
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// legacy / post-delay path – block scrolling and maybe start drag
|
|
387
428
|
e.preventDefault();
|
|
388
|
-
|
|
429
|
+
|
|
389
430
|
currentMousePosition = {x: c.clientX, y: c.clientY};
|
|
390
431
|
if (
|
|
391
432
|
Math.abs(currentMousePosition.x - dragStartMousePosition.x) >= MIN_MOVEMENT_BEFORE_DRAG_START_PX ||
|
|
@@ -410,12 +451,30 @@ export function dndzone(node, options) {
|
|
|
410
451
|
printDebug(() => "cannot start a new drag before finalizing previous one");
|
|
411
452
|
return;
|
|
412
453
|
}
|
|
413
|
-
e.
|
|
454
|
+
const isTouchStart = !!e.touches;
|
|
455
|
+
const useDelay = isTouchStart && config.delayTouchStartMs > 0;
|
|
456
|
+
|
|
457
|
+
if (!useDelay) {
|
|
458
|
+
e.preventDefault();
|
|
459
|
+
}
|
|
414
460
|
e.stopPropagation();
|
|
415
|
-
|
|
461
|
+
|
|
462
|
+
const c = isTouchStart ? e.touches[0] : e;
|
|
416
463
|
dragStartMousePosition = {x: c.clientX, y: c.clientY};
|
|
417
464
|
currentMousePosition = {...dragStartMousePosition};
|
|
418
465
|
originalDragTarget = e.currentTarget;
|
|
466
|
+
|
|
467
|
+
if (useDelay) {
|
|
468
|
+
touchHoldElapsed = false;
|
|
469
|
+
touchDragHoldTimer = window.setTimeout(() => {
|
|
470
|
+
// If the finger is still down and no false-alarm happened
|
|
471
|
+
if (!originalDragTarget) return;
|
|
472
|
+
touchHoldElapsed = true;
|
|
473
|
+
removeMaybeListeners();
|
|
474
|
+
handleDragStart();
|
|
475
|
+
}, config.delayTouchStartMs);
|
|
476
|
+
}
|
|
477
|
+
|
|
419
478
|
addMaybeListeners();
|
|
420
479
|
}
|
|
421
480
|
|
|
@@ -487,9 +546,19 @@ export function dndzone(node, options) {
|
|
|
487
546
|
dropTargetClasses = [],
|
|
488
547
|
transformDraggedElement = () => {},
|
|
489
548
|
centreDraggedOnCursor = false,
|
|
490
|
-
dropAnimationDisabled = false
|
|
549
|
+
dropAnimationDisabled = false,
|
|
550
|
+
delayTouchStart: delayTouchStartOpt = false
|
|
491
551
|
}) {
|
|
492
552
|
config.dropAnimationDurationMs = dropAnimationDurationMs;
|
|
553
|
+
|
|
554
|
+
let effectiveDelayMs = 0;
|
|
555
|
+
if (delayTouchStartOpt === true) {
|
|
556
|
+
effectiveDelayMs = DEFAULT_TOUCH_DELAY_MS;
|
|
557
|
+
} else if (typeof delayTouchStartOpt === "number" && isFinite(delayTouchStartOpt) && delayTouchStartOpt >= 0) {
|
|
558
|
+
effectiveDelayMs = delayTouchStartOpt;
|
|
559
|
+
}
|
|
560
|
+
config.delayTouchStartMs = effectiveDelayMs;
|
|
561
|
+
|
|
493
562
|
if (config.type && newType !== config.type) {
|
|
494
563
|
unregisterDropZone(node, config.type);
|
|
495
564
|
}
|