toloframework
Version:
Javascript/HTML/CSS compiler for Firefox OS or nodewebkit apps using modules in the nodejs style.
1,452 lines (1,169 loc) • 227 kB
JavaScript
/**
* interact.js v1.2.6
*
* Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
* Open source under the MIT License.
* https://raw.github.com/taye/interact.js/master/LICENSE
*/
(function (realWindow) {
'use strict';
// return early if there's no window to work with (eg. Node.js)
if (!realWindow) { return; }
var // get wrapped window if using Shadow DOM polyfill
window = (function () {
// create a TextNode
var el = realWindow.document.createTextNode('');
// check if it's wrapped by a polyfill
if (el.ownerDocument !== realWindow.document
&& typeof realWindow.wrap === 'function'
&& realWindow.wrap(el) === el) {
// return wrapped window
return realWindow.wrap(realWindow);
}
// no Shadow DOM polyfil or native implementation
return realWindow;
}()),
document = window.document,
DocumentFragment = window.DocumentFragment || blank,
SVGElement = window.SVGElement || blank,
SVGSVGElement = window.SVGSVGElement || blank,
SVGElementInstance = window.SVGElementInstance || blank,
HTMLElement = window.HTMLElement || window.Element,
PointerEvent = (window.PointerEvent || window.MSPointerEvent),
pEventTypes,
hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); },
tmpXY = {}, // reduce object creation in getXY()
documents = [], // all documents being listened to
interactables = [], // all set interactables
interactions = [], // all interactions
dynamicDrop = false,
// {
// type: {
// selectors: ['selector', ...],
// contexts : [document, ...],
// listeners: [[listener, useCapture], ...]
// }
// }
delegatedEvents = {},
defaultOptions = {
base: {
accept : null,
actionChecker : null,
styleCursor : true,
preventDefault: 'auto',
origin : { x: 0, y: 0 },
deltaSource : 'page',
allowFrom : null,
ignoreFrom : null,
_context : document,
dropChecker : null
},
drag: {
enabled: false,
manualStart: true,
max: Infinity,
maxPerElement: 1,
snap: null,
restrict: null,
inertia: null,
autoScroll: null,
axis: 'xy'
},
drop: {
enabled: false,
accept: null,
overlap: 'pointer'
},
resize: {
enabled: false,
manualStart: false,
max: Infinity,
maxPerElement: 1,
snap: null,
restrict: null,
inertia: null,
autoScroll: null,
square: false,
preserveAspectRatio: false,
axis: 'xy',
// use default margin
margin: NaN,
// object with props left, right, top, bottom which are
// true/false values to resize when the pointer is over that edge,
// CSS selectors to match the handles for each direction
// or the Elements for each handle
edges: null,
// a value of 'none' will limit the resize rect to a minimum of 0x0
// 'negate' will alow the rect to have negative width/height
// 'reposition' will keep the width/height positive by swapping
// the top and bottom edges and/or swapping the left and right edges
invert: 'none'
},
gesture: {
manualStart: false,
enabled: false,
max: Infinity,
maxPerElement: 1,
restrict: null
},
perAction: {
manualStart: false,
max: Infinity,
maxPerElement: 1,
snap: {
enabled : false,
endOnly : false,
range : Infinity,
targets : null,
offsets : null,
relativePoints: null
},
restrict: {
enabled: false,
endOnly: false
},
autoScroll: {
enabled : false,
container : null, // the item that is scrolled (Window or HTMLElement)
margin : 60,
speed : 300 // the scroll speed in pixels per second
},
inertia: {
enabled : false,
resistance : 10, // the lambda in exponential decay
minSpeed : 100, // target speed must be above this for inertia to start
endSpeed : 10, // the speed at which inertia is slow enough to stop
allowResume : true, // allow resuming an action in inertia phase
zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0
smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia
}
},
_holdDuration: 600
},
// Things related to autoScroll
autoScroll = {
interaction: null,
i: null, // the handle returned by window.setInterval
x: 0, y: 0, // Direction each pulse is to scroll in
// scroll the window by the values in scroll.x/y
scroll: function () {
var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,
container = options.container || getWindow(autoScroll.interaction.element),
now = new Date().getTime(),
// change in time in seconds
dtx = (now - autoScroll.prevTimeX) / 1000,
dty = (now - autoScroll.prevTimeY) / 1000,
vx, vy, sx, sy;
// displacement
if (options.velocity) {
vx = options.velocity.x;
vy = options.velocity.y;
}
else {
vx = vy = options.speed
}
sx = vx * dtx;
sy = vy * dty;
if (sx >= 1 || sy >= 1) {
if (isWindow(container)) {
container.scrollBy(autoScroll.x * sx, autoScroll.y * sy);
}
else if (container) {
container.scrollLeft += autoScroll.x * sx;
container.scrollTop += autoScroll.y * sy;
}
if (sx >=1) autoScroll.prevTimeX = now;
if (sy >= 1) autoScroll.prevTimeY = now;
}
if (autoScroll.isScrolling) {
cancelFrame(autoScroll.i);
autoScroll.i = reqFrame(autoScroll.scroll);
}
},
isScrolling: false,
prevTimeX: 0,
prevTimeY: 0,
start: function (interaction) {
autoScroll.isScrolling = true;
cancelFrame(autoScroll.i);
autoScroll.interaction = interaction;
autoScroll.prevTimeX = new Date().getTime();
autoScroll.prevTimeY = new Date().getTime();
autoScroll.i = reqFrame(autoScroll.scroll);
},
stop: function () {
autoScroll.isScrolling = false;
cancelFrame(autoScroll.i);
}
},
// Does the browser support touch input?
supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
// Does the browser support PointerEvents
supportsPointerEvent = !!PointerEvent,
// Less Precision with touch input
margin = supportsTouch || supportsPointerEvent? 20: 10,
pointerMoveTolerance = 1,
// for ignoring browser's simulated mouse events
prevTouchTime = 0,
// Allow this many interactions to happen simultaneously
maxInteractions = Infinity,
// Check if is IE9 or older
actionCursors = (document.all && !window.atob) ? {
drag : 'move',
resizex : 'e-resize',
resizey : 's-resize',
resizexy: 'se-resize',
resizetop : 'n-resize',
resizeleft : 'w-resize',
resizebottom : 's-resize',
resizeright : 'e-resize',
resizetopleft : 'se-resize',
resizebottomright: 'se-resize',
resizetopright : 'ne-resize',
resizebottomleft : 'ne-resize',
gesture : ''
} : {
drag : 'move',
resizex : 'ew-resize',
resizey : 'ns-resize',
resizexy: 'nwse-resize',
resizetop : 'ns-resize',
resizeleft : 'ew-resize',
resizebottom : 'ns-resize',
resizeright : 'ew-resize',
resizetopleft : 'nwse-resize',
resizebottomright: 'nwse-resize',
resizetopright : 'nesw-resize',
resizebottomleft : 'nesw-resize',
gesture : ''
},
actionIsEnabled = {
drag : true,
resize : true,
gesture: true
},
// because Webkit and Opera still use 'mousewheel' event type
wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel',
eventTypes = [
'dragstart',
'dragmove',
'draginertiastart',
'dragend',
'dragenter',
'dragleave',
'dropactivate',
'dropdeactivate',
'dropmove',
'drop',
'resizestart',
'resizemove',
'resizeinertiastart',
'resizeend',
'gesturestart',
'gesturemove',
'gestureinertiastart',
'gestureend',
'down',
'move',
'up',
'cancel',
'tap',
'doubletap',
'hold'
],
globalEvents = {},
// Opera Mobile must be handled differently
isOperaMobile = navigator.appName == 'Opera' &&
supportsTouch &&
navigator.userAgent.match('Presto'),
// scrolling doesn't change the result of getClientRects on iOS 7
isIOS7 = (/iP(hone|od|ad)/.test(navigator.platform)
&& /OS 7[^\d]/.test(navigator.appVersion)),
// prefix matchesSelector
prefixedMatchesSelector = 'matches' in Element.prototype?
'matches': 'webkitMatchesSelector' in Element.prototype?
'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?
'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?
'oMatchesSelector': 'msMatchesSelector',
// will be polyfill function if browser is IE8
ie8MatchesSelector,
// native requestAnimationFrame or polyfill
reqFrame = realWindow.requestAnimationFrame,
cancelFrame = realWindow.cancelAnimationFrame,
// Events wrapper
events = (function () {
var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),
addEvent = useAttachEvent? 'attachEvent': 'addEventListener',
removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener',
on = useAttachEvent? 'on': '',
elements = [],
targets = [],
attachedListeners = [];
function add (element, type, listener, useCapture) {
var elementIndex = indexOf(elements, element),
target = targets[elementIndex];
if (!target) {
target = {
events: {},
typeCount: 0
};
elementIndex = elements.push(element) - 1;
targets.push(target);
attachedListeners.push((useAttachEvent ? {
supplied: [],
wrapped : [],
useCount: []
} : null));
}
if (!target.events[type]) {
target.events[type] = [];
target.typeCount++;
}
if (!contains(target.events[type], listener)) {
var ret;
if (useAttachEvent) {
var listeners = attachedListeners[elementIndex],
listenerIndex = indexOf(listeners.supplied, listener);
var wrapped = listeners.wrapped[listenerIndex] || function (event) {
if (!event.immediatePropagationStopped) {
event.target = event.srcElement;
event.currentTarget = element;
event.preventDefault = event.preventDefault || preventDef;
event.stopPropagation = event.stopPropagation || stopProp;
event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;
if (/mouse|click/.test(event.type)) {
event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;
event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;
}
listener(event);
}
};
ret = element[addEvent](on + type, wrapped, Boolean(useCapture));
if (listenerIndex === -1) {
listeners.supplied.push(listener);
listeners.wrapped.push(wrapped);
listeners.useCount.push(1);
}
else {
listeners.useCount[listenerIndex]++;
}
}
else {
ret = element[addEvent](type, listener, useCapture || false);
}
target.events[type].push(listener);
return ret;
}
}
function remove (element, type, listener, useCapture) {
var i,
elementIndex = indexOf(elements, element),
target = targets[elementIndex],
listeners,
listenerIndex,
wrapped = listener;
if (!target || !target.events) {
return;
}
if (useAttachEvent) {
listeners = attachedListeners[elementIndex];
listenerIndex = indexOf(listeners.supplied, listener);
wrapped = listeners.wrapped[listenerIndex];
}
if (type === 'all') {
for (type in target.events) {
if (target.events.hasOwnProperty(type)) {
remove(element, type, 'all');
}
}
return;
}
if (target.events[type]) {
var len = target.events[type].length;
if (listener === 'all') {
for (i = 0; i < len; i++) {
remove(element, type, target.events[type][i], Boolean(useCapture));
}
return;
} else {
for (i = 0; i < len; i++) {
if (target.events[type][i] === listener) {
element[removeEvent](on + type, wrapped, useCapture || false);
target.events[type].splice(i, 1);
if (useAttachEvent && listeners) {
listeners.useCount[listenerIndex]--;
if (listeners.useCount[listenerIndex] === 0) {
listeners.supplied.splice(listenerIndex, 1);
listeners.wrapped.splice(listenerIndex, 1);
listeners.useCount.splice(listenerIndex, 1);
}
}
break;
}
}
}
if (target.events[type] && target.events[type].length === 0) {
target.events[type] = null;
target.typeCount--;
}
}
if (!target.typeCount) {
targets.splice(elementIndex, 1);
elements.splice(elementIndex, 1);
attachedListeners.splice(elementIndex, 1);
}
}
function preventDef () {
this.returnValue = false;
}
function stopProp () {
this.cancelBubble = true;
}
function stopImmProp () {
this.cancelBubble = true;
this.immediatePropagationStopped = true;
}
return {
add: add,
remove: remove,
useAttachEvent: useAttachEvent,
_elements: elements,
_targets: targets,
_attachedListeners: attachedListeners
};
}());
function blank () {}
function isElement (o) {
if (!o || (typeof o !== 'object')) { return false; }
var _window = getWindow(o) || window;
return (/object|function/.test(typeof _window.Element)
? o instanceof _window.Element //DOM2
: o.nodeType === 1 && typeof o.nodeName === "string");
}
function isWindow (thing) { return thing === window || !!(thing && thing.Window) && (thing instanceof thing.Window); }
function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; }
function isArray (thing) {
return isObject(thing)
&& (typeof thing.length !== undefined)
&& isFunction(thing.splice);
}
function isObject (thing) { return !!thing && (typeof thing === 'object'); }
function isFunction (thing) { return typeof thing === 'function'; }
function isNumber (thing) { return typeof thing === 'number' ; }
function isBool (thing) { return typeof thing === 'boolean' ; }
function isString (thing) { return typeof thing === 'string' ; }
function trySelector (value) {
if (!isString(value)) { return false; }
// an exception will be raised if it is invalid
document.querySelector(value);
return true;
}
function extend (dest, source) {
for (var prop in source) {
dest[prop] = source[prop];
}
return dest;
}
var prefixedPropREs = {
webkit: /(Movement[XY]|Radius[XY]|RotationAngle|Force)$/
};
function pointerExtend (dest, source) {
for (var prop in source) {
var deprecated = false;
// skip deprecated prefixed properties
for (var vendor in prefixedPropREs) {
if (prop.indexOf(vendor) === 0 && prefixedPropREs[vendor].test(prop)) {
deprecated = true;
break;
}
}
if (!deprecated) {
dest[prop] = source[prop];
}
}
return dest;
}
function copyCoords (dest, src) {
dest.page = dest.page || {};
dest.page.x = src.page.x;
dest.page.y = src.page.y;
dest.client = dest.client || {};
dest.client.x = src.client.x;
dest.client.y = src.client.y;
dest.timeStamp = src.timeStamp;
}
function setEventXY (targetObj, pointers, interaction) {
var pointer = (pointers.length > 1
? pointerAverage(pointers)
: pointers[0]);
getPageXY(pointer, tmpXY, interaction);
targetObj.page.x = tmpXY.x;
targetObj.page.y = tmpXY.y;
getClientXY(pointer, tmpXY, interaction);
targetObj.client.x = tmpXY.x;
targetObj.client.y = tmpXY.y;
targetObj.timeStamp = new Date().getTime();
}
function setEventDeltas (targetObj, prev, cur) {
targetObj.page.x = cur.page.x - prev.page.x;
targetObj.page.y = cur.page.y - prev.page.y;
targetObj.client.x = cur.client.x - prev.client.x;
targetObj.client.y = cur.client.y - prev.client.y;
targetObj.timeStamp = new Date().getTime() - prev.timeStamp;
// set pointer velocity
var dt = Math.max(targetObj.timeStamp / 1000, 0.001);
targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt;
targetObj.page.vx = targetObj.page.x / dt;
targetObj.page.vy = targetObj.page.y / dt;
targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;
targetObj.client.vx = targetObj.client.x / dt;
targetObj.client.vy = targetObj.client.y / dt;
}
function isNativePointer (pointer) {
return (pointer instanceof window.Event
|| (supportsTouch && window.Touch && pointer instanceof window.Touch));
}
// Get specified X/Y coords for mouse or event.touches[0]
function getXY (type, pointer, xy) {
xy = xy || {};
type = type || 'page';
xy.x = pointer[type + 'X'];
xy.y = pointer[type + 'Y'];
return xy;
}
function getPageXY (pointer, page) {
page = page || {};
// Opera Mobile handles the viewport and scrolling oddly
if (isOperaMobile && isNativePointer(pointer)) {
getXY('screen', pointer, page);
page.x += window.scrollX;
page.y += window.scrollY;
}
else {
getXY('page', pointer, page);
}
return page;
}
function getClientXY (pointer, client) {
client = client || {};
if (isOperaMobile && isNativePointer(pointer)) {
// Opera Mobile handles the viewport and scrolling oddly
getXY('screen', pointer, client);
}
else {
getXY('client', pointer, client);
}
return client;
}
function getScrollXY (win) {
win = win || window;
return {
x: win.scrollX || win.document.documentElement.scrollLeft,
y: win.scrollY || win.document.documentElement.scrollTop
};
}
function getPointerId (pointer) {
return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier;
}
function getActualElement (element) {
return (element instanceof SVGElementInstance
? element.correspondingUseElement
: element);
}
function getWindow (node) {
if (isWindow(node)) {
return node;
}
var rootNode = (node.ownerDocument || node);
return rootNode.defaultView || rootNode.parentWindow || window;
}
function getElementClientRect (element) {
var clientRect = (element instanceof SVGElement
? element.getBoundingClientRect()
: element.getClientRects()[0]);
return clientRect && {
left : clientRect.left,
right : clientRect.right,
top : clientRect.top,
bottom: clientRect.bottom,
width : clientRect.width || clientRect.right - clientRect.left,
height: clientRect.height || clientRect.bottom - clientRect.top
};
}
function getElementRect (element) {
var clientRect = getElementClientRect(element);
if (!isIOS7 && clientRect) {
var scroll = getScrollXY(getWindow(element));
clientRect.left += scroll.x;
clientRect.right += scroll.x;
clientRect.top += scroll.y;
clientRect.bottom += scroll.y;
}
return clientRect;
}
function getTouchPair (event) {
var touches = [];
// array of touches is supplied
if (isArray(event)) {
touches[0] = event[0];
touches[1] = event[1];
}
// an event
else {
if (event.type === 'touchend') {
if (event.touches.length === 1) {
touches[0] = event.touches[0];
touches[1] = event.changedTouches[0];
}
else if (event.touches.length === 0) {
touches[0] = event.changedTouches[0];
touches[1] = event.changedTouches[1];
}
}
else {
touches[0] = event.touches[0];
touches[1] = event.touches[1];
}
}
return touches;
}
function pointerAverage (pointers) {
var average = {
pageX : 0,
pageY : 0,
clientX: 0,
clientY: 0,
screenX: 0,
screenY: 0
};
var prop;
for (var i = 0; i < pointers.length; i++) {
for (prop in average) {
average[prop] += pointers[i][prop];
}
}
for (prop in average) {
average[prop] /= pointers.length;
}
return average;
}
function touchBBox (event) {
if (!event.length && !(event.touches && event.touches.length > 1)) {
return;
}
var touches = getTouchPair(event),
minX = Math.min(touches[0].pageX, touches[1].pageX),
minY = Math.min(touches[0].pageY, touches[1].pageY),
maxX = Math.max(touches[0].pageX, touches[1].pageX),
maxY = Math.max(touches[0].pageY, touches[1].pageY);
return {
x: minX,
y: minY,
left: minX,
top: minY,
width: maxX - minX,
height: maxY - minY
};
}
function touchDistance (event, deltaSource) {
deltaSource = deltaSource || defaultOptions.deltaSource;
var sourceX = deltaSource + 'X',
sourceY = deltaSource + 'Y',
touches = getTouchPair(event);
var dx = touches[0][sourceX] - touches[1][sourceX],
dy = touches[0][sourceY] - touches[1][sourceY];
return hypot(dx, dy);
}
function touchAngle (event, prevAngle, deltaSource) {
deltaSource = deltaSource || defaultOptions.deltaSource;
var sourceX = deltaSource + 'X',
sourceY = deltaSource + 'Y',
touches = getTouchPair(event),
dx = touches[0][sourceX] - touches[1][sourceX],
dy = touches[0][sourceY] - touches[1][sourceY],
angle = 180 * Math.atan(dy / dx) / Math.PI;
if (isNumber(prevAngle)) {
var dr = angle - prevAngle,
drClamped = dr % 360;
if (drClamped > 315) {
angle -= 360 + (angle / 360)|0 * 360;
}
else if (drClamped > 135) {
angle -= 180 + (angle / 360)|0 * 360;
}
else if (drClamped < -315) {
angle += 360 + (angle / 360)|0 * 360;
}
else if (drClamped < -135) {
angle += 180 + (angle / 360)|0 * 360;
}
}
return angle;
}
function getOriginXY (interactable, element) {
var origin = interactable
? interactable.options.origin
: defaultOptions.origin;
if (origin === 'parent') {
origin = parentElement(element);
}
else if (origin === 'self') {
origin = interactable.getRect(element);
}
else if (trySelector(origin)) {
origin = closest(element, origin) || { x: 0, y: 0 };
}
if (isFunction(origin)) {
origin = origin(interactable && element);
}
if (isElement(origin)) {
origin = getElementRect(origin);
}
origin.x = ('x' in origin)? origin.x : origin.left;
origin.y = ('y' in origin)? origin.y : origin.top;
return origin;
}
// http://stackoverflow.com/a/5634528/2280888
function _getQBezierValue(t, p1, p2, p3) {
var iT = 1 - t;
return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
}
function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
return {
x: _getQBezierValue(position, startX, cpX, endX),
y: _getQBezierValue(position, startY, cpY, endY)
};
}
// http://gizma.com/easing/
function easeOutQuad (t, b, c, d) {
t /= d;
return -c * t*(t-2) + b;
}
function nodeContains (parent, child) {
while (child) {
if (child === parent) {
return true;
}
child = child.parentNode;
}
return false;
}
function closest (child, selector) {
var parent = parentElement(child);
while (isElement(parent)) {
if (matchesSelector(parent, selector)) { return parent; }
parent = parentElement(parent);
}
return null;
}
function parentElement (node) {
var parent = node.parentNode;
if (isDocFrag(parent)) {
// skip past #shado-root fragments
while ((parent = parent.host) && isDocFrag(parent)) {}
return parent;
}
return parent;
}
function inContext (interactable, element) {
return interactable._context === element.ownerDocument
|| nodeContains(interactable._context, element);
}
function testIgnore (interactable, interactableElement, element) {
var ignoreFrom = interactable.options.ignoreFrom;
if (!ignoreFrom || !isElement(element)) { return false; }
if (isString(ignoreFrom)) {
return matchesUpTo(element, ignoreFrom, interactableElement);
}
else if (isElement(ignoreFrom)) {
return nodeContains(ignoreFrom, element);
}
return false;
}
function testAllow (interactable, interactableElement, element) {
var allowFrom = interactable.options.allowFrom;
if (!allowFrom) { return true; }
if (!isElement(element)) { return false; }
if (isString(allowFrom)) {
return matchesUpTo(element, allowFrom, interactableElement);
}
else if (isElement(allowFrom)) {
return nodeContains(allowFrom, element);
}
return false;
}
function checkAxis (axis, interactable) {
if (!interactable) { return false; }
var thisAxis = interactable.options.drag.axis;
return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);
}
function checkSnap (interactable, action) {
var options = interactable.options;
if (/^resize/.test(action)) {
action = 'resize';
}
return options[action].snap && options[action].snap.enabled;
}
function checkRestrict (interactable, action) {
var options = interactable.options;
if (/^resize/.test(action)) {
action = 'resize';
}
return options[action].restrict && options[action].restrict.enabled;
}
function checkAutoScroll (interactable, action) {
var options = interactable.options;
if (/^resize/.test(action)) {
action = 'resize';
}
return options[action].autoScroll && options[action].autoScroll.enabled;
}
function withinInteractionLimit (interactable, element, action) {
var options = interactable.options,
maxActions = options[action.name].max,
maxPerElement = options[action.name].maxPerElement,
activeInteractions = 0,
targetCount = 0,
targetElementCount = 0;
for (var i = 0, len = interactions.length; i < len; i++) {
var interaction = interactions[i],
otherAction = interaction.prepared.name,
active = interaction.interacting();
if (!active) { continue; }
activeInteractions++;
if (activeInteractions >= maxInteractions) {
return false;
}
if (interaction.target !== interactable) { continue; }
targetCount += (otherAction === action.name)|0;
if (targetCount >= maxActions) {
return false;
}
if (interaction.element === element) {
targetElementCount++;
if (otherAction !== action.name || targetElementCount >= maxPerElement) {
return false;
}
}
}
return maxInteractions > 0;
}
// Test for the element that's "above" all other qualifiers
function indexOfDeepestElement (elements) {
var dropzone,
deepestZone = elements[0],
index = deepestZone? 0: -1,
parent,
deepestZoneParents = [],
dropzoneParents = [],
child,
i,
n;
for (i = 1; i < elements.length; i++) {
dropzone = elements[i];
// an element might belong to multiple selector dropzones
if (!dropzone || dropzone === deepestZone) {
continue;
}
if (!deepestZone) {
deepestZone = dropzone;
index = i;
continue;
}
// check if the deepest or current are document.documentElement or document.rootElement
// - if the current dropzone is, do nothing and continue
if (dropzone.parentNode === dropzone.ownerDocument) {
continue;
}
// - if deepest is, update with the current dropzone and continue to next
else if (deepestZone.parentNode === dropzone.ownerDocument) {
deepestZone = dropzone;
index = i;
continue;
}
if (!deepestZoneParents.length) {
parent = deepestZone;
while (parent.parentNode && parent.parentNode !== parent.ownerDocument) {
deepestZoneParents.unshift(parent);
parent = parent.parentNode;
}
}
// if this element is an svg element and the current deepest is
// an HTMLElement
if (deepestZone instanceof HTMLElement
&& dropzone instanceof SVGElement
&& !(dropzone instanceof SVGSVGElement)) {
if (dropzone === deepestZone.parentNode) {
continue;
}
parent = dropzone.ownerSVGElement;
}
else {
parent = dropzone;
}
dropzoneParents = [];
while (parent.parentNode !== parent.ownerDocument) {
dropzoneParents.unshift(parent);
parent = parent.parentNode;
}
n = 0;
// get (position of last common ancestor) + 1
while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) {
n++;
}
var parents = [
dropzoneParents[n - 1],
dropzoneParents[n],
deepestZoneParents[n]
];
child = parents[0].lastChild;
while (child) {
if (child === parents[1]) {
deepestZone = dropzone;
index = i;
deepestZoneParents = [];
break;
}
else if (child === parents[2]) {
break;
}
child = child.previousSibling;
}
}
return index;
}
function Interaction () {
this.target = null; // current interactable being interacted with
this.element = null; // the target element of the interactable
this.dropTarget = null; // the dropzone a drag target might be dropped into
this.dropElement = null; // the element at the time of checking
this.prevDropTarget = null; // the dropzone that was recently dragged away from
this.prevDropElement = null; // the element at the time of checking
this.prepared = { // action that's ready to be fired on next move event
name : null,
axis : null,
edges: null
};
this.matches = []; // all selectors that are matched by target element
this.matchElements = []; // corresponding elements
this.inertiaStatus = {
active : false,
smoothEnd : false,
ending : false,
startEvent: null,
upCoords: {},
xe: 0, ye: 0,
sx: 0, sy: 0,
t0: 0,
vx0: 0, vys: 0,
duration: 0,
resumeDx: 0,
resumeDy: 0,
lambda_v0: 0,
one_ve_v0: 0,
i : null
};
if (isFunction(Function.prototype.bind)) {
this.boundInertiaFrame = this.inertiaFrame.bind(this);
this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);
}
else {
var that = this;
this.boundInertiaFrame = function () { return that.inertiaFrame(); };
this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); };
}
this.activeDrops = {
dropzones: [], // the dropzones that are mentioned below
elements : [], // elements of dropzones that accept the target draggable
rects : [] // the rects of the elements mentioned above
};
// keep track of added pointers
this.pointers = [];
this.pointerIds = [];
this.downTargets = [];
this.downTimes = [];
this.holdTimers = [];
// 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.tapTime = 0; // time of the most recent tap event
this.prevTap = null;
this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 };
this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };
this.snapOffsets = [];
this.gesture = {
start: { x: 0, y: 0 },
startDistance: 0, // distance between two touches of touchStart
prevDistance : 0,
distance : 0,
scale: 1, // gesture.distance / gesture.startDistance
startAngle: 0, // angle of line joining two touches
prevAngle : 0 // angle of the previous gesture event
};
this.snapStatus = {
x : 0, y : 0,
dx : 0, dy : 0,
realX : 0, realY : 0,
snappedX: 0, snappedY: 0,
targets : [],
locked : false,
changed : false
};
this.restrictStatus = {
dx : 0, dy : 0,
restrictedX: 0, restrictedY: 0,
snap : null,
restricted : false,
changed : false
};
this.restrictStatus.snap = this.snapStatus;
this.pointerIsDown = false;
this.pointerWasMoved = false;
this.gesturing = false;
this.dragging = false;
this.resizing = false;
this.resizeAxes = 'xy';
this.mouse = false;
interactions.push(this);
}
Interaction.prototype = {
getPageXY : function (pointer, xy) { return getPageXY(pointer, xy, this); },
getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); },
setEventXY : function (target, ptr) { return setEventXY(target, ptr, this); },
pointerOver: function (pointer, event, eventTarget) {
if (this.prepared.name || !this.mouse) { return; }
var curMatches = [],
curMatchElements = [],
prevTargetElement = this.element;
this.addPointer(pointer);
if (this.target
&& (testIgnore(this.target, this.element, eventTarget)
|| !testAllow(this.target, this.element, eventTarget))) {
// if the eventTarget should be ignored or shouldn't be allowed
// clear the previous target
this.target = null;
this.element = null;
this.matches = [];
this.matchElements = [];
}
var elementInteractable = interactables.get(eventTarget),
elementAction = (elementInteractable
&& !testIgnore(elementInteractable, eventTarget, eventTarget)
&& testAllow(elementInteractable, eventTarget, eventTarget)
&& validateAction(
elementInteractable.getAction(pointer, event, this, eventTarget),
elementInteractable));
if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) {
elementAction = null;
}
function pushCurMatches (interactable, selector) {
if (interactable
&& inContext(interactable, eventTarget)
&& !testIgnore(interactable, eventTarget, eventTarget)
&& testAllow(interactable, eventTarget, eventTarget)
&& matchesSelector(eventTarget, selector)) {
curMatches.push(interactable);
curMatchElements.push(eventTarget);
}
}
if (elementAction) {
this.target = elementInteractable;
this.element = eventTarget;
this.matches = [];
this.matchElements = [];
}
else {
interactables.forEachSelector(pushCurMatches);
if (this.validateSelector(pointer, event, curMatches, curMatchElements)) {
this.matches = curMatches;
this.matchElements = curMatchElements;
this.pointerHover(pointer, event, this.matches, this.matchElements);
events.add(eventTarget,
PointerEvent? pEventTypes.move : 'mousemove',
listeners.pointerHover);
}
else if (this.target) {
if (nodeContains(prevTargetElement, eventTarget)) {
this.pointerHover(pointer, event, this.matches, this.matchElements);
events.add(this.element,
PointerEvent? pEventTypes.move : 'mousemove',
listeners.pointerHover);
}
else {
this.target = null;
this.element = null;
this.matches = [];
this.matchElements = [];
}
}
}
},
// Check what action would be performed on pointerMove target if a mouse
// button were pressed and change the cursor accordingly
pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) {
var target = this.target;
if (!this.prepared.name && this.mouse) {
var action;
// update pointer coords for defaultActionChecker to use
this.setEventXY(this.curCoords, [pointer]);
if (matches) {
action = this.validateSelector(pointer, event, matches, matchElements);
}
else if (target) {
action = validateAction(target.getAction(this.pointers[0], event, this, this.element), this.target);
}
if (target && target.options.styleCursor) {
if (action) {
target._doc.documentElement.style.cursor = getActionCursor(action);
}
else {
target._doc.documentElement.style.cursor = '';
}
}
}
else if (this.prepared.name) {
this.checkAndPreventDefault(event, target, this.element);
}
},
pointerOut: function (pointer, event, eventTarget) {
if (this.prepared.name) { return; }
// Remove temporary event listeners for selector Interactables
if (!interactables.get(eventTarget)) {
events.remove(eventTarget,
PointerEvent? pEventTypes.move : 'mousemove',
listeners.pointerHover);
}
if (this.target && this.target.options.styleCursor && !this.interacting()) {
this.target._doc.documentElement.style.cursor = '';
}
},
selectorDown: function (pointer, event, eventTarget, curEventTarget) {
var that = this,
// copy event to be used in timeout for IE8
eventCopy = events.useAttachEvent? extend({}, event) : event,