interactjs
Version:
Drag and drop, resizing and multi-touch gestures with inertia and snapping for modern browsers (and also IE8+)
1,723 lines (1,353 loc) • 220 kB
JavaScript
/**
* interact.js v1.3.0-alpha.3+sha.9595c40
*
* Copyright (c) 2012-2017 Taye Adeyemi <dev@taye.me>
* Open source 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 _require = require('./utils/arr'),
indexOf = _require.indexOf;
var extend = require('./utils/extend.js');
function fireUntilImmediateStopped(event, listeners) {
for (var i = 0, len = listeners.length; i < len && !event.immediatePropagationStopped; i++) {
listeners[i](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 ? indexOf(eventList, listener) : -1;
if (index !== -1) {
eventList.splice(index, 1);
}
if (eventList && eventList.length === 0 || !listener) {
this[eventType] = listener;
}
};
return Eventable;
}();
module.exports = Eventable;
},{"./utils/arr":36,"./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":35,"./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 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;
var _require2 = require('./utils/window'),
getWindow = _require2.getWindow;
var _require3 = require('./utils/arr'),
indexOf = _require3.indexOf,
contains = _require3.contains;
var _require4 = require('./utils/browser'),
wheelEvent = _require4.wheelEvent;
// all set interactables
scope.interactables = [];
/*\
* Interactable
[ property ]
**
* Object type returned by @interact
\*/
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
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];
}
}
}
};
/*\
* Interactable.getRect
[ method ]
*
* The default function to get an Interactables bounding rect. Can be
* overridden using @Interactable.rectChecker.
*
- element (Element) #optional The element to measure.
= (object) The object's bounding rectangle.
o {
o top : 0,
o left : 0,
o bottom: 0,
o right : 0,
o width : 0,
o height: 0
o }
\*/
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);
};
/*\
* Interactable.rectChecker
[ method ]
*
* Returns or sets the function used to calculate the interactable's
* element's rectangle
*
- checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect
= (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 _iterator = actions.names, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref = _i.value;
}
var action = _ref;
this.options[action][optionName] = newValue;
}
return this;
}
return this.options[optionName];
};
/*\
* Interactable.origin
[ method ]
*
* Gets or sets the origin of the Interactable's element. The x and y
* of the origin will be subtracted from action event coordinates.
*
- origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector
* OR
- origin (Element) #optional An HTML or SVG Element whose rect will be used
**
= (object) The current origin or this Interactable
\*/
Interactable.prototype.origin = function origin(newValue) {
return this._backCompatOption('origin', newValue);
};
/*\
* Interactable.deltaSource
[ method ]
*
* Returns or sets the mouse coordinate types used to calculate the
* movement of the pointer.
*
- newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work
= (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;
};
/*\
* Interactable.context
[ method ]
*
* Gets the selector context Node of the Interactable. The default is `window.document`.
*
= (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);
};
/*\
* Interactable.fire
[ method ]
*
* Calls listeners for the given InteractEvent type bound globally
* and directly to this Interactable
*
- iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable
= (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 i = 0; i < eventType.length; i++) {
this[method](eventType[i], listener, options);
}
return true;
}
if (is.object(eventType)) {
for (var prop in eventType) {
this[method](prop, eventType[prop], listener);
}
return true;
}
};
/*\
* Interactable.on
[ method ]
*
* Binds a listener for an InteractEvent, pointerEvent or DOM event.
*
- eventType (string | array | object) The types of events to listen for
- listener (function) The function event (s)
- options (object | boolean) #optional options object or useCapture flag for addEventListener
= (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;
};
/*\
* Interactable.off
[ method ]
*
* Removes an InteractEvent, pointerEvent or DOM event listener
*
- eventType (string | array | object) The types of events that were listened for
- listener (function) The listener function to be removed
- options (object | boolean) #optional options object or useCapture flag for removeEventListener
= (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;
};
/*\
* Interactable.set
[ method ]
*
* Reset the options of this Interactable
- options (object) The new settings to apply
= (object) This Interactable
\*/
Interactable.prototype.set = function set(options) {
if (!is.object(options)) {
options = {};
}
this.options = extend({}, defaults.base);
var perActions = extend({}, defaults.perAction);
for (var actionName in actions.methodDict) {
var methodName = actions.methodDict[actionName];
this.options[actionName] = extend({}, defaults[actionName]);
this.setPerAction(actionName, perActions);
this[methodName](options[actionName]);
}
for (var _iterator2 = Interactable.settingsMethods, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) {
var _ref2;
if (_isArray2) {
if (_i2 >= _iterator2.length) break;
_ref2 = _iterator2[_i2++];
} else {
_i2 = _iterator2.next();
if (_i2.done) break;
_ref2 = _i2.value;
}
var setting = _ref2;
this.options[setting] = defaults.base[setting];
if (setting in options) {
this[setting](options[setting]);
}
}
signals.fire('set', {
options: options,
interactable: this
});
return this;
};
/*\
* Interactable.unset
[ method ]
*
* Remove this interactable from the list of interactables and remove
* it's action capabilities and event listeners
*
= (object) @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(indexOf(scope.interactables, this), 1);
// Stop related interactions when an Interactable is unset
for (var _iterator3 = scope.interactions || [], _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) {
var _ref3;
if (_isArray3) {
if (_i3 >= _iterator3.length) break;
_ref3 = _iterator3[_i3++];
} else {
_i3 = _iterator3.next();
if (_i3.done) break;
_ref3 = _i3.value;
}
var interaction = _ref3;
if (interaction.target === this && interaction.interacting()) {
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.forEachSelector = function (callback, element) {
for (var i = 0; i < this.length; i++) {
var interactable = this[i];
// skip non CSS selector targets and out of context elements
if (!is.string(interactable.target) || element && !interactable.inContext(element)) {
continue;
}
var ret = callback(interactable, interactable.target, interactable._context, i, this);
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":34,"./utils/Signals":35,"./utils/arr":36,"./utils/browser":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 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() {
_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.mouse = false;
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
});
};
/*\
* Interaction.start
[ method ]
*
* 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)
*
- action (object) The action to be performed - drag, resize, etc.
- target (Interactable) The Interactable to target
- element (Element) The DOM Element to target
= (object) interact
**
| 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);
| }
| });
\*/
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 (utils.indexOf(scope.interactions, 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);
}
}
};
/*\
* Interaction.doMove
[ method ]
*
* 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.
*
**
| 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();
| }
| });
\*/
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);
};
/*\
* Interaction.end
[ method ]
*
* Stop the current action and fire an end event. Inertial movement does
* not happen.
*
- event (PointerEvent) #optional
**
| 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();
| }
| });
\*/
Interaction.prototype.end = function end(event) {
event = event || this.prevEvent;
if (this.interacting()) {
signals.fire('action-end', {
event: event,
interaction: this
});
}
this.stop();
};
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) {
return utils.indexOf(this.pointerIds, 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 id = utils.getPointerId(pointer);
var index = this.mouse ? 0 : utils.indexOf(this.pointerIds, id);
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, len = methodNames.length; i < len; i++) {
var method = methodNames[i];
listeners[method] = doOnInteractions(method);
}
function doOnInteractions(method) {
return function (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 _i = 0; _i < event.changedTouches.length; _i++) {
var pointer = event.changedTouches[_i];
var interaction = finder.search(pointer, event.type, eventTarget);
matches.push([pointer, interaction || new Interaction()]);
}
} else {
var invalidPointer = false;
if (!browser.supportsPointerEvent && /mouse/.test(event.type)) {
// ignore mouse events while touch interactions are active
for (var _i2 = 0; _i2 < scope.interactions.length && !invalidPointer; _i2++) {
invalidPointer = !scope.interactions[_i2].mouse && scope.interactions[_i2].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();
_interaction.mouse = /mouse/i.test(event.pointerType || event.type)
// MSPointerEvent.MSPOINTER_TYPE_MOUSE
|| event.pointerType === 4 || !event.pointerType;
}
matches.push([event, _interaction]);
}
}
for (var _iterator = matches, _isArray = Array.isArray(_iterator), _i3 = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref;
if (_isArray) {
if (_i3 >= _iterator.length) break;
_ref = _iterator[_i3++];
} else {
_i3 = _iterator.next();
if (_i3.done) break;
_ref = _i3.value;
}
var _ref2 = _ref,
_pointer = _ref2[0],
_interaction2 = _ref2[1];
_interaction2._updateEventTargets(eventTarget, curEventTarget);
_interaction2[method](_pointer, event, eventTarget, curEventTarget);
}
};
}
function endAll(event) {
for (var _i4 = 0; _i4 < scope.interactions.length; _i4++) {
var interaction = scope.interactions[_i4];
interaction.end(event);
signals.fire('endall', { event: event, interaction: interaction });
}
}
var docEvents = {/* 'eventType': listenerFunc */};
var pEventTypes = browser.pEventTypes;
if (scope.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(_ref3, signalName) {
var doc = _ref3.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]);
}
}
signals.on('update-pointer-down', function (_ref4) {
var interaction = _ref4.interaction,
pointer = _ref4.pointer,
pointerId = _ref4.pointerId,
pointerIndex = _ref4.pointerIndex,
event = _ref4.event,
eventTarget = _ref4.eventTarget,
down = _ref4.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":34,"./utils":44,"./utils/Signals":35,"./utils/browser":37,"./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');
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;
}
});
/*\
* Interactable.draggable
[ method ]
*
* Gets or sets whether drag actions can be performed on the
* Interactable
*
= (boolean) Indicates if this can be the target of drag events
| var isDraggable = interact('ul li').draggable();
* or
- options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)
= (object) This Interactable
| 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
| });
\*/
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');
var interact = require('../interact');
var InteractEvent = require('../InteractEvent');
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, interaction.element);
}
var dragEvent = interaction.prevEvent;
var dropEvents = getDropEvents(interaction, event, dragEvent);
if (dropEvents.activate) {
fireActiveDrops(interaction, 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 = interaction.activeDrops.elements = interaction.activeDrops.rects = interaction.dropEvents = null;
});
function collectDrops(interaction, element) {
var drops = [];
var elements = [];
element = element || interaction.element;
// collect all dropzones and their elements which qualify for a drop
for (var _iterator = scope.interactables, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) {
var _ref6;
if (_isArray) {
if (_i >= _iterator.length) break;
_ref6 = _iterator[_i++];
} else {
_i = _iterator.next();
if (_i.done) break;
_ref6 = _i.value;
}
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 i = 0; i < dropElements.length; i++) {
var currentElement = dropElements[i];
if (currentElement !== element) {
drops.push(current);
elements.push(currentElement);
}
}
}
return {
elements: elements,
dropzones: drops
};
}
function fireActiveDrops(interaction, event) {
var prevElement = void 0;
// loop through all active dropzones and trigger event
for (var i = 0; i < interaction.activeDrops.dropzones.length; i++) {
var current = interaction.activeDrops.dropzones[i];
var currentElement = interaction.activeDrops.elements[i];
// prevent trigger of duplicate events on same element
if (currentElement !== prevElement) {