UNPKG

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
/** * 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,