react-dnd-touch-backend
Advanced tools
Comparing version 0.8.1 to 0.8.2
1102
dist/Touch.js
@@ -6,62 +6,55 @@ /** | ||
'use strict'; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
exports.default = createTouchBackend; | ||
exports.TouchBackend = void 0; | ||
var _invariant = _interopRequireDefault(require("invariant")); | ||
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } | ||
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; var ownKeys = Object.keys(source); if (typeof Object.getOwnPropertySymbols === 'function') { ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function (sym) { return Object.getOwnPropertyDescriptor(source, sym).enumerable; })); } ownKeys.forEach(function (key) { _defineProperty(target, key, source[key]); }); } return target; } | ||
function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } | ||
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } | ||
function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } | ||
function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } | ||
var __assign = (this && this.__assign) || function () { | ||
__assign = Object.assign || function(t) { | ||
for (var s, i = 1, n = arguments.length; i < n; i++) { | ||
s = arguments[i]; | ||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) | ||
t[p] = s[p]; | ||
} | ||
return t; | ||
}; | ||
return __assign.apply(this, arguments); | ||
}; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
var _a; | ||
var invariant = require("invariant"); | ||
function isTouchEvent(e) { | ||
return !!e.targetTouches; | ||
} | ||
function getEventClientTouchOffset(e) { | ||
if (e.targetTouches.length === 1) { | ||
return getEventClientOffset(e.targetTouches[0]); | ||
} | ||
if (e.targetTouches.length === 1) { | ||
return getEventClientOffset(e.targetTouches[0]); | ||
} | ||
} | ||
function getEventClientOffset(e) { | ||
if (e.targetTouches) { | ||
return getEventClientTouchOffset(e); | ||
} else { | ||
return { | ||
x: e.clientX, | ||
y: e.clientY | ||
}; | ||
} | ||
} // Used for MouseEvent.buttons (note the s on the end). | ||
if (isTouchEvent(e)) { | ||
return getEventClientTouchOffset(e); | ||
} | ||
else { | ||
return { | ||
x: e.clientX, | ||
y: e.clientY | ||
}; | ||
} | ||
} | ||
// Used for MouseEvent.buttons (note the s on the end). | ||
var MouseButtons = { | ||
Left: 1, | ||
Right: 2, | ||
Center: 4 // Used for e.button (note the lack of an s on the end). | ||
Left: 1, | ||
Right: 2, | ||
Center: 4 | ||
}; | ||
// Used for e.button (note the lack of an s on the end). | ||
var MouseButton = { | ||
Left: 0, | ||
Center: 1, | ||
Right: 2 | ||
/** | ||
* Only touch events and mouse events where the left button is pressed should initiate a drag. | ||
* @param {MouseEvent | TouchEvent} e The event | ||
*/ | ||
Left: 0, | ||
Center: 1, | ||
Right: 2 | ||
}; | ||
/** | ||
* Only touch events and mouse events where the left button is pressed should initiate a drag. | ||
* @param {MouseEvent | TouchEvent} e The event | ||
*/ | ||
function eventShouldStartDrag(e) { | ||
// For touch events, button will be undefined. If e.button is defined, | ||
// then it should be MouseButton.Left. | ||
return e.button === undefined || e.button === MouseButton.Left; | ||
// For touch events, button will be undefined. If e.button is defined, | ||
// then it should be MouseButton.Left. | ||
return e.button === undefined || e.button === MouseButton.Left; | ||
} | ||
@@ -74,607 +67,450 @@ /** | ||
*/ | ||
function eventShouldEndDrag(e) { | ||
// Touch events will have buttons be undefined, while mouse events will have e.buttons's left button | ||
// bit field unset if the left mouse button has been released | ||
return e.buttons === undefined || (e.buttons & MouseButtons.Left) === 0; | ||
} // Polyfill for document.elementsFromPoint | ||
var elementsFromPoint = (typeof document !== 'undefined' && document.elementsFromPoint || function (x, y) { | ||
if (document.msElementsFromPoint) { | ||
// msElementsFromPoint is much faster but returns a node-list, so convert it to an array | ||
var msElements = document.msElementsFromPoint(x, y); | ||
return msElements && Array.prototype.slice.call(msElements, 0); | ||
} | ||
var elements = [], | ||
previousPointerEvents = [], | ||
current, | ||
i, | ||
d; // get all elements via elementFromPoint, and remove them from hit-testing in order | ||
while ((current = document.elementFromPoint(x, y)) && elements.indexOf(current) === -1 && current !== null) { | ||
// push the element and its current style | ||
elements.push(current); | ||
previousPointerEvents.push({ | ||
value: current.style.getPropertyValue('pointer-events'), | ||
priority: current.style.getPropertyPriority('pointer-events') | ||
}); // add "pointer-events: none", to get to the underlying element | ||
current.style.setProperty('pointer-events', 'none', 'important'); | ||
} // restore the previous pointer-events values | ||
for (i = previousPointerEvents.length; d = previousPointerEvents[--i];) { | ||
elements[i].style.setProperty('pointer-events', d.value ? d.value : '', d.priority); | ||
} // return our results | ||
return elements; | ||
}).bind(typeof document !== 'undefined' ? document : null); | ||
var supportsPassive = function () { | ||
// simular to jQuery's test | ||
var supported = false; | ||
try { | ||
addEventListener('test', null, Object.defineProperty({}, 'passive', { | ||
get: function get() { | ||
supported = true; | ||
} | ||
})); | ||
} catch (e) {} | ||
return supported; | ||
}(); | ||
var ELEMENT_NODE = 1; | ||
function getNodeClientOffset(node) { | ||
var el = node.nodeType === ELEMENT_NODE ? node : node.parentElement; | ||
if (!el) { | ||
return null; | ||
} | ||
var _el$getBoundingClient = el.getBoundingClientRect(), | ||
top = _el$getBoundingClient.top, | ||
left = _el$getBoundingClient.left; | ||
return { | ||
x: left, | ||
y: top | ||
}; | ||
// Touch events will have buttons be undefined, while mouse events will have e.buttons's left button | ||
// bit field unset if the left mouse button has been released | ||
return e.buttons === undefined || (e.buttons & MouseButtons.Left) === 0; | ||
} | ||
var eventNames = { | ||
mouse: { | ||
start: 'mousedown', | ||
move: 'mousemove', | ||
end: 'mouseup', | ||
contextmenu: 'contextmenu' | ||
}, | ||
touch: { | ||
start: 'touchstart', | ||
move: 'touchmove', | ||
end: 'touchend' | ||
}, | ||
keyboard: { | ||
keydown: 'keydown' | ||
} | ||
}; | ||
var TouchBackend = | ||
/*#__PURE__*/ | ||
function () { | ||
function TouchBackend(manager) { | ||
var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; | ||
_classCallCheck(this, TouchBackend); | ||
options.delayTouchStart = options.delayTouchStart || options.delay; | ||
options = _objectSpread({ | ||
enableTouchEvents: true, | ||
enableMouseEvents: false, | ||
enableKeyboardEvents: false, | ||
ignoreContextMenu: false, | ||
enableHoverOutsideTarget: false, | ||
delayTouchStart: 0, | ||
delayMouseStart: 0, | ||
touchSlop: 0, | ||
scrollAngleRanges: undefined | ||
}, options); | ||
this.actions = manager.getActions(); | ||
this.monitor = manager.getMonitor(); | ||
this.registry = manager.getRegistry(); | ||
this.enableKeyboardEvents = options.enableKeyboardEvents; | ||
this.enableMouseEvents = options.enableMouseEvents; | ||
this.delayTouchStart = options.delayTouchStart; | ||
this.delayMouseStart = options.delayMouseStart; | ||
this.ignoreContextMenu = options.ignoreContextMenu; | ||
this.touchSlop = options.touchSlop; | ||
this.scrollAngleRanges = options.scrollAngleRanges; | ||
this.enableHoverOutsideTarget = options.enableHoverOutsideTarget; | ||
this.sourceNodes = {}; | ||
this.sourceNodeOptions = {}; | ||
this.sourcePreviewNodes = {}; | ||
this.sourcePreviewNodeOptions = {}; | ||
this.targetNodes = {}; | ||
this.targetNodeOptions = {}; | ||
this.listenerTypes = []; | ||
this._mouseClientOffset = {}; | ||
this._isScrolling = false; | ||
if (options.enableMouseEvents) { | ||
this.listenerTypes.push('mouse'); | ||
} | ||
if (options.enableTouchEvents) { | ||
this.listenerTypes.push('touch'); | ||
} | ||
if (options.enableKeyboardEvents) { | ||
this.listenerTypes.push('keyboard'); | ||
} | ||
if (options.getDropTargetElementsAtPoint) { | ||
this.getDropTargetElementsAtPoint = options.getDropTargetElementsAtPoint; | ||
} | ||
this.getSourceClientOffset = this.getSourceClientOffset.bind(this); | ||
this.handleTopMoveStart = this.handleTopMoveStart.bind(this); | ||
this.handleTopMoveStartDelay = this.handleTopMoveStartDelay.bind(this); | ||
this.handleTopMoveStartCapture = this.handleTopMoveStartCapture.bind(this); | ||
this.handleTopMoveCapture = this.handleTopMoveCapture.bind(this); | ||
this.handleTopMove = this.handleTopMove.bind(this); | ||
this.handleTopMoveEndCapture = this.handleTopMoveEndCapture.bind(this); | ||
this.handleCancelOnEscape = this.handleCancelOnEscape.bind(this); | ||
} | ||
_createClass(TouchBackend, [{ | ||
key: "setup", | ||
value: function setup() { | ||
if (typeof window === 'undefined') { | ||
return; | ||
} | ||
(0, _invariant.default)(!this.constructor.isSetUp, 'Cannot have two Touch backends at the same time.'); | ||
this.constructor.isSetUp = true; | ||
this.addEventListener(window, 'start', this.getTopMoveStartHandler()); | ||
this.addEventListener(window, 'start', this.handleTopMoveStartCapture, true); | ||
this.addEventListener(window, 'move', this.handleTopMove); | ||
this.addEventListener(window, 'move', this.handleTopMoveCapture, true); | ||
this.addEventListener(window, 'end', this.handleTopMoveEndCapture, true); | ||
if (this.enableMouseEvents && !this.ignoreContextMenu) { | ||
this.addEventListener(window, 'contextmenu', this.handleTopMoveEndCapture); | ||
} | ||
if (this.enableKeyboardEvents) { | ||
this.addEventListener(window, 'keydown', this.handleCancelOnEscape, true); | ||
} | ||
} | ||
}, { | ||
key: "teardown", | ||
value: function teardown() { | ||
if (typeof window === 'undefined') { | ||
return; | ||
} | ||
this.constructor.isSetUp = false; | ||
this._mouseClientOffset = {}; | ||
this.removeEventListener(window, 'start', this.handleTopMoveStartCapture, true); | ||
this.removeEventListener(window, 'start', this.handleTopMoveStart); | ||
this.removeEventListener(window, 'move', this.handleTopMoveCapture, true); | ||
this.removeEventListener(window, 'move', this.handleTopMove); | ||
this.removeEventListener(window, 'end', this.handleTopMoveEndCapture, true); | ||
if (this.enableMouseEvents && !this.ignoreContextMenu) { | ||
this.removeEventListener(window, 'contextmenu', this.handleTopMoveEndCapture); | ||
} | ||
if (this.enableKeyboardEvents) { | ||
this.removeEventListener(window, 'keydown', this.handleCancelOnEscape, true); | ||
} | ||
this.uninstallSourceNodeRemovalObserver(); | ||
} | ||
}, { | ||
key: "addEventListener", | ||
value: function addEventListener(subject, event, handler, capture) { | ||
var options = supportsPassive ? { | ||
capture: capture, | ||
passive: false | ||
} : capture; | ||
this.listenerTypes.forEach(function (listenerType) { | ||
var evt = eventNames[listenerType][event]; | ||
if (evt) { | ||
subject.addEventListener(evt, handler, options); | ||
// Polyfill for document.elementsFromPoint | ||
var elementsFromPoint = ((typeof document !== 'undefined' && document.elementsFromPoint) || | ||
function (x, y) { | ||
if (document.msElementsFromPoint) { | ||
// msElementsFromPoint is much faster but returns a node-list, so convert it to an array | ||
var msElements = document.msElementsFromPoint(x, y); | ||
return msElements && Array.prototype.slice.call(msElements, 0); | ||
} | ||
}); | ||
} | ||
}, { | ||
key: "removeEventListener", | ||
value: function removeEventListener(subject, event, handler, capture) { | ||
var options = supportsPassive ? { | ||
capture: capture, | ||
passive: false | ||
} : capture; | ||
this.listenerTypes.forEach(function (listenerType) { | ||
var evt = eventNames[listenerType][event]; | ||
if (evt) { | ||
subject.removeEventListener(evt, handler, options); | ||
var elements = [], previousPointerEvents = [], current, i, d; | ||
// get all elements via elementFromPoint, and remove them from hit-testing in order | ||
while ((current = document.elementFromPoint(x, y)) && | ||
elements.indexOf(current) === -1 && | ||
current !== null) { | ||
// push the element and its current style | ||
elements.push(current); | ||
previousPointerEvents.push({ | ||
value: current.style.getPropertyValue('pointer-events'), | ||
priority: current.style.getPropertyPriority('pointer-events') | ||
}); | ||
// add "pointer-events: none", to get to the underlying element | ||
current.style.setProperty('pointer-events', 'none', 'important'); | ||
} | ||
}); | ||
// restore the previous pointer-events values | ||
for (i = previousPointerEvents.length; (d = previousPointerEvents[--i]);) { | ||
elements[i].style.setProperty('pointer-events', d.value ? d.value : '', d.priority); | ||
} | ||
// return our results | ||
return elements; | ||
}).bind(typeof document !== 'undefined' ? document : null); | ||
var supportsPassive = (function () { | ||
// simular to jQuery's test | ||
var supported = false; | ||
try { | ||
addEventListener('test', null, Object.defineProperty({}, 'passive', { | ||
get: function () { | ||
supported = true; | ||
} | ||
})); | ||
} | ||
}, { | ||
key: "connectDragSource", | ||
value: function connectDragSource(sourceId, node, options) { | ||
var _this = this; | ||
var handleMoveStart = this.handleMoveStart.bind(this, sourceId); | ||
this.sourceNodes[sourceId] = node; | ||
this.addEventListener(node, 'start', handleMoveStart); | ||
return function () { | ||
delete _this.sourceNodes[sourceId]; | ||
_this.removeEventListener(node, 'start', handleMoveStart); | ||
}; | ||
catch (e) { } | ||
return supported; | ||
})(); | ||
var ELEMENT_NODE = 1; | ||
function getNodeClientOffset(node) { | ||
var el = node.nodeType === ELEMENT_NODE ? node : node.parentElement; | ||
if (!el) { | ||
return null; | ||
} | ||
}, { | ||
key: "connectDragPreview", | ||
value: function connectDragPreview(sourceId, node, options) { | ||
var _this2 = this; | ||
this.sourcePreviewNodeOptions[sourceId] = options; | ||
this.sourcePreviewNodes[sourceId] = node; | ||
return function () { | ||
delete _this2.sourcePreviewNodes[sourceId]; | ||
delete _this2.sourcePreviewNodeOptions[sourceId]; | ||
}; | ||
} | ||
}, { | ||
key: "connectDropTarget", | ||
value: function connectDropTarget(targetId, node) { | ||
var _this3 = this; | ||
var handleMove = function handleMove(e) { | ||
var coords; | ||
if (!_this3.monitor.isDragging()) { | ||
return; | ||
var _a = el.getBoundingClientRect(), top = _a.top, left = _a.left; | ||
return { x: left, y: top }; | ||
} | ||
var eventNames = (_a = {}, | ||
_a["mouse" /* mouse */] = { | ||
start: 'mousedown', | ||
move: 'mousemove', | ||
end: 'mouseup', | ||
contextmenu: 'contextmenu' | ||
}, | ||
_a["touch" /* touch */] = { | ||
start: 'touchstart', | ||
move: 'touchmove', | ||
end: 'touchend' | ||
}, | ||
_a["keyboard" /* keyboard */] = { | ||
keydown: 'keydown' | ||
}, | ||
_a); | ||
var TouchBackend = /** @class */ (function () { | ||
function TouchBackend(manager, options) { | ||
var _this = this; | ||
if (options === void 0) { options = {}; } | ||
this.getSourceClientOffset = function (sourceId) { | ||
return getNodeClientOffset(_this.sourceNodes[sourceId]); | ||
}; | ||
this.handleTopMoveStartCapture = function (e) { | ||
if (!eventShouldStartDrag(e)) { | ||
return; | ||
} | ||
_this.moveStartSourceIds = []; | ||
}; | ||
this.handleMoveStart = function (sourceId) { | ||
// Just because we received an event doesn't necessarily mean we need to collect drag sources. | ||
// We only collect start collecting drag sources on touch and left mouse events. | ||
if (Array.isArray(_this.moveStartSourceIds)) { | ||
_this.moveStartSourceIds.unshift(sourceId); | ||
} | ||
}; | ||
this.handleTopMoveStart = function (e) { | ||
if (!eventShouldStartDrag(e)) { | ||
return; | ||
} | ||
// Don't prematurely preventDefault() here since it might: | ||
// 1. Mess up scrolling | ||
// 2. Mess up long tap (which brings up context menu) | ||
// 3. If there's an anchor link as a child, tap won't be triggered on link | ||
var clientOffset = getEventClientOffset(e); | ||
if (clientOffset) { | ||
_this._mouseClientOffset = clientOffset; | ||
} | ||
_this.waitingForDelay = false; | ||
}; | ||
this.handleTopMoveStartDelay = function (e) { | ||
if (!eventShouldStartDrag(e)) { | ||
return; | ||
} | ||
var delay = e.type === eventNames.touch.start | ||
? _this.delayTouchStart | ||
: _this.delayMouseStart; | ||
_this.timeout = setTimeout(_this.handleTopMoveStart.bind(_this, e), delay); | ||
_this.waitingForDelay = true; | ||
}; | ||
this.handleTopMoveCapture = function () { | ||
_this.dragOverTargetIds = []; | ||
}; | ||
this.handleMove = function (_, targetId) { | ||
_this.dragOverTargetIds.unshift(targetId); | ||
}; | ||
this.handleTopMove = function (e) { | ||
clearTimeout(_this.timeout); | ||
if (_this.waitingForDelay) { | ||
return; | ||
} | ||
var _a = _this, moveStartSourceIds = _a.moveStartSourceIds, dragOverTargetIds = _a.dragOverTargetIds, enableHoverOutsideTarget = _a.enableHoverOutsideTarget; | ||
var clientOffset = getEventClientOffset(e); | ||
if (!clientOffset) { | ||
return; | ||
} | ||
// If the touch move started as a scroll, or is is between the scroll angles | ||
if (_this._isScrolling || | ||
(!_this.monitor.isDragging() && | ||
inAngleRanges(_this._mouseClientOffset.x, _this._mouseClientOffset.y, clientOffset.x, clientOffset.y, _this.scrollAngleRanges))) { | ||
_this._isScrolling = true; | ||
return; | ||
} | ||
// If we're not dragging and we've moved a little, that counts as a drag start | ||
if (!_this.monitor.isDragging() && | ||
_this._mouseClientOffset.hasOwnProperty('x') && | ||
moveStartSourceIds && | ||
distance(_this._mouseClientOffset.x, _this._mouseClientOffset.y, clientOffset.x, clientOffset.y) > (_this.touchSlop ? _this.touchSlop : 0)) { | ||
_this.moveStartSourceIds = null; | ||
_this.actions.beginDrag(moveStartSourceIds, { | ||
clientOffset: _this._mouseClientOffset, | ||
getSourceClientOffset: _this.getSourceClientOffset, | ||
publishSource: false | ||
}); | ||
} | ||
if (!_this.monitor.isDragging()) { | ||
return; | ||
} | ||
var sourceNode = _this.sourceNodes[_this.monitor.getSourceId()]; | ||
_this.installSourceNodeRemovalObserver(sourceNode); | ||
_this.actions.publishDragSource(); | ||
e.preventDefault(); | ||
// Get the node elements of the hovered DropTargets | ||
var dragOverTargetNodes = dragOverTargetIds.map(function (key) { return _this.targetNodes[key]; }); | ||
// Get the a ordered list of nodes that are touched by | ||
var elementsAtPoint = _this.getDropTargetElementsAtPoint | ||
? _this.getDropTargetElementsAtPoint(clientOffset.x, clientOffset.y, dragOverTargetNodes) | ||
: elementsFromPoint(clientOffset.x, clientOffset.y); | ||
// Extend list with parents that are not receiving elementsFromPoint events (size 0 elements and svg groups) | ||
var elementsAtPointExtended = []; | ||
for (var nodeId in elementsAtPoint) { | ||
if (!elementsAtPoint.hasOwnProperty(nodeId)) { | ||
continue; | ||
} | ||
var currentNode = elementsAtPoint[nodeId]; | ||
elementsAtPointExtended.push(currentNode); | ||
while (currentNode) { | ||
currentNode = currentNode.parentElement; | ||
if (elementsAtPointExtended.indexOf(currentNode) === -1) { | ||
elementsAtPointExtended.push(currentNode); | ||
} | ||
} | ||
} | ||
var orderedDragOverTargetIds = elementsAtPointExtended | ||
// Filter off nodes that arent a hovered DropTargets nodes | ||
.filter(function (node) { return dragOverTargetNodes.indexOf(node) > -1; }) | ||
// Map back the nodes elements to targetIds | ||
.map(function (node) { | ||
for (var targetId in _this.targetNodes) { | ||
if (node === _this.targetNodes[targetId]) { | ||
return targetId; | ||
} | ||
} | ||
return null; | ||
}) | ||
// Filter off possible null rows | ||
.filter(function (node) { return !!node; }) | ||
.filter(function (id, index, ids) { return ids.indexOf(id) === index; }); | ||
// Invoke hover for drop targets when source node is still over and pointer is outside | ||
if (enableHoverOutsideTarget) { | ||
for (var targetId in _this.targetNodes) { | ||
if (_this.targetNodes[targetId] && | ||
_this.targetNodes[targetId].contains(sourceNode) && | ||
orderedDragOverTargetIds.indexOf(targetId) === -1) { | ||
orderedDragOverTargetIds.unshift(targetId); | ||
break; | ||
} | ||
} | ||
} | ||
// Reverse order because dnd-core reverse it before calling the DropTarget drop methods | ||
orderedDragOverTargetIds.reverse(); | ||
_this.actions.hover(orderedDragOverTargetIds, { | ||
clientOffset: clientOffset | ||
}); | ||
}; | ||
this.handleTopMoveEndCapture = function (e) { | ||
_this._isScrolling = false; | ||
if (!eventShouldEndDrag(e)) { | ||
return; | ||
} | ||
if (!_this.monitor.isDragging() || _this.monitor.didDrop()) { | ||
_this.moveStartSourceIds = null; | ||
return; | ||
} | ||
e.preventDefault(); | ||
_this._mouseClientOffset = {}; | ||
_this.uninstallSourceNodeRemovalObserver(); | ||
_this.actions.drop(); | ||
_this.actions.endDrag(); | ||
}; | ||
this.handleCancelOnEscape = function (e) { | ||
if (e.key === 'Escape' && _this.monitor.isDragging()) { | ||
_this._mouseClientOffset = {}; | ||
_this.uninstallSourceNodeRemovalObserver(); | ||
_this.actions.endDrag(); | ||
} | ||
}; | ||
options.delayTouchStart = options.delayTouchStart || options.delay; | ||
options = __assign({ enableTouchEvents: true, enableMouseEvents: false, enableKeyboardEvents: false, ignoreContextMenu: false, enableHoverOutsideTarget: false, delayTouchStart: 0, delayMouseStart: 0, touchSlop: 0, scrollAngleRanges: undefined }, options); | ||
this.actions = manager.getActions(); | ||
this.monitor = manager.getMonitor(); | ||
this.registry = manager.getRegistry(); | ||
this.enableKeyboardEvents = options.enableKeyboardEvents; | ||
this.enableMouseEvents = options.enableMouseEvents; | ||
this.delayTouchStart = options.delayTouchStart; | ||
this.delayMouseStart = options.delayMouseStart; | ||
this.ignoreContextMenu = options.ignoreContextMenu; | ||
this.touchSlop = options.touchSlop; | ||
this.scrollAngleRanges = options.scrollAngleRanges; | ||
this.enableHoverOutsideTarget = options.enableHoverOutsideTarget; | ||
this.sourceNodes = {}; | ||
this.sourceNodeOptions = {}; | ||
this.sourcePreviewNodes = {}; | ||
this.sourcePreviewNodeOptions = {}; | ||
this.targetNodes = {}; | ||
this.targetNodeOptions = {}; | ||
this.listenerTypes = []; | ||
this._mouseClientOffset = {}; | ||
this._isScrolling = false; | ||
if (options.enableMouseEvents) { | ||
this.listenerTypes.push("mouse" /* mouse */); | ||
} | ||
/** | ||
* Grab the coordinates for the current mouse/touch position | ||
*/ | ||
switch (e.type) { | ||
case eventNames.mouse.move: | ||
coords = { | ||
x: e.clientX, | ||
y: e.clientY | ||
}; | ||
break; | ||
case eventNames.touch.move: | ||
coords = { | ||
x: e.touches[0].clientX, | ||
y: e.touches[0].clientY | ||
}; | ||
break; | ||
if (options.enableTouchEvents) { | ||
this.listenerTypes.push("touch" /* touch */); | ||
} | ||
/** | ||
* Use the coordinates to grab the element the drag ended on. | ||
* If the element is the same as the target node (or any of it's children) then we have hit a drop target and can handle the move. | ||
*/ | ||
var droppedOn = document.elementFromPoint(coords.x, coords.y); | ||
var childMatch = node.contains(droppedOn); | ||
if (droppedOn === node || childMatch) { | ||
return _this3.handleMove(e, targetId); | ||
if (options.enableKeyboardEvents) { | ||
this.listenerTypes.push("keyboard" /* keyboard */); | ||
} | ||
}; | ||
/** | ||
* Attaching the event listener to the body so that touchmove will work while dragging over multiple target elements. | ||
*/ | ||
this.addEventListener(document.querySelector('body'), 'move', handleMove); | ||
this.targetNodes[targetId] = node; | ||
return function () { | ||
delete _this3.targetNodes[targetId]; | ||
_this3.removeEventListener(document.querySelector('body'), 'move', handleMove); | ||
}; | ||
if (options.getDropTargetElementsAtPoint) { | ||
this.getDropTargetElementsAtPoint = options.getDropTargetElementsAtPoint; | ||
} | ||
} | ||
}, { | ||
key: "getSourceClientOffset", | ||
value: function getSourceClientOffset(sourceId) { | ||
return getNodeClientOffset(this.sourceNodes[sourceId]); | ||
} | ||
}, { | ||
key: "handleTopMoveStartCapture", | ||
value: function handleTopMoveStartCapture(e) { | ||
if (!eventShouldStartDrag(e)) { | ||
return; | ||
} | ||
this.moveStartSourceIds = []; | ||
} | ||
}, { | ||
key: "handleMoveStart", | ||
value: function handleMoveStart(sourceId) { | ||
// Just because we received an event doesn't necessarily mean we need to collect drag sources. | ||
// We only collect start collecting drag sources on touch and left mouse events. | ||
if (Array.isArray(this.moveStartSourceIds)) { | ||
this.moveStartSourceIds.unshift(sourceId); | ||
} | ||
} | ||
}, { | ||
key: "getTopMoveStartHandler", | ||
value: function getTopMoveStartHandler() { | ||
if (!this.delayTouchStart && !this.delayMouseStart) { | ||
return this.handleTopMoveStart; | ||
} | ||
return this.handleTopMoveStartDelay; | ||
} | ||
}, { | ||
key: "handleTopMoveStart", | ||
value: function handleTopMoveStart(e) { | ||
if (!eventShouldStartDrag(e)) { | ||
return; | ||
} // Don't prematurely preventDefault() here since it might: | ||
// 1. Mess up scrolling | ||
// 2. Mess up long tap (which brings up context menu) | ||
// 3. If there's an anchor link as a child, tap won't be triggered on link | ||
var clientOffset = getEventClientOffset(e); | ||
if (clientOffset) { | ||
this._mouseClientOffset = clientOffset; | ||
} | ||
this.waitingForDelay = false; | ||
} | ||
}, { | ||
key: "handleTopMoveStartDelay", | ||
value: function handleTopMoveStartDelay(e) { | ||
if (!eventShouldStartDrag(e)) { | ||
return; | ||
} | ||
var delay = e.type === eventNames.touch.start ? this.delayTouchStart : this.delayMouseStart; | ||
this.timeout = setTimeout(this.handleTopMoveStart.bind(this, e), delay); | ||
this.waitingForDelay = true; | ||
} | ||
}, { | ||
key: "handleTopMoveCapture", | ||
value: function handleTopMoveCapture(e) { | ||
this.dragOverTargetIds = []; | ||
} | ||
}, { | ||
key: "handleMove", | ||
value: function handleMove(e, targetId) { | ||
this.dragOverTargetIds.unshift(targetId); | ||
} | ||
}, { | ||
key: "handleTopMove", | ||
value: function handleTopMove(e) { | ||
var _this4 = this; | ||
clearTimeout(this.timeout); | ||
if (this.waitingForDelay) { | ||
return; | ||
} | ||
var moveStartSourceIds = this.moveStartSourceIds, | ||
dragOverTargetIds = this.dragOverTargetIds, | ||
enableHoverOutsideTarget = this.enableHoverOutsideTarget; | ||
var clientOffset = getEventClientOffset(e); | ||
if (!clientOffset) { | ||
return; | ||
} // If the touch move started as a scroll, or is is between the scroll angles | ||
if (this._isScrolling || !this.monitor.isDragging() && inAngleRanges(this._mouseClientOffset.x, this._mouseClientOffset.y, clientOffset.x, clientOffset.y, this.scrollAngleRanges)) { | ||
this._isScrolling = true; | ||
return; | ||
} // If we're not dragging and we've moved a little, that counts as a drag start | ||
if (!this.monitor.isDragging() && this._mouseClientOffset.hasOwnProperty('x') && moveStartSourceIds && distance(this._mouseClientOffset.x, this._mouseClientOffset.y, clientOffset.x, clientOffset.y) > (this.touchSlop ? this.touchSlop : 0)) { | ||
this.moveStartSourceIds = null; | ||
this.actions.beginDrag(moveStartSourceIds, { | ||
clientOffset: this._mouseClientOffset, | ||
getSourceClientOffset: this.getSourceClientOffset, | ||
publishSource: false | ||
}); | ||
} | ||
if (!this.monitor.isDragging()) { | ||
return; | ||
} | ||
var sourceNode = this.sourceNodes[this.monitor.getSourceId()]; | ||
this.installSourceNodeRemovalObserver(sourceNode); | ||
this.actions.publishDragSource(); | ||
e.preventDefault(); // Get the node elements of the hovered DropTargets | ||
var dragOverTargetNodes = dragOverTargetIds.map(function (key) { | ||
return _this4.targetNodes[key]; | ||
}); // Get the a ordered list of nodes that are touched by | ||
var elementsAtPoint = this.getDropTargetElementsAtPoint ? this.getDropTargetElementsAtPoint(clientOffset.x, clientOffset.y, dragOverTargetNodes) : elementsFromPoint(clientOffset.x, clientOffset.y); // Extend list with parents that are not receiving elementsFromPoint events (size 0 elements and svg groups) | ||
var elementsAtPointExtended = []; | ||
for (var nodeId in elementsAtPoint) { | ||
if (!elementsAtPoint.hasOwnProperty(nodeId)) { | ||
continue; | ||
TouchBackend.prototype.setup = function () { | ||
if (typeof window === 'undefined') { | ||
return; | ||
} | ||
var currentNode = elementsAtPoint[nodeId]; | ||
elementsAtPointExtended.push(currentNode); | ||
while (currentNode) { | ||
currentNode = currentNode.parentElement; | ||
if (elementsAtPointExtended.indexOf(currentNode) === -1) { | ||
elementsAtPointExtended.push(currentNode); | ||
} | ||
invariant(!TouchBackend.isSetUp, 'Cannot have two Touch backends at the same time.'); | ||
TouchBackend.isSetUp = true; | ||
this.addEventListener(window, 'start', this.getTopMoveStartHandler()); | ||
this.addEventListener(window, 'start', this.handleTopMoveStartCapture, true); | ||
this.addEventListener(window, 'move', this.handleTopMove); | ||
this.addEventListener(window, 'move', this.handleTopMoveCapture, true); | ||
this.addEventListener(window, 'end', this.handleTopMoveEndCapture, true); | ||
if (this.enableMouseEvents && !this.ignoreContextMenu) { | ||
this.addEventListener(window, 'contextmenu', this.handleTopMoveEndCapture); | ||
} | ||
} | ||
var orderedDragOverTargetIds = elementsAtPointExtended // Filter off nodes that arent a hovered DropTargets nodes | ||
.filter(function (node) { | ||
return dragOverTargetNodes.indexOf(node) > -1; | ||
}) // Map back the nodes elements to targetIds | ||
.map(function (node) { | ||
for (var targetId in _this4.targetNodes) { | ||
if (node === _this4.targetNodes[targetId]) { | ||
return targetId; | ||
} | ||
if (this.enableKeyboardEvents) { | ||
this.addEventListener(window, 'keydown', this.handleCancelOnEscape, true); | ||
} | ||
return null; | ||
}) // Filter off possible null rows | ||
.filter(function (node) { | ||
return !!node; | ||
}).filter(function (id, index, ids) { | ||
return ids.indexOf(id) === index; | ||
}); // Invoke hover for drop targets when source node is still over and pointer is outside | ||
if (enableHoverOutsideTarget) { | ||
for (var targetId in this.targetNodes) { | ||
if (this.targetNodes[targetId] && this.targetNodes[targetId].contains(sourceNode) && orderedDragOverTargetIds.indexOf(targetId) === -1) { | ||
orderedDragOverTargetIds.unshift(targetId); | ||
break; | ||
} | ||
}; | ||
TouchBackend.prototype.teardown = function () { | ||
if (typeof window === 'undefined') { | ||
return; | ||
} | ||
} // Reverse order because dnd-core reverse it before calling the DropTarget drop methods | ||
orderedDragOverTargetIds.reverse(); | ||
this.actions.hover(orderedDragOverTargetIds, { | ||
clientOffset: clientOffset | ||
}); | ||
} | ||
}, { | ||
key: "handleTopMoveEndCapture", | ||
value: function handleTopMoveEndCapture(e) { | ||
this._isScrolling = false; | ||
if (!eventShouldEndDrag(e)) { | ||
return; | ||
} | ||
if (!this.monitor.isDragging() || this.monitor.didDrop()) { | ||
this.moveStartSourceIds = null; | ||
return; | ||
} | ||
e.preventDefault(); | ||
this._mouseClientOffset = {}; | ||
this.uninstallSourceNodeRemovalObserver(); | ||
this.actions.drop(); | ||
this.actions.endDrag(); | ||
} | ||
}, { | ||
key: "handleCancelOnEscape", | ||
value: function handleCancelOnEscape(e) { | ||
if (e.key === 'Escape' && this.monitor.isDragging()) { | ||
TouchBackend.isSetUp = false; | ||
this._mouseClientOffset = {}; | ||
this.removeEventListener(window, 'start', this.handleTopMoveStartCapture, true); | ||
this.removeEventListener(window, 'start', this.handleTopMoveStart); | ||
this.removeEventListener(window, 'move', this.handleTopMoveCapture, true); | ||
this.removeEventListener(window, 'move', this.handleTopMove); | ||
this.removeEventListener(window, 'end', this.handleTopMoveEndCapture, true); | ||
if (this.enableMouseEvents && !this.ignoreContextMenu) { | ||
this.removeEventListener(window, 'contextmenu', this.handleTopMoveEndCapture); | ||
} | ||
if (this.enableKeyboardEvents) { | ||
this.removeEventListener(window, 'keydown', this.handleCancelOnEscape, true); | ||
} | ||
this.uninstallSourceNodeRemovalObserver(); | ||
this.actions.endDrag(); | ||
} | ||
} | ||
}, { | ||
key: "installSourceNodeRemovalObserver", | ||
value: function installSourceNodeRemovalObserver(node) { | ||
var _this5 = this; | ||
this.uninstallSourceNodeRemovalObserver(); | ||
this.draggedSourceNode = node; | ||
this.draggedSourceNodeRemovalObserver = new window.MutationObserver(function () { | ||
if (!node.parentElement) { | ||
_this5.resurrectSourceNode(); | ||
_this5.uninstallSourceNodeRemovalObserver(); | ||
}; | ||
TouchBackend.prototype.addEventListener = function (subject, event, handler, capture) { | ||
var options = supportsPassive ? { capture: capture, passive: false } : capture; | ||
this.listenerTypes.forEach(function (listenerType) { | ||
var evt = eventNames[listenerType][event]; | ||
if (evt) { | ||
subject.addEventListener(evt, handler, options); | ||
} | ||
}); | ||
}; | ||
TouchBackend.prototype.removeEventListener = function (subject, event, handler, capture) { | ||
var options = supportsPassive ? { capture: capture, passive: false } : capture; | ||
this.listenerTypes.forEach(function (listenerType) { | ||
var evt = eventNames[listenerType][event]; | ||
if (evt) { | ||
subject.removeEventListener(evt, handler, options); | ||
} | ||
}); | ||
}; | ||
TouchBackend.prototype.connectDragSource = function (sourceId, node) { | ||
var _this = this; | ||
var handleMoveStart = this.handleMoveStart.bind(this, sourceId); | ||
this.sourceNodes[sourceId] = node; | ||
this.addEventListener(node, 'start', handleMoveStart); | ||
return function () { | ||
delete _this.sourceNodes[sourceId]; | ||
_this.removeEventListener(node, 'start', handleMoveStart); | ||
}; | ||
}; | ||
TouchBackend.prototype.connectDragPreview = function (sourceId, node, options) { | ||
var _this = this; | ||
this.sourcePreviewNodeOptions[sourceId] = options; | ||
this.sourcePreviewNodes[sourceId] = node; | ||
return function () { | ||
delete _this.sourcePreviewNodes[sourceId]; | ||
delete _this.sourcePreviewNodeOptions[sourceId]; | ||
}; | ||
}; | ||
TouchBackend.prototype.connectDropTarget = function (targetId, node) { | ||
var _this = this; | ||
var handleMove = function (e) { | ||
var coords; | ||
if (!_this.monitor.isDragging()) { | ||
return; | ||
} | ||
/** | ||
* Grab the coordinates for the current mouse/touch position | ||
*/ | ||
switch (e.type) { | ||
case eventNames.mouse.move: | ||
coords = { x: e.clientX, y: e.clientY }; | ||
break; | ||
case eventNames.touch.move: | ||
coords = { x: e.touches[0].clientX, y: e.touches[0].clientY }; | ||
break; | ||
} | ||
/** | ||
* Use the coordinates to grab the element the drag ended on. | ||
* If the element is the same as the target node (or any of it's children) then we have hit a drop target and can handle the move. | ||
*/ | ||
var droppedOn = document.elementFromPoint(coords.x, coords.y); | ||
var childMatch = node.contains(droppedOn); | ||
if (droppedOn === node || childMatch) { | ||
return _this.handleMove(e, targetId); | ||
} | ||
}; | ||
/** | ||
* Attaching the event listener to the body so that touchmove will work while dragging over multiple target elements. | ||
*/ | ||
this.addEventListener(document.body, 'move', handleMove); | ||
this.targetNodes[targetId] = node; | ||
return function () { | ||
delete _this.targetNodes[targetId]; | ||
_this.removeEventListener(document.body, 'move', handleMove); | ||
}; | ||
}; | ||
TouchBackend.prototype.getTopMoveStartHandler = function () { | ||
if (!this.delayTouchStart && !this.delayMouseStart) { | ||
return this.handleTopMoveStart; | ||
} | ||
}); | ||
if (!node || !node.parentElement) { | ||
return; | ||
} | ||
this.draggedSourceNodeRemovalObserver.observe(node.parentElement, { | ||
childList: true | ||
}); | ||
return this.handleTopMoveStartDelay; | ||
}; | ||
TouchBackend.prototype.installSourceNodeRemovalObserver = function (node) { | ||
var _this = this; | ||
this.uninstallSourceNodeRemovalObserver(); | ||
this.draggedSourceNode = node; | ||
this.draggedSourceNodeRemovalObserver = new MutationObserver(function () { | ||
if (!node.parentElement) { | ||
_this.resurrectSourceNode(); | ||
_this.uninstallSourceNodeRemovalObserver(); | ||
} | ||
}); | ||
if (!node || !node.parentElement) { | ||
return; | ||
} | ||
this.draggedSourceNodeRemovalObserver.observe(node.parentElement, { | ||
childList: true | ||
}); | ||
}; | ||
TouchBackend.prototype.resurrectSourceNode = function () { | ||
this.draggedSourceNode.style.display = 'none'; | ||
this.draggedSourceNode.removeAttribute('data-reactid'); | ||
document.body.appendChild(this.draggedSourceNode); | ||
}; | ||
TouchBackend.prototype.uninstallSourceNodeRemovalObserver = function () { | ||
if (this.draggedSourceNodeRemovalObserver) { | ||
this.draggedSourceNodeRemovalObserver.disconnect(); | ||
} | ||
this.draggedSourceNodeRemovalObserver = null; | ||
this.draggedSourceNode = null; | ||
}; | ||
return TouchBackend; | ||
}()); | ||
exports.TouchBackend = TouchBackend; | ||
function isDragDropManager(optionsOrManager) { | ||
return !!optionsOrManager.getMonitor; | ||
} | ||
function createTouchBackend(optionsOrManager) { | ||
var touchBackendFactory = function (manager) { | ||
return new TouchBackend(manager, optionsOrManager); | ||
}; | ||
if (isDragDropManager(optionsOrManager)) { | ||
return touchBackendFactory(optionsOrManager); | ||
} | ||
}, { | ||
key: "resurrectSourceNode", | ||
value: function resurrectSourceNode() { | ||
this.draggedSourceNode.style.display = 'none'; | ||
this.draggedSourceNode.removeAttribute('data-reactid'); | ||
document.body.appendChild(this.draggedSourceNode); | ||
else { | ||
return touchBackendFactory; | ||
} | ||
}, { | ||
key: "uninstallSourceNodeRemovalObserver", | ||
value: function uninstallSourceNodeRemovalObserver() { | ||
if (this.draggedSourceNodeRemovalObserver) { | ||
this.draggedSourceNodeRemovalObserver.disconnect(); | ||
} | ||
this.draggedSourceNodeRemovalObserver = null; | ||
this.draggedSourceNode = null; | ||
} | ||
}]); | ||
return TouchBackend; | ||
}(); | ||
exports.TouchBackend = TouchBackend; | ||
function createTouchBackend() { | ||
var optionsOrManager = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var touchBackendFactory = function touchBackendFactory(manager) { | ||
return new TouchBackend(manager, optionsOrManager); | ||
}; | ||
if (optionsOrManager.getMonitor) { | ||
return touchBackendFactory(optionsOrManager); | ||
} else { | ||
return touchBackendFactory; | ||
} | ||
} | ||
exports.default = createTouchBackend; | ||
function distance(x1, y1, x2, y2) { | ||
return Math.sqrt(Math.pow(Math.abs(x2 - x1), 2) + Math.pow(Math.abs(y2 - y1), 2)); | ||
return Math.sqrt(Math.pow(Math.abs(x2 - x1), 2) + Math.pow(Math.abs(y2 - y1), 2)); | ||
} | ||
function inAngleRanges(x1, y1, x2, y2, angleRanges) { | ||
if (angleRanges == null) { | ||
if (!angleRanges) { | ||
return false; | ||
} | ||
var angle = (Math.atan2(y2 - y1, x2 - x1) * 180) / Math.PI + 180; | ||
for (var i = 0; i < angleRanges.length; ++i) { | ||
if ((angleRanges[i].start == null || angle >= angleRanges[i].start) && | ||
(angleRanges[i].end == null || angle <= angleRanges[i].end)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
var angle = Math.atan2(y2 - y1, x2 - x1) * 180 / Math.PI + 180; | ||
for (var i = 0; i < angleRanges.length; ++i) { | ||
if ((angleRanges[i].start == null || angle >= angleRanges[i].start) && (angleRanges[i].end == null || angle <= angleRanges[i].end)) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} |
{ | ||
"name": "react-dnd-touch-backend", | ||
"version": "0.8.1", | ||
"version": "0.8.2", | ||
"description": "Touch backend for react-dnd", | ||
@@ -13,6 +13,7 @@ "main": "dist/Touch.js", | ||
"dev": "npm run js-dev && npm run js-dev-drop && npm run js-dev-hover && npm run start", | ||
"compile": "babel src/Touch.js --out-file dist/Touch.js", | ||
"compile": "tsc", | ||
"lint": "eslint --quiet --cache . --ext .js --ext .jsx", | ||
"prepublishOnly": "npm run clean && npm run compile", | ||
"test": "npm run clean && npm run lint" | ||
"test": "npm run clean && npm run lint", | ||
"prettier": "prettier --write src/*.ts" | ||
}, | ||
@@ -37,2 +38,3 @@ "repository": { | ||
"dependencies": { | ||
"dnd-core": "^7.4.4", | ||
"invariant": "^2.2.4" | ||
@@ -58,2 +60,3 @@ }, | ||
"lodash": "^4.17.11", | ||
"prettier": "^1.17.1", | ||
"prop-types": "^15.7.2", | ||
@@ -63,3 +66,4 @@ "react": "^16.8.2", | ||
"react-dom": "^16.8.2", | ||
"rimraf": "2.6.3" | ||
"rimraf": "2.6.3", | ||
"typescript": "^3.4.5" | ||
}, | ||
@@ -72,3 +76,7 @@ "browserify": { | ||
] | ||
}, | ||
"prettier": { | ||
"singleQuote": true, | ||
"semi": false | ||
} | ||
} |
@@ -64,3 +64,3 @@ <img src="https://avatars2.githubusercontent.com/u/6412038?v=3&s=200" alt="react logo" title="react" align="right" width="64" height="64" /> | ||
### Drag Preview | ||
Since native Drag-n-Drop is not currently supported in touch devices. A custom [DragPreview](https://gaearon.github.io/react-dnd/docs-drag-layer.html) is required. Check out the [example](https://github.com/yahoo/react-dnd-touch-backend/blob/master/examples/js/ItemPreview.jsx) for a sample implementation. | ||
Since native Drag-n-Drop is not currently supported in touch devices. A custom [DragPreview](https://react-dnd.github.io/react-dnd/examples/drag-around/custom-drag-layer) is required. Check out the [example](https://github.com/yahoo/react-dnd-touch-backend/blob/master/examples/dropTarget/js/DragPreview.jsx) for a sample implementation. | ||
@@ -67,0 +67,0 @@ We might try to build it directly in the Backend itself in the future to compensate for this limitation. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1161
59561
3
21
10
1
+ Addeddnd-core@^7.4.4