interactjs
Version:
Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE9+)
1,732 lines (1,347 loc) • 203 kB
JavaScript
/**
* interact.js v1.3.4
*
* Copyright (c) 2012-2018 Taye Adeyemi <dev@taye.me>
* Released under the MIT License.
* https://raw.github.com/taye/interact.js/master/LICENSE
*/
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.interact = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
'use strict';
/*
* In a (windowless) server environment this file exports a factory function
* that takes the window to use.
*
* var interact = require('interact.js')(windowObject);
*
* See https://github.com/taye/interact.js/issues/187
*/
if (typeof window === 'undefined') {
module.exports = function (window) {
require('./src/utils/window').init(window);
return require('./src/index');
};
} else {
module.exports = require('./src/index');
}
},{"./src/index":19,"./src/utils/window":52}],2:[function(require,module,exports){
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var extend = require('./utils/extend.js');
function fireUntilImmediateStopped(event, listeners) {
for (var _i = 0; _i < listeners.length; _i++) {
var _ref;
_ref = listeners[_i];
var listener = _ref;
if (event.immediatePropagationStopped) {
break;
}
listener(event);
}
}
var Eventable = function () {
function Eventable(options) {
_classCallCheck(this, Eventable);
this.options = extend({}, options || {});
}
Eventable.prototype.fire = function fire(event) {
var listeners = void 0;
var onEvent = 'on' + event.type;
var global = this.global;
// Interactable#on() listeners
if (listeners = this[event.type]) {
fireUntilImmediateStopped(event, listeners);
}
// interactable.onevent listener
if (this[onEvent]) {
this[onEvent](event);
}
// interact.on() listeners
if (!event.propagationStopped && global && (listeners = global[event.type])) {
fireUntilImmediateStopped(event, listeners);
}
};
Eventable.prototype.on = function on(eventType, listener) {
// if this type of event was never bound
if (this[eventType]) {
this[eventType].push(listener);
} else {
this[eventType] = [listener];
}
};
Eventable.prototype.off = function off(eventType, listener) {
// if it is an action event type
var eventList = this[eventType];
var index = eventList ? eventList.indexOf(listener) : -1;
if (index !== -1) {
eventList.splice(index, 1);
}
if (eventList && eventList.length === 0 || !listener) {
this[eventType] = undefined;
}
};
return Eventable;
}();
module.exports = Eventable;
},{"./utils/extend.js":41}],3:[function(require,module,exports){
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var extend = require('./utils/extend');
var getOriginXY = require('./utils/getOriginXY');
var defaults = require('./defaultOptions');
var signals = require('./utils/Signals').new();
var InteractEvent = function () {
/** */
function InteractEvent(interaction, event, action, phase, element, related) {
var preEnd = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : false;
_classCallCheck(this, InteractEvent);
var target = interaction.target;
var deltaSource = (target && target.options || defaults).deltaSource;
var origin = getOriginXY(target, element, action);
var starting = phase === 'start';
var ending = phase === 'end';
var coords = starting ? interaction.startCoords : interaction.curCoords;
var prevEvent = interaction.prevEvent;
element = element || interaction.element;
var page = extend({}, coords.page);
var client = extend({}, coords.client);
page.x -= origin.x;
page.y -= origin.y;
client.x -= origin.x;
client.y -= origin.y;
this.ctrlKey = event.ctrlKey;
this.altKey = event.altKey;
this.shiftKey = event.shiftKey;
this.metaKey = event.metaKey;
this.button = event.button;
this.buttons = event.buttons;
this.target = element;
this.currentTarget = element;
this.relatedTarget = related || null;
this.preEnd = preEnd;
this.type = action + (phase || '');
this.interaction = interaction;
this.interactable = target;
this.t0 = starting ? interaction.downTimes[interaction.downTimes.length - 1] : prevEvent.t0;
var signalArg = {
interaction: interaction,
event: event,
action: action,
phase: phase,
element: element,
related: related,
page: page,
client: client,
coords: coords,
starting: starting,
ending: ending,
deltaSource: deltaSource,
iEvent: this
};
signals.fire('set-xy', signalArg);
if (ending) {
// use previous coords when ending
this.pageX = prevEvent.pageX;
this.pageY = prevEvent.pageY;
this.clientX = prevEvent.clientX;
this.clientY = prevEvent.clientY;
} else {
this.pageX = page.x;
this.pageY = page.y;
this.clientX = client.x;
this.clientY = client.y;
}
this.x0 = interaction.startCoords.page.x - origin.x;
this.y0 = interaction.startCoords.page.y - origin.y;
this.clientX0 = interaction.startCoords.client.x - origin.x;
this.clientY0 = interaction.startCoords.client.y - origin.y;
signals.fire('set-delta', signalArg);
this.timeStamp = coords.timeStamp;
this.dt = interaction.pointerDelta.timeStamp;
this.duration = this.timeStamp - this.t0;
// speed and velocity in pixels per second
this.speed = interaction.pointerDelta[deltaSource].speed;
this.velocityX = interaction.pointerDelta[deltaSource].vx;
this.velocityY = interaction.pointerDelta[deltaSource].vy;
this.swipe = ending || phase === 'inertiastart' ? this.getSwipe() : null;
signals.fire('new', signalArg);
}
InteractEvent.prototype.getSwipe = function getSwipe() {
var interaction = this.interaction;
if (interaction.prevEvent.speed < 600 || this.timeStamp - interaction.prevEvent.timeStamp > 150) {
return null;
}
var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI;
var overlap = 22.5;
if (angle < 0) {
angle += 360;
}
var left = 135 - overlap <= angle && angle < 225 + overlap;
var up = 225 - overlap <= angle && angle < 315 + overlap;
var right = !left && (315 - overlap <= angle || angle < 45 + overlap);
var down = !up && 45 - overlap <= angle && angle < 135 + overlap;
return {
up: up,
down: down,
left: left,
right: right,
angle: angle,
speed: interaction.prevEvent.speed,
velocity: {
x: interaction.prevEvent.velocityX,
y: interaction.prevEvent.velocityY
}
};
};
InteractEvent.prototype.preventDefault = function preventDefault() {};
/** */
InteractEvent.prototype.stopImmediatePropagation = function stopImmediatePropagation() {
this.immediatePropagationStopped = this.propagationStopped = true;
};
/** */
InteractEvent.prototype.stopPropagation = function stopPropagation() {
this.propagationStopped = true;
};
return InteractEvent;
}();
signals.on('set-delta', function (_ref) {
var iEvent = _ref.iEvent,
interaction = _ref.interaction,
starting = _ref.starting,
deltaSource = _ref.deltaSource;
var prevEvent = starting ? iEvent : interaction.prevEvent;
if (deltaSource === 'client') {
iEvent.dx = iEvent.clientX - prevEvent.clientX;
iEvent.dy = iEvent.clientY - prevEvent.clientY;
} else {
iEvent.dx = iEvent.pageX - prevEvent.pageX;
iEvent.dy = iEvent.pageY - prevEvent.pageY;
}
});
InteractEvent.signals = signals;
module.exports = InteractEvent;
},{"./defaultOptions":18,"./utils/Signals":34,"./utils/extend":41,"./utils/getOriginXY":42}],4:[function(require,module,exports){
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var clone = require('./utils/clone');
var is = require('./utils/is');
var events = require('./utils/events');
var extend = require('./utils/extend');
var actions = require('./actions/base');
var scope = require('./scope');
var Eventable = require('./Eventable');
var defaults = require('./defaultOptions');
var signals = require('./utils/Signals').new();
var _require = require('./utils/domUtils'),
getElementRect = _require.getElementRect,
nodeContains = _require.nodeContains,
trySelector = _require.trySelector,
matchesSelector = _require.matchesSelector;
var _require2 = require('./utils/window'),
getWindow = _require2.getWindow;
var _require3 = require('./utils/arr'),
contains = _require3.contains;
var _require4 = require('./utils/browser'),
wheelEvent = _require4.wheelEvent;
// all set interactables
scope.interactables = [];
var Interactable = function () {
/** */
function Interactable(target, options) {
_classCallCheck(this, Interactable);
options = options || {};
this.target = target;
this.events = new Eventable();
this._context = options.context || scope.document;
this._win = getWindow(trySelector(target) ? this._context : target);
this._doc = this._win.document;
signals.fire('new', {
target: target,
options: options,
interactable: this,
win: this._win
});
scope.addDocument(this._doc, this._win);
scope.interactables.push(this);
this.set(options);
}
Interactable.prototype.setOnEvents = function setOnEvents(action, phases) {
var onAction = 'on' + action;
if (is.function(phases.onstart)) {
this.events[onAction + 'start'] = phases.onstart;
}
if (is.function(phases.onmove)) {
this.events[onAction + 'move'] = phases.onmove;
}
if (is.function(phases.onend)) {
this.events[onAction + 'end'] = phases.onend;
}
if (is.function(phases.oninertiastart)) {
this.events[onAction + 'inertiastart'] = phases.oninertiastart;
}
return this;
};
Interactable.prototype.setPerAction = function setPerAction(action, options) {
// for all the default per-action options
for (var option in options) {
// if this option exists for this action
if (option in defaults[action]) {
// if the option in the options arg is an object value
if (is.object(options[option])) {
// duplicate the object and merge
this.options[action][option] = clone(this.options[action][option] || {});
extend(this.options[action][option], options[option]);
if (is.object(defaults.perAction[option]) && 'enabled' in defaults.perAction[option]) {
this.options[action][option].enabled = options[option].enabled === false ? false : true;
}
} else if (is.bool(options[option]) && is.object(defaults.perAction[option])) {
this.options[action][option].enabled = options[option];
} else if (options[option] !== undefined) {
// or if it's not undefined, do a plain assignment
this.options[action][option] = options[option];
}
}
}
};
/**
* The default function to get an Interactables bounding rect. Can be
* overridden using {@link Interactable.rectChecker}.
*
* @param {Element} [element] The element to measure.
* @return {object} The object's bounding rectangle.
*/
Interactable.prototype.getRect = function getRect(element) {
element = element || this.target;
if (is.string(this.target) && !is.element(element)) {
element = this._context.querySelector(this.target);
}
return getElementRect(element);
};
/**
* Returns or sets the function used to calculate the interactable's
* element's rectangle
*
* @param {function} [checker] A function which returns this Interactable's
* bounding rectangle. See {@link Interactable.getRect}
* @return {function | object} The checker function or this Interactable
*/
Interactable.prototype.rectChecker = function rectChecker(checker) {
if (is.function(checker)) {
this.getRect = checker;
return this;
}
if (checker === null) {
delete this.options.getRect;
return this;
}
return this.getRect;
};
Interactable.prototype._backCompatOption = function _backCompatOption(optionName, newValue) {
if (trySelector(newValue) || is.object(newValue)) {
this.options[optionName] = newValue;
for (var _i = 0; _i < actions.names.length; _i++) {
var _ref;
_ref = actions.names[_i];
var action = _ref;
this.options[action][optionName] = newValue;
}
return this;
}
return this.options[optionName];
};
/**
* Gets or sets the origin of the Interactable's element. The x and y
* of the origin will be subtracted from action event coordinates.
*
* @param {Element | object | string} [origin] An HTML or SVG Element whose
* rect will be used, an object eg. { x: 0, y: 0 } or string 'parent', 'self'
* or any CSS selector
*
* @return {object} The current origin or this Interactable
*/
Interactable.prototype.origin = function origin(newValue) {
return this._backCompatOption('origin', newValue);
};
/**
* Returns or sets the mouse coordinate types used to calculate the
* movement of the pointer.
*
* @param {string} [newValue] Use 'client' if you will be scrolling while
* interacting; Use 'page' if you want autoScroll to work
* @return {string | object} The current deltaSource or this Interactable
*/
Interactable.prototype.deltaSource = function deltaSource(newValue) {
if (newValue === 'page' || newValue === 'client') {
this.options.deltaSource = newValue;
return this;
}
return this.options.deltaSource;
};
/**
* Gets the selector context Node of the Interactable. The default is
* `window.document`.
*
* @return {Node} The context Node of this Interactable
*/
Interactable.prototype.context = function context() {
return this._context;
};
Interactable.prototype.inContext = function inContext(element) {
return this._context === element.ownerDocument || nodeContains(this._context, element);
};
/**
* Calls listeners for the given InteractEvent type bound globally
* and directly to this Interactable
*
* @param {InteractEvent} iEvent The InteractEvent object to be fired on this
* Interactable
* @return {Interactable} this Interactable
*/
Interactable.prototype.fire = function fire(iEvent) {
this.events.fire(iEvent);
return this;
};
Interactable.prototype._onOffMultiple = function _onOffMultiple(method, eventType, listener, options) {
if (is.string(eventType) && eventType.search(' ') !== -1) {
eventType = eventType.trim().split(/ +/);
}
if (is.array(eventType)) {
for (var _i2 = 0; _i2 < eventType.length; _i2++) {
var _ref2;
_ref2 = eventType[_i2];
var type = _ref2;
this[method](type, listener, options);
}
return true;
}
if (is.object(eventType)) {
for (var prop in eventType) {
this[method](prop, eventType[prop], listener);
}
return true;
}
};
/**
* Binds a listener for an InteractEvent, pointerEvent or DOM event.
*
* @param {string | array | object} eventType The types of events to listen
* for
* @param {function} listener The function event (s)
* @param {object | boolean} [options] options object or useCapture flag
* for addEventListener
* @return {object} This Interactable
*/
Interactable.prototype.on = function on(eventType, listener, options) {
if (this._onOffMultiple('on', eventType, listener, options)) {
return this;
}
if (eventType === 'wheel') {
eventType = wheelEvent;
}
if (contains(Interactable.eventTypes, eventType)) {
this.events.on(eventType, listener);
}
// delegated event for selector
else if (is.string(this.target)) {
events.addDelegate(this.target, this._context, eventType, listener, options);
} else {
events.add(this.target, eventType, listener, options);
}
return this;
};
/**
* Removes an InteractEvent, pointerEvent or DOM event listener
*
* @param {string | array | object} eventType The types of events that were
* listened for
* @param {function} listener The listener function to be removed
* @param {object | boolean} [options] options object or useCapture flag for
* removeEventListener
* @return {object} This Interactable
*/
Interactable.prototype.off = function off(eventType, listener, options) {
if (this._onOffMultiple('off', eventType, listener, options)) {
return this;
}
if (eventType === 'wheel') {
eventType = wheelEvent;
}
// if it is an action event type
if (contains(Interactable.eventTypes, eventType)) {
this.events.off(eventType, listener);
}
// delegated event
else if (is.string(this.target)) {
events.removeDelegate(this.target, this._context, eventType, listener, options);
}
// remove listener from this Interatable's element
else {
events.remove(this.target, eventType, listener, options);
}
return this;
};
/**
* Reset the options of this Interactable
*
* @param {object} options The new settings to apply
* @return {object} This Interactable
*/
Interactable.prototype.set = function set(options) {
if (!is.object(options)) {
options = {};
}
this.options = clone(defaults.base);
var perActions = clone(defaults.perAction);
for (var actionName in actions.methodDict) {
var methodName = actions.methodDict[actionName];
this.options[actionName] = clone(defaults[actionName]);
this.setPerAction(actionName, perActions);
this[methodName](options[actionName]);
}
for (var _i3 = 0; _i3 < Interactable.settingsMethods.length; _i3++) {
var _ref3;
_ref3 = Interactable.settingsMethods[_i3];
var setting = _ref3;
this.options[setting] = defaults.base[setting];
if (setting in options) {
this[setting](options[setting]);
}
}
signals.fire('set', {
options: options,
interactable: this
});
return this;
};
/**
* Remove this interactable from the list of interactables and remove it's
* action capabilities and event listeners
*
* @return {interact}
*/
Interactable.prototype.unset = function unset() {
events.remove(this.target, 'all');
if (is.string(this.target)) {
// remove delegated events
for (var type in events.delegatedEvents) {
var delegated = events.delegatedEvents[type];
if (delegated.selectors[0] === this.target && delegated.contexts[0] === this._context) {
delegated.selectors.splice(0, 1);
delegated.contexts.splice(0, 1);
delegated.listeners.splice(0, 1);
// remove the arrays if they are empty
if (!delegated.selectors.length) {
delegated[type] = null;
}
}
events.remove(this._context, type, events.delegateListener);
events.remove(this._context, type, events.delegateUseCapture, true);
}
} else {
events.remove(this, 'all');
}
signals.fire('unset', { interactable: this });
scope.interactables.splice(scope.interactables.indexOf(this), 1);
// Stop related interactions when an Interactable is unset
for (var _i4 = 0; _i4 < (scope.interactions || []).length; _i4++) {
var _ref4;
_ref4 = (scope.interactions || [])[_i4];
var interaction = _ref4;
if (interaction.target === this && interaction.interacting() && !interaction._ending) {
interaction.stop();
}
}
return scope.interact;
};
return Interactable;
}();
scope.interactables.indexOfElement = function indexOfElement(target, context) {
context = context || scope.document;
for (var i = 0; i < this.length; i++) {
var interactable = this[i];
if (interactable.target === target && interactable._context === context) {
return i;
}
}
return -1;
};
scope.interactables.get = function interactableGet(element, options, dontCheckInContext) {
var ret = this[this.indexOfElement(element, options && options.context)];
return ret && (is.string(element) || dontCheckInContext || ret.inContext(element)) ? ret : null;
};
scope.interactables.forEachMatch = function (element, callback) {
for (var _i5 = 0; _i5 < this.length; _i5++) {
var _ref5;
_ref5 = this[_i5];
var interactable = _ref5;
var ret = void 0;
if ((is.string(interactable.target)
// target is a selector and the element matches
? is.element(element) && matchesSelector(element, interactable.target) :
// target is the element
element === interactable.target) &&
// the element is in context
interactable.inContext(element)) {
ret = callback(interactable);
}
if (ret !== undefined) {
return ret;
}
}
};
// all interact.js eventTypes
Interactable.eventTypes = scope.eventTypes = [];
Interactable.signals = signals;
Interactable.settingsMethods = ['deltaSource', 'origin', 'preventDefault', 'rectChecker'];
module.exports = Interactable;
},{"./Eventable":2,"./actions/base":6,"./defaultOptions":18,"./scope":33,"./utils/Signals":34,"./utils/arr":35,"./utils/browser":36,"./utils/clone":37,"./utils/domUtils":39,"./utils/events":40,"./utils/extend":41,"./utils/is":46,"./utils/window":52}],5:[function(require,module,exports){
'use strict';
function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
var scope = require('./scope');
var utils = require('./utils');
var events = require('./utils/events');
var browser = require('./utils/browser');
var domObjects = require('./utils/domObjects');
var finder = require('./utils/interactionFinder');
var signals = require('./utils/Signals').new();
var listeners = {};
var methodNames = ['pointerDown', 'pointerMove', 'pointerUp', 'updatePointer', 'removePointer'];
// for ignoring browser's simulated mouse events
var prevTouchTime = 0;
// all active and idle interactions
scope.interactions = [];
var Interaction = function () {
/** */
function Interaction(_ref) {
var pointerType = _ref.pointerType;
_classCallCheck(this, Interaction);
this.target = null; // current interactable being interacted with
this.element = null; // the target element of the interactable
this.prepared = { // action that's ready to be fired on next move event
name: null,
axis: null,
edges: null
};
// keep track of added pointers
this.pointers = [];
this.pointerIds = [];
this.downTargets = [];
this.downTimes = [];
// Previous native pointer move event coordinates
this.prevCoords = {
page: { x: 0, y: 0 },
client: { x: 0, y: 0 },
timeStamp: 0
};
// current native pointer move event coordinates
this.curCoords = {
page: { x: 0, y: 0 },
client: { x: 0, y: 0 },
timeStamp: 0
};
// Starting InteractEvent pointer coordinates
this.startCoords = {
page: { x: 0, y: 0 },
client: { x: 0, y: 0 },
timeStamp: 0
};
// Change in coordinates and time of the pointer
this.pointerDelta = {
page: { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
client: { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
timeStamp: 0
};
this.downEvent = null; // pointerdown/mousedown/touchstart event
this.downPointer = {};
this._eventTarget = null;
this._curEventTarget = null;
this.prevEvent = null; // previous action event
this.pointerIsDown = false;
this.pointerWasMoved = false;
this._interacting = false;
this._ending = false;
this.pointerType = pointerType;
signals.fire('new', this);
scope.interactions.push(this);
}
Interaction.prototype.pointerDown = function pointerDown(pointer, event, eventTarget) {
var pointerIndex = this.updatePointer(pointer, event, true);
signals.fire('down', {
pointer: pointer,
event: event,
eventTarget: eventTarget,
pointerIndex: pointerIndex,
interaction: this
});
};
/**
* ```js
* interact(target)
* .draggable({
* // disable the default drag start by down->move
* manualStart: true
* })
* // start dragging after the user holds the pointer down
* .on('hold', function (event) {
* var interaction = event.interaction;
*
* if (!interaction.interacting()) {
* interaction.start({ name: 'drag' },
* event.interactable,
* event.currentTarget);
* }
* });
* ```
*
* Start an action with the given Interactable and Element as tartgets. The
* action must be enabled for the target Interactable and an appropriate
* number of pointers must be held down - 1 for drag/resize, 2 for gesture.
*
* Use it with `interactable.<action>able({ manualStart: false })` to always
* [start actions manually](https://github.com/taye/interact.js/issues/114)
*
* @param {object} action The action to be performed - drag, resize, etc.
* @param {Interactable} target The Interactable to target
* @param {Element} element The DOM Element to target
* @return {object} interact
*/
Interaction.prototype.start = function start(action, target, element) {
if (this.interacting() || !this.pointerIsDown || this.pointerIds.length < (action.name === 'gesture' ? 2 : 1)) {
return;
}
// if this interaction had been removed after stopping
// add it back
if (scope.interactions.indexOf(this) === -1) {
scope.interactions.push(this);
}
utils.copyAction(this.prepared, action);
this.target = target;
this.element = element;
signals.fire('action-start', {
interaction: this,
event: this.downEvent
});
};
Interaction.prototype.pointerMove = function pointerMove(pointer, event, eventTarget) {
if (!this.simulation) {
this.updatePointer(pointer);
utils.setCoords(this.curCoords, this.pointers);
}
var duplicateMove = this.curCoords.page.x === this.prevCoords.page.x && this.curCoords.page.y === this.prevCoords.page.y && this.curCoords.client.x === this.prevCoords.client.x && this.curCoords.client.y === this.prevCoords.client.y;
var dx = void 0;
var dy = void 0;
// register movement greater than pointerMoveTolerance
if (this.pointerIsDown && !this.pointerWasMoved) {
dx = this.curCoords.client.x - this.startCoords.client.x;
dy = this.curCoords.client.y - this.startCoords.client.y;
this.pointerWasMoved = utils.hypot(dx, dy) > Interaction.pointerMoveTolerance;
}
var signalArg = {
pointer: pointer,
pointerIndex: this.getPointerIndex(pointer),
event: event,
eventTarget: eventTarget,
dx: dx,
dy: dy,
duplicate: duplicateMove,
interaction: this,
interactingBeforeMove: this.interacting()
};
if (!duplicateMove) {
// set pointer coordinate, time changes and speeds
utils.setCoordDeltas(this.pointerDelta, this.prevCoords, this.curCoords);
}
signals.fire('move', signalArg);
if (!duplicateMove) {
// if interacting, fire an 'action-move' signal etc
if (this.interacting()) {
this.doMove(signalArg);
}
if (this.pointerWasMoved) {
utils.copyCoords(this.prevCoords, this.curCoords);
}
}
};
/**
* ```js
* interact(target)
* .draggable(true)
* .on('dragmove', function (event) {
* if (someCondition) {
* // change the snap settings
* event.interactable.draggable({ snap: { targets: [] }});
* // fire another move event with re-calculated snap
* event.interaction.doMove();
* }
* });
* ```
*
* Force a move of the current action at the same coordinates. Useful if
* snap/restrict has been changed and you want a movement with the new
* settings.
*/
Interaction.prototype.doMove = function doMove(signalArg) {
signalArg = utils.extend({
pointer: this.pointers[0],
event: this.prevEvent,
eventTarget: this._eventTarget,
interaction: this
}, signalArg || {});
signals.fire('before-action-move', signalArg);
if (!this._dontFireMove) {
signals.fire('action-move', signalArg);
}
this._dontFireMove = false;
};
// End interact move events and stop auto-scroll unless simulation is running
Interaction.prototype.pointerUp = function pointerUp(pointer, event, eventTarget, curEventTarget) {
var pointerIndex = this.getPointerIndex(pointer);
signals.fire(/cancel$/i.test(event.type) ? 'cancel' : 'up', {
pointer: pointer,
pointerIndex: pointerIndex,
event: event,
eventTarget: eventTarget,
curEventTarget: curEventTarget,
interaction: this
});
if (!this.simulation) {
this.end(event);
}
this.pointerIsDown = false;
this.removePointer(pointer, event);
};
/**
* ```js
* interact(target)
* .draggable(true)
* .on('move', function (event) {
* if (event.pageX > 1000) {
* // end the current action
* event.interaction.end();
* // stop all further listeners from being called
* event.stopImmediatePropagation();
* }
* });
* ```
*
* Stop the current action and fire an end event. Inertial movement does
* not happen.
*
* @param {PointerEvent} [event]
*/
Interaction.prototype.end = function end(event) {
this._ending = true;
event = event || this.prevEvent;
if (this.interacting()) {
signals.fire('action-end', {
event: event,
interaction: this
});
}
this.stop();
this._ending = false;
};
Interaction.prototype.currentAction = function currentAction() {
return this._interacting ? this.prepared.name : null;
};
Interaction.prototype.interacting = function interacting() {
return this._interacting;
};
/** */
Interaction.prototype.stop = function stop() {
signals.fire('stop', { interaction: this });
if (this._interacting) {
signals.fire('stop-active', { interaction: this });
signals.fire('stop-' + this.prepared.name, { interaction: this });
}
this.target = this.element = null;
this._interacting = false;
this.prepared.name = this.prevEvent = null;
};
Interaction.prototype.getPointerIndex = function getPointerIndex(pointer) {
// mouse and pen interactions may have only one pointer
if (this.pointerType === 'mouse' || this.pointerType === 'pen') {
return 0;
}
return this.pointerIds.indexOf(utils.getPointerId(pointer));
};
Interaction.prototype.updatePointer = function updatePointer(pointer, event) {
var down = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : event && /(down|start)$/i.test(event.type);
var id = utils.getPointerId(pointer);
var index = this.getPointerIndex(pointer);
if (index === -1) {
index = this.pointerIds.length;
this.pointerIds[index] = id;
}
if (down) {
signals.fire('update-pointer-down', {
pointer: pointer,
event: event,
down: down,
pointerId: id,
pointerIndex: index,
interaction: this
});
}
this.pointers[index] = pointer;
return index;
};
Interaction.prototype.removePointer = function removePointer(pointer, event) {
var index = this.getPointerIndex(pointer);
if (index === -1) {
return;
}
signals.fire('remove-pointer', {
pointer: pointer,
event: event,
pointerIndex: index,
interaction: this
});
this.pointers.splice(index, 1);
this.pointerIds.splice(index, 1);
this.downTargets.splice(index, 1);
this.downTimes.splice(index, 1);
};
Interaction.prototype._updateEventTargets = function _updateEventTargets(target, currentTarget) {
this._eventTarget = target;
this._curEventTarget = currentTarget;
};
return Interaction;
}();
for (var _i = 0; _i < methodNames.length; _i++) {
var method = methodNames[_i];
listeners[method] = doOnInteractions(method);
}
function doOnInteractions(method) {
return function (event) {
var pointerType = utils.getPointerType(event);
var _utils$getEventTarget = utils.getEventTargets(event),
eventTarget = _utils$getEventTarget[0],
curEventTarget = _utils$getEventTarget[1];
var matches = []; // [ [pointer, interaction], ...]
if (browser.supportsTouch && /touch/.test(event.type)) {
prevTouchTime = new Date().getTime();
for (var _i2 = 0; _i2 < event.changedTouches.length; _i2++) {
var _ref2;
_ref2 = event.changedTouches[_i2];
var changedTouch = _ref2;
var pointer = changedTouch;
var interaction = finder.search(pointer, event.type, eventTarget);
matches.push([pointer, interaction || new Interaction({ pointerType: pointerType })]);
}
} else {
var invalidPointer = false;
if (!browser.supportsPointerEvent && /mouse/.test(event.type)) {
// ignore mouse events while touch interactions are active
for (var i = 0; i < scope.interactions.length && !invalidPointer; i++) {
invalidPointer = scope.interactions[i].pointerType !== 'mouse' && scope.interactions[i].pointerIsDown;
}
// try to ignore mouse events that are simulated by the browser
// after a touch event
invalidPointer = invalidPointer || new Date().getTime() - prevTouchTime < 500
// on iOS and Firefox Mobile, MouseEvent.timeStamp is zero if simulated
|| event.timeStamp === 0;
}
if (!invalidPointer) {
var _interaction = finder.search(event, event.type, eventTarget);
if (!_interaction) {
_interaction = new Interaction({ pointerType: pointerType });
}
matches.push([event, _interaction]);
}
}
for (var _i3 = 0; _i3 < matches.length; _i3++) {
var _ref3 = matches[_i3];
var _pointer = _ref3[0];
var _interaction2 = _ref3[1];
_interaction2._updateEventTargets(eventTarget, curEventTarget);
_interaction2[method](_pointer, event, eventTarget, curEventTarget);
}
};
}
function endAll(event) {
for (var _i4 = 0; _i4 < scope.interactions.length; _i4++) {
var _ref4;
_ref4 = scope.interactions[_i4];
var interaction = _ref4;
interaction.end(event);
signals.fire('endall', { event: event, interaction: interaction });
}
}
var docEvents = {/* 'eventType': listenerFunc */};
var pEventTypes = browser.pEventTypes;
if (domObjects.PointerEvent) {
docEvents[pEventTypes.down] = listeners.pointerDown;
docEvents[pEventTypes.move] = listeners.pointerMove;
docEvents[pEventTypes.up] = listeners.pointerUp;
docEvents[pEventTypes.cancel] = listeners.pointerUp;
} else {
docEvents.mousedown = listeners.pointerDown;
docEvents.mousemove = listeners.pointerMove;
docEvents.mouseup = listeners.pointerUp;
docEvents.touchstart = listeners.pointerDown;
docEvents.touchmove = listeners.pointerMove;
docEvents.touchend = listeners.pointerUp;
docEvents.touchcancel = listeners.pointerUp;
}
docEvents.blur = endAll;
function onDocSignal(_ref5, signalName) {
var doc = _ref5.doc;
var eventMethod = signalName.indexOf('add') === 0 ? events.add : events.remove;
// delegate event listener
for (var eventType in scope.delegatedEvents) {
eventMethod(doc, eventType, events.delegateListener);
eventMethod(doc, eventType, events.delegateUseCapture, true);
}
for (var _eventType in docEvents) {
eventMethod(doc, _eventType, docEvents[_eventType], browser.isIOS ? { passive: false } : undefined);
}
}
signals.on('update-pointer-down', function (_ref6) {
var interaction = _ref6.interaction,
pointer = _ref6.pointer,
pointerId = _ref6.pointerId,
pointerIndex = _ref6.pointerIndex,
event = _ref6.event,
eventTarget = _ref6.eventTarget,
down = _ref6.down;
interaction.pointerIds[pointerIndex] = pointerId;
interaction.pointers[pointerIndex] = pointer;
if (down) {
interaction.pointerIsDown = true;
}
if (!interaction.interacting()) {
utils.setCoords(interaction.startCoords, interaction.pointers);
utils.copyCoords(interaction.curCoords, interaction.startCoords);
utils.copyCoords(interaction.prevCoords, interaction.startCoords);
interaction.downEvent = event;
interaction.downTimes[pointerIndex] = interaction.curCoords.timeStamp;
interaction.downTargets[pointerIndex] = eventTarget || event && utils.getEventTargets(event)[0];
interaction.pointerWasMoved = false;
utils.pointerExtend(interaction.downPointer, pointer);
}
});
scope.signals.on('add-document', onDocSignal);
scope.signals.on('remove-document', onDocSignal);
Interaction.pointerMoveTolerance = 1;
Interaction.doOnInteractions = doOnInteractions;
Interaction.endAll = endAll;
Interaction.signals = signals;
Interaction.docEvents = docEvents;
scope.endAllInteractions = endAll;
module.exports = Interaction;
},{"./scope":33,"./utils":44,"./utils/Signals":34,"./utils/browser":36,"./utils/domObjects":38,"./utils/events":40,"./utils/interactionFinder":45}],6:[function(require,module,exports){
'use strict';
var Interaction = require('../Interaction');
var InteractEvent = require('../InteractEvent');
var actions = {
firePrepared: firePrepared,
names: [],
methodDict: {}
};
Interaction.signals.on('action-start', function (_ref) {
var interaction = _ref.interaction,
event = _ref.event;
interaction._interacting = true;
firePrepared(interaction, event, 'start');
});
Interaction.signals.on('action-move', function (_ref2) {
var interaction = _ref2.interaction,
event = _ref2.event,
preEnd = _ref2.preEnd;
firePrepared(interaction, event, 'move', preEnd);
// if the action was ended in a listener
if (!interaction.interacting()) {
return false;
}
});
Interaction.signals.on('action-end', function (_ref3) {
var interaction = _ref3.interaction,
event = _ref3.event;
firePrepared(interaction, event, 'end');
});
function firePrepared(interaction, event, phase, preEnd) {
var actionName = interaction.prepared.name;
var newEvent = new InteractEvent(interaction, event, actionName, phase, interaction.element, null, preEnd);
interaction.target.fire(newEvent);
interaction.prevEvent = newEvent;
}
module.exports = actions;
},{"../InteractEvent":3,"../Interaction":5}],7:[function(require,module,exports){
'use strict';
var actions = require('./base');
var utils = require('../utils');
var InteractEvent = require('../InteractEvent');
/** @lends Interactable */
var Interactable = require('../Interactable');
var Interaction = require('../Interaction');
var defaultOptions = require('../defaultOptions');
var drag = {
defaults: {
enabled: false,
mouseButtons: null,
origin: null,
snap: null,
restrict: null,
inertia: null,
autoScroll: null,
startAxis: 'xy',
lockAxis: 'xy'
},
checker: function checker(pointer, event, interactable) {
var dragOptions = interactable.options.drag;
return dragOptions.enabled ? { name: 'drag', axis: dragOptions.lockAxis === 'start' ? dragOptions.startAxis : dragOptions.lockAxis } : null;
},
getCursor: function getCursor() {
return 'move';
}
};
Interaction.signals.on('before-action-move', function (_ref) {
var interaction = _ref.interaction;
if (interaction.prepared.name !== 'drag') {
return;
}
var axis = interaction.prepared.axis;
if (axis === 'x') {
interaction.curCoords.page.y = interaction.startCoords.page.y;
interaction.curCoords.client.y = interaction.startCoords.client.y;
interaction.pointerDelta.page.speed = Math.abs(interaction.pointerDelta.page.vx);
interaction.pointerDelta.client.speed = Math.abs(interaction.pointerDelta.client.vx);
interaction.pointerDelta.client.vy = 0;
interaction.pointerDelta.page.vy = 0;
} else if (axis === 'y') {
interaction.curCoords.page.x = interaction.startCoords.page.x;
interaction.curCoords.client.x = interaction.startCoords.client.x;
interaction.pointerDelta.page.speed = Math.abs(interaction.pointerDelta.page.vy);
interaction.pointerDelta.client.speed = Math.abs(interaction.pointerDelta.client.vy);
interaction.pointerDelta.client.vx = 0;
interaction.pointerDelta.page.vx = 0;
}
});
// dragmove
InteractEvent.signals.on('new', function (_ref2) {
var iEvent = _ref2.iEvent,
interaction = _ref2.interaction;
if (iEvent.type !== 'dragmove') {
return;
}
var axis = interaction.prepared.axis;
if (axis === 'x') {
iEvent.pageY = interaction.startCoords.page.y;
iEvent.clientY = interaction.startCoords.client.y;
iEvent.dy = 0;
} else if (axis === 'y') {
iEvent.pageX = interaction.startCoords.page.x;
iEvent.clientX = interaction.startCoords.client.x;
iEvent.dx = 0;
}
});
/**
* ```js
* interact(element).draggable({
* onstart: function (event) {},
* onmove : function (event) {},
* onend : function (event) {},
*
* // the axis in which the first movement must be
* // for the drag sequence to start
* // 'xy' by default - any direction
* startAxis: 'x' || 'y' || 'xy',
*
* // 'xy' by default - don't restrict to one axis (move in any direction)
* // 'x' or 'y' to restrict movement to either axis
* // 'start' to restrict movement to the axis the drag started in
* lockAxis: 'x' || 'y' || 'xy' || 'start',
*
* // max number of drags that can happen concurrently
* // with elements of this Interactable. Infinity by default
* max: Infinity,
*
* // max number of drags that can target the same element+Interactable
* // 1 by default
* maxPerElement: 2
* });
*
* var isDraggable = interact('element').draggable(); // true
* ```
*
* Get or set whether drag actions can be performed on the target
*
* @param {boolean | object} [options] true/false or An object with event
* listeners to be fired on drag events (object makes the Interactable
* draggable)
* @return {boolean | Interactable} boolean indicating if this can be the
* target of drag events, or this Interctable
*/
Interactable.prototype.draggable = function (options) {
if (utils.is.object(options)) {
this.options.drag.enabled = options.enabled === false ? false : true;
this.setPerAction('drag', options);
this.setOnEvents('drag', options);
if (/^(xy|x|y|start)$/.test(options.lockAxis)) {
this.options.drag.lockAxis = options.lockAxis;
}
if (/^(xy|x|y)$/.test(options.startAxis)) {
this.options.drag.startAxis = options.startAxis;
}
return this;
}
if (utils.is.bool(options)) {
this.options.drag.enabled = options;
if (!options) {
this.ondragstart = this.ondragstart = this.ondragend = null;
}
return this;
}
return this.options.drag;
};
actions.drag = drag;
actions.names.push('drag');
utils.merge(Interactable.eventTypes, ['dragstart', 'dragmove', 'draginertiastart', 'draginertiaresume', 'dragend']);
actions.methodDict.drag = 'draggable';
defaultOptions.drag = drag.defaults;
module.exports = drag;
},{"../InteractEvent":3,"../Interactable":4,"../Interaction":5,"../defaultOptions":18,"../utils":44,"./base":6}],8:[function(require,module,exports){
'use strict';
var actions = require('./base');
var utils = require('../utils');
var scope = require('../scope');
/** @lends module:interact */
var interact = require('../interact');
var InteractEvent = require('../InteractEvent');
/** @lends Interactable */
var Interactable = require('../Interactable');
var Interaction = require('../Interaction');
var defaultOptions = require('../defaultOptions');
var drop = {
defaults: {
enabled: false,
accept: null,
overlap: 'pointer'
}
};
var dynamicDrop = false;
Interaction.signals.on('action-start', function (_ref) {
var interaction = _ref.interaction,
event = _ref.event;
if (interaction.prepared.name !== 'drag') {
return;
}
// reset active dropzones
interaction.activeDrops.dropzones = [];
interaction.activeDrops.elements = [];
interaction.activeDrops.rects = [];
interaction.dropEvents = null;
if (!interaction.dynamicDrop) {
setActiveDrops(interaction.activeDrops, interaction.element);
}
var dragEvent = interaction.prevEvent;
var dropEvents = getDropEvents(interaction, event, dragEvent);
if (dropEvents.activate) {
fireActiveDrops(interaction.activeDrops, dropEvents.activate);
}
});
InteractEvent.signals.on('new', function (_ref2) {
var interaction = _ref2.interaction,
iEvent = _ref2.iEvent,
event = _ref2.event;
if (iEvent.type !== 'dragmove' && iEvent.type !== 'dragend') {
return;
}
var draggableElement = interaction.element;
var dragEvent = iEvent;
var dropResult = getDrop(dragEvent, event, draggableElement);
interaction.dropTarget = dropResult.dropzone;
interaction.dropElement = dropResult.element;
interaction.dropEvents = getDropEvents(interaction, event, dragEvent);
});
Interaction.signals.on('action-move', function (_ref3) {
var interaction = _ref3.interaction;
if (interaction.prepared.name !== 'drag') {
return;
}
fireDropEvents(interaction, interaction.dropEvents);
});
Interaction.signals.on('action-end', function (_ref4) {
var interaction = _ref4.interaction;
if (interaction.prepared.name === 'drag') {
fireDropEvents(interaction, interaction.dropEvents);
}
});
Interaction.signals.on('stop-drag', function (_ref5) {
var interaction = _ref5.interaction;
interaction.activeDrops = {
dropzones: null,
elements: null,
rects: null
};
interaction.dropEvents = null;
});
function collectDrops(activeDrops, element) {
var drops = [];
var elements = [];
// collect all dropzones and their elements which qualify for a drop
for (var _i = 0; _i < scope.interactables.length; _i++) {
var _ref6;
_ref6 = scope.interactables[_i];
var current = _ref6;
if (!current.options.drop.enabled) {
continue;
}
var accept = current.options.drop.accept;
// test the draggable element against the dropzone's accept setting
if (utils.is.element(accept) && accept !== element || utils.is.string(accept) && !utils.matchesSelector(element, accept)) {
continue;
}
// query for new elements if necessary
var dropElements = utils.is.string(current.target) ? current._context.querySelectorAll(current.target) : [current.target];
for (var _i2 = 0; _i2 < dropElements.length; _i2++) {
var _ref7;
_ref7 = dropElements[_i2];
var currentElement = _ref7;
if (currentElement !== element) {
drops.push(current);
elements.push(currentElement);
}
}
}
return {
elements: elements,
dropzones: drops
};
}
function fireActiveDrops(activeDrops, event) {
var prevElement = void 0;
// loop through all active dropzones and trigger event
for (var i = 0; i < activeDrops.dropzones.length; i++) {
var current = activeDrops.dropzones[i];
var currentElement = activeDrops.elements[i];
// prevent trigger of duplicate events on same element
if (currentElement !== prevElement) {
// set current element as event target
event.target = currentElement;
current.fire(event);
}
prevElement = currentElement;
}
}
// Collect a new set of possible drops and save them in activeDrops.
// setActiveDrops should always be called when a drag has just started or a
// drag event happens while dynamicDrop is true
function setActiveDrops(activeDrops, dragElement) {
// get dropzones and their elements that could receive the draggable
var possibleDrops = collectDrops(activeDrops, dragElement);
activeDrops.dropzones = possibleDrops.dropzones;
activeDrops.elements = possibleDrops.elements;
activeDrops.rects = [];
for (var i = 0; i < activeDrops.dropzones.length; i++) {
activeDrops.rects[i] = activeDrops.dropzones[i].getRect(activeDrops.elements[i]);
}
}
function getDrop(dragEvent, event, dragElement) {
var interaction = dragEvent.interaction;
var validDrops = [];
if (dynamicDrop) {
setActiveDrops(interaction.activeDrops, dragElement);
}
// collect all dropzones and their elements which qualify for a drop
for (var j = 0; j < interaction.act