not really known
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

5831 lines
209 KiB

  1. /**
  2. * interact.js v1.2.4
  3. *
  4. * Copyright (c) 2012-2015 Taye Adeyemi <dev@taye.me>
  5. * Open source under the MIT License.
  6. * https://raw.github.com/taye/interact.js/master/LICENSE
  7. */
  8. (function (realWindow) {
  9. 'use strict';
  10. var // get wrapped window if using Shadow DOM polyfill
  11. window = (function () {
  12. // create a TextNode
  13. var el = realWindow.document.createTextNode('');
  14. // check if it's wrapped by a polyfill
  15. if (el.ownerDocument !== realWindow.document
  16. && typeof realWindow.wrap === 'function'
  17. && realWindow.wrap(el) === el) {
  18. // return wrapped window
  19. return realWindow.wrap(realWindow);
  20. }
  21. // no Shadow DOM polyfil or native implementation
  22. return realWindow;
  23. }()),
  24. document = window.document,
  25. DocumentFragment = window.DocumentFragment || blank,
  26. SVGElement = window.SVGElement || blank,
  27. SVGSVGElement = window.SVGSVGElement || blank,
  28. SVGElementInstance = window.SVGElementInstance || blank,
  29. HTMLElement = window.HTMLElement || window.Element,
  30. PointerEvent = (window.PointerEvent || window.MSPointerEvent),
  31. pEventTypes,
  32. hypot = Math.hypot || function (x, y) { return Math.sqrt(x * x + y * y); },
  33. tmpXY = {}, // reduce object creation in getXY()
  34. documents = [], // all documents being listened to
  35. interactables = [], // all set interactables
  36. interactions = [], // all interactions
  37. dynamicDrop = false,
  38. // {
  39. // type: {
  40. // selectors: ['selector', ...],
  41. // contexts : [document, ...],
  42. // listeners: [[listener, useCapture], ...]
  43. // }
  44. // }
  45. delegatedEvents = {},
  46. defaultOptions = {
  47. base: {
  48. accept : null,
  49. actionChecker : null,
  50. styleCursor : true,
  51. preventDefault: 'auto',
  52. origin : { x: 0, y: 0 },
  53. deltaSource : 'page',
  54. allowFrom : null,
  55. ignoreFrom : null,
  56. _context : document,
  57. dropChecker : null
  58. },
  59. drag: {
  60. enabled: false,
  61. manualStart: true,
  62. max: Infinity,
  63. maxPerElement: 1,
  64. snap: null,
  65. restrict: null,
  66. inertia: null,
  67. autoScroll: null,
  68. axis: 'xy',
  69. },
  70. drop: {
  71. enabled: false,
  72. accept: null,
  73. overlap: 'pointer'
  74. },
  75. resize: {
  76. enabled: false,
  77. manualStart: false,
  78. max: Infinity,
  79. maxPerElement: 1,
  80. snap: null,
  81. restrict: null,
  82. inertia: null,
  83. autoScroll: null,
  84. square: false,
  85. axis: 'xy',
  86. // object with props left, right, top, bottom which are
  87. // true/false values to resize when the pointer is over that edge,
  88. // CSS selectors to match the handles for each direction
  89. // or the Elements for each handle
  90. edges: null,
  91. // a value of 'none' will limit the resize rect to a minimum of 0x0
  92. // 'negate' will alow the rect to have negative width/height
  93. // 'reposition' will keep the width/height positive by swapping
  94. // the top and bottom edges and/or swapping the left and right edges
  95. invert: 'none'
  96. },
  97. gesture: {
  98. manualStart: false,
  99. enabled: false,
  100. max: Infinity,
  101. maxPerElement: 1,
  102. restrict: null
  103. },
  104. perAction: {
  105. manualStart: false,
  106. max: Infinity,
  107. maxPerElement: 1,
  108. snap: {
  109. enabled : false,
  110. endOnly : false,
  111. range : Infinity,
  112. targets : null,
  113. offsets : null,
  114. relativePoints: null
  115. },
  116. restrict: {
  117. enabled: false,
  118. endOnly: false
  119. },
  120. autoScroll: {
  121. enabled : false,
  122. container : null, // the item that is scrolled (Window or HTMLElement)
  123. margin : 60,
  124. speed : 300 // the scroll speed in pixels per second
  125. },
  126. inertia: {
  127. enabled : false,
  128. resistance : 10, // the lambda in exponential decay
  129. minSpeed : 100, // target speed must be above this for inertia to start
  130. endSpeed : 10, // the speed at which inertia is slow enough to stop
  131. allowResume : true, // allow resuming an action in inertia phase
  132. zeroResumeDelta : true, // if an action is resumed after launch, set dx/dy to 0
  133. smoothEndDuration: 300 // animate to snap/restrict endOnly if there's no inertia
  134. }
  135. },
  136. _holdDuration: 600
  137. },
  138. // Things related to autoScroll
  139. autoScroll = {
  140. interaction: null,
  141. i: null, // the handle returned by window.setInterval
  142. x: 0, y: 0, // Direction each pulse is to scroll in
  143. // scroll the window by the values in scroll.x/y
  144. scroll: function () {
  145. var options = autoScroll.interaction.target.options[autoScroll.interaction.prepared.name].autoScroll,
  146. container = options.container || getWindow(autoScroll.interaction.element),
  147. now = new Date().getTime(),
  148. // change in time in seconds
  149. dt = (now - autoScroll.prevTime) / 1000,
  150. // displacement
  151. s = options.speed * dt;
  152. if (s >= 1) {
  153. if (isWindow(container)) {
  154. container.scrollBy(autoScroll.x * s, autoScroll.y * s);
  155. }
  156. else if (container) {
  157. container.scrollLeft += autoScroll.x * s;
  158. container.scrollTop += autoScroll.y * s;
  159. }
  160. autoScroll.prevTime = now;
  161. }
  162. if (autoScroll.isScrolling) {
  163. cancelFrame(autoScroll.i);
  164. autoScroll.i = reqFrame(autoScroll.scroll);
  165. }
  166. },
  167. edgeMove: function (event) {
  168. var interaction,
  169. target,
  170. doAutoscroll = false;
  171. for (var i = 0; i < interactions.length; i++) {
  172. interaction = interactions[i];
  173. if (interaction.interacting()
  174. && checkAutoScroll(interaction.target, interaction.prepared.name)) {
  175. target = interaction.target;
  176. doAutoscroll = true;
  177. break;
  178. }
  179. }
  180. if (!doAutoscroll) { return; }
  181. var top,
  182. right,
  183. bottom,
  184. left,
  185. options = target.options[interaction.prepared.name].autoScroll,
  186. container = options.container || getWindow(interaction.element);
  187. if (isWindow(container)) {
  188. left = event.clientX < autoScroll.margin;
  189. top = event.clientY < autoScroll.margin;
  190. right = event.clientX > container.innerWidth - autoScroll.margin;
  191. bottom = event.clientY > container.innerHeight - autoScroll.margin;
  192. }
  193. else {
  194. var rect = getElementRect(container);
  195. left = event.clientX < rect.left + autoScroll.margin;
  196. top = event.clientY < rect.top + autoScroll.margin;
  197. right = event.clientX > rect.right - autoScroll.margin;
  198. bottom = event.clientY > rect.bottom - autoScroll.margin;
  199. }
  200. autoScroll.x = (right ? 1: left? -1: 0);
  201. autoScroll.y = (bottom? 1: top? -1: 0);
  202. if (!autoScroll.isScrolling) {
  203. // set the autoScroll properties to those of the target
  204. autoScroll.margin = options.margin;
  205. autoScroll.speed = options.speed;
  206. autoScroll.start(interaction);
  207. }
  208. },
  209. isScrolling: false,
  210. prevTime: 0,
  211. start: function (interaction) {
  212. autoScroll.isScrolling = true;
  213. cancelFrame(autoScroll.i);
  214. autoScroll.interaction = interaction;
  215. autoScroll.prevTime = new Date().getTime();
  216. autoScroll.i = reqFrame(autoScroll.scroll);
  217. },
  218. stop: function () {
  219. autoScroll.isScrolling = false;
  220. cancelFrame(autoScroll.i);
  221. }
  222. },
  223. // Does the browser support touch input?
  224. supportsTouch = (('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch),
  225. // Does the browser support PointerEvents
  226. supportsPointerEvent = !!PointerEvent,
  227. // Less Precision with touch input
  228. margin = supportsTouch || supportsPointerEvent? 20: 10,
  229. pointerMoveTolerance = 1,
  230. // for ignoring browser's simulated mouse events
  231. prevTouchTime = 0,
  232. // Allow this many interactions to happen simultaneously
  233. maxInteractions = Infinity,
  234. // Check if is IE9 or older
  235. actionCursors = (document.all && !window.atob) ? {
  236. drag : 'move',
  237. resizex : 'e-resize',
  238. resizey : 's-resize',
  239. resizexy: 'se-resize',
  240. resizetop : 'n-resize',
  241. resizeleft : 'w-resize',
  242. resizebottom : 's-resize',
  243. resizeright : 'e-resize',
  244. resizetopleft : 'se-resize',
  245. resizebottomright: 'se-resize',
  246. resizetopright : 'ne-resize',
  247. resizebottomleft : 'ne-resize',
  248. gesture : ''
  249. } : {
  250. drag : 'move',
  251. resizex : 'ew-resize',
  252. resizey : 'ns-resize',
  253. resizexy: 'nwse-resize',
  254. resizetop : 'ns-resize',
  255. resizeleft : 'ew-resize',
  256. resizebottom : 'ns-resize',
  257. resizeright : 'ew-resize',
  258. resizetopleft : 'nwse-resize',
  259. resizebottomright: 'nwse-resize',
  260. resizetopright : 'nesw-resize',
  261. resizebottomleft : 'nesw-resize',
  262. gesture : ''
  263. },
  264. actionIsEnabled = {
  265. drag : true,
  266. resize : true,
  267. gesture: true
  268. },
  269. // because Webkit and Opera still use 'mousewheel' event type
  270. wheelEvent = 'onmousewheel' in document? 'mousewheel': 'wheel',
  271. eventTypes = [
  272. 'dragstart',
  273. 'dragmove',
  274. 'draginertiastart',
  275. 'dragend',
  276. 'dragenter',
  277. 'dragleave',
  278. 'dropactivate',
  279. 'dropdeactivate',
  280. 'dropmove',
  281. 'drop',
  282. 'resizestart',
  283. 'resizemove',
  284. 'resizeinertiastart',
  285. 'resizeend',
  286. 'gesturestart',
  287. 'gesturemove',
  288. 'gestureinertiastart',
  289. 'gestureend',
  290. 'down',
  291. 'move',
  292. 'up',
  293. 'cancel',
  294. 'tap',
  295. 'doubletap',
  296. 'hold'
  297. ],
  298. globalEvents = {},
  299. // Opera Mobile must be handled differently
  300. isOperaMobile = navigator.appName == 'Opera' &&
  301. supportsTouch &&
  302. navigator.userAgent.match('Presto'),
  303. // scrolling doesn't change the result of
  304. // getBoundingClientRect/getClientRects on iOS <=7 but it does on iOS 8
  305. isIOS7orLower = (/iP(hone|od|ad)/.test(navigator.platform)
  306. && /OS [1-7][^\d]/.test(navigator.appVersion)),
  307. // prefix matchesSelector
  308. prefixedMatchesSelector = 'matches' in Element.prototype?
  309. 'matches': 'webkitMatchesSelector' in Element.prototype?
  310. 'webkitMatchesSelector': 'mozMatchesSelector' in Element.prototype?
  311. 'mozMatchesSelector': 'oMatchesSelector' in Element.prototype?
  312. 'oMatchesSelector': 'msMatchesSelector',
  313. // will be polyfill function if browser is IE8
  314. ie8MatchesSelector,
  315. // native requestAnimationFrame or polyfill
  316. reqFrame = realWindow.requestAnimationFrame,
  317. cancelFrame = realWindow.cancelAnimationFrame,
  318. // Events wrapper
  319. events = (function () {
  320. var useAttachEvent = ('attachEvent' in window) && !('addEventListener' in window),
  321. addEvent = useAttachEvent? 'attachEvent': 'addEventListener',
  322. removeEvent = useAttachEvent? 'detachEvent': 'removeEventListener',
  323. on = useAttachEvent? 'on': '',
  324. elements = [],
  325. targets = [],
  326. attachedListeners = [];
  327. function add (element, type, listener, useCapture) {
  328. var elementIndex = indexOf(elements, element),
  329. target = targets[elementIndex];
  330. if (!target) {
  331. target = {
  332. events: {},
  333. typeCount: 0
  334. };
  335. elementIndex = elements.push(element) - 1;
  336. targets.push(target);
  337. attachedListeners.push((useAttachEvent ? {
  338. supplied: [],
  339. wrapped : [],
  340. useCount: []
  341. } : null));
  342. }
  343. if (!target.events[type]) {
  344. target.events[type] = [];
  345. target.typeCount++;
  346. }
  347. if (!contains(target.events[type], listener)) {
  348. var ret;
  349. if (useAttachEvent) {
  350. var listeners = attachedListeners[elementIndex],
  351. listenerIndex = indexOf(listeners.supplied, listener);
  352. var wrapped = listeners.wrapped[listenerIndex] || function (event) {
  353. if (!event.immediatePropagationStopped) {
  354. event.target = event.srcElement;
  355. event.currentTarget = element;
  356. event.preventDefault = event.preventDefault || preventDef;
  357. event.stopPropagation = event.stopPropagation || stopProp;
  358. event.stopImmediatePropagation = event.stopImmediatePropagation || stopImmProp;
  359. if (/mouse|click/.test(event.type)) {
  360. event.pageX = event.clientX + getWindow(element).document.documentElement.scrollLeft;
  361. event.pageY = event.clientY + getWindow(element).document.documentElement.scrollTop;
  362. }
  363. listener(event);
  364. }
  365. };
  366. ret = element[addEvent](on + type, wrapped, Boolean(useCapture));
  367. if (listenerIndex === -1) {
  368. listeners.supplied.push(listener);
  369. listeners.wrapped.push(wrapped);
  370. listeners.useCount.push(1);
  371. }
  372. else {
  373. listeners.useCount[listenerIndex]++;
  374. }
  375. }
  376. else {
  377. ret = element[addEvent](type, listener, useCapture || false);
  378. }
  379. target.events[type].push(listener);
  380. return ret;
  381. }
  382. }
  383. function remove (element, type, listener, useCapture) {
  384. var i,
  385. elementIndex = indexOf(elements, element),
  386. target = targets[elementIndex],
  387. listeners,
  388. listenerIndex,
  389. wrapped = listener;
  390. if (!target || !target.events) {
  391. return;
  392. }
  393. if (useAttachEvent) {
  394. listeners = attachedListeners[elementIndex];
  395. listenerIndex = indexOf(listeners.supplied, listener);
  396. wrapped = listeners.wrapped[listenerIndex];
  397. }
  398. if (type === 'all') {
  399. for (type in target.events) {
  400. if (target.events.hasOwnProperty(type)) {
  401. remove(element, type, 'all');
  402. }
  403. }
  404. return;
  405. }
  406. if (target.events[type]) {
  407. var len = target.events[type].length;
  408. if (listener === 'all') {
  409. for (i = 0; i < len; i++) {
  410. remove(element, type, target.events[type][i], Boolean(useCapture));
  411. }
  412. } else {
  413. for (i = 0; i < len; i++) {
  414. if (target.events[type][i] === listener) {
  415. element[removeEvent](on + type, wrapped, useCapture || false);
  416. target.events[type].splice(i, 1);
  417. if (useAttachEvent && listeners) {
  418. listeners.useCount[listenerIndex]--;
  419. if (listeners.useCount[listenerIndex] === 0) {
  420. listeners.supplied.splice(listenerIndex, 1);
  421. listeners.wrapped.splice(listenerIndex, 1);
  422. listeners.useCount.splice(listenerIndex, 1);
  423. }
  424. }
  425. break;
  426. }
  427. }
  428. }
  429. if (target.events[type] && target.events[type].length === 0) {
  430. target.events[type] = null;
  431. target.typeCount--;
  432. }
  433. }
  434. if (!target.typeCount) {
  435. targets.splice(elementIndex);
  436. elements.splice(elementIndex);
  437. attachedListeners.splice(elementIndex);
  438. }
  439. }
  440. function preventDef () {
  441. this.returnValue = false;
  442. }
  443. function stopProp () {
  444. this.cancelBubble = true;
  445. }
  446. function stopImmProp () {
  447. this.cancelBubble = true;
  448. this.immediatePropagationStopped = true;
  449. }
  450. return {
  451. add: add,
  452. remove: remove,
  453. useAttachEvent: useAttachEvent,
  454. _elements: elements,
  455. _targets: targets,
  456. _attachedListeners: attachedListeners
  457. };
  458. }());
  459. function blank () {}
  460. function isElement (o) {
  461. if (!o || (typeof o !== 'object')) { return false; }
  462. var _window = getWindow(o) || window;
  463. return (/object|function/.test(typeof _window.Element)
  464. ? o instanceof _window.Element //DOM2
  465. : o.nodeType === 1 && typeof o.nodeName === "string");
  466. }
  467. function isWindow (thing) { return !!(thing && thing.Window) && (thing instanceof thing.Window); }
  468. function isDocFrag (thing) { return !!thing && thing instanceof DocumentFragment; }
  469. function isArray (thing) {
  470. return isObject(thing)
  471. && (typeof thing.length !== undefined)
  472. && isFunction(thing.splice);
  473. }
  474. function isObject (thing) { return !!thing && (typeof thing === 'object'); }
  475. function isFunction (thing) { return typeof thing === 'function'; }
  476. function isNumber (thing) { return typeof thing === 'number' ; }
  477. function isBool (thing) { return typeof thing === 'boolean' ; }
  478. function isString (thing) { return typeof thing === 'string' ; }
  479. function trySelector (value) {
  480. if (!isString(value)) { return false; }
  481. // an exception will be raised if it is invalid
  482. document.querySelector(value);
  483. return true;
  484. }
  485. function extend (dest, source) {
  486. for (var prop in source) {
  487. dest[prop] = source[prop];
  488. }
  489. return dest;
  490. }
  491. function copyCoords (dest, src) {
  492. dest.page = dest.page || {};
  493. dest.page.x = src.page.x;
  494. dest.page.y = src.page.y;
  495. dest.client = dest.client || {};
  496. dest.client.x = src.client.x;
  497. dest.client.y = src.client.y;
  498. dest.timeStamp = src.timeStamp;
  499. }
  500. function setEventXY (targetObj, pointer, interaction) {
  501. if (!pointer) {
  502. if (interaction.pointerIds.length > 1) {
  503. pointer = touchAverage(interaction.pointers);
  504. }
  505. else {
  506. pointer = interaction.pointers[0];
  507. }
  508. }
  509. getPageXY(pointer, tmpXY, interaction);
  510. targetObj.page.x = tmpXY.x;
  511. targetObj.page.y = tmpXY.y;
  512. getClientXY(pointer, tmpXY, interaction);
  513. targetObj.client.x = tmpXY.x;
  514. targetObj.client.y = tmpXY.y;
  515. targetObj.timeStamp = new Date().getTime();
  516. }
  517. function setEventDeltas (targetObj, prev, cur) {
  518. targetObj.page.x = cur.page.x - prev.page.x;
  519. targetObj.page.y = cur.page.y - prev.page.y;
  520. targetObj.client.x = cur.client.x - prev.client.x;
  521. targetObj.client.y = cur.client.y - prev.client.y;
  522. targetObj.timeStamp = new Date().getTime() - prev.timeStamp;
  523. // set pointer velocity
  524. var dt = Math.max(targetObj.timeStamp / 1000, 0.001);
  525. targetObj.page.speed = hypot(targetObj.page.x, targetObj.page.y) / dt;
  526. targetObj.page.vx = targetObj.page.x / dt;
  527. targetObj.page.vy = targetObj.page.y / dt;
  528. targetObj.client.speed = hypot(targetObj.client.x, targetObj.page.y) / dt;
  529. targetObj.client.vx = targetObj.client.x / dt;
  530. targetObj.client.vy = targetObj.client.y / dt;
  531. }
  532. // Get specified X/Y coords for mouse or event.touches[0]
  533. function getXY (type, pointer, xy) {
  534. xy = xy || {};
  535. type = type || 'page';
  536. xy.x = pointer[type + 'X'];
  537. xy.y = pointer[type + 'Y'];
  538. return xy;
  539. }
  540. function getPageXY (pointer, page, interaction) {
  541. page = page || {};
  542. if (pointer instanceof InteractEvent) {
  543. if (/inertiastart/.test(pointer.type)) {
  544. interaction = interaction || pointer.interaction;
  545. extend(page, interaction.inertiaStatus.upCoords.page);
  546. page.x += interaction.inertiaStatus.sx;
  547. page.y += interaction.inertiaStatus.sy;
  548. }
  549. else {
  550. page.x = pointer.pageX;
  551. page.y = pointer.pageY;
  552. }
  553. }
  554. // Opera Mobile handles the viewport and scrolling oddly
  555. else if (isOperaMobile) {
  556. getXY('screen', pointer, page);
  557. page.x += window.scrollX;
  558. page.y += window.scrollY;
  559. }
  560. else {
  561. getXY('page', pointer, page);
  562. }
  563. return page;
  564. }
  565. function getClientXY (pointer, client, interaction) {
  566. client = client || {};
  567. if (pointer instanceof InteractEvent) {
  568. if (/inertiastart/.test(pointer.type)) {
  569. extend(client, interaction.inertiaStatus.upCoords.client);
  570. client.x += interaction.inertiaStatus.sx;
  571. client.y += interaction.inertiaStatus.sy;
  572. }
  573. else {
  574. client.x = pointer.clientX;
  575. client.y = pointer.clientY;
  576. }
  577. }
  578. else {
  579. // Opera Mobile handles the viewport and scrolling oddly
  580. getXY(isOperaMobile? 'screen': 'client', pointer, client);
  581. }
  582. return client;
  583. }
  584. function getScrollXY (win) {
  585. win = win || window;
  586. return {
  587. x: win.scrollX || win.document.documentElement.scrollLeft,
  588. y: win.scrollY || win.document.documentElement.scrollTop
  589. };
  590. }
  591. function getPointerId (pointer) {
  592. return isNumber(pointer.pointerId)? pointer.pointerId : pointer.identifier;
  593. }
  594. function getActualElement (element) {
  595. return (element instanceof SVGElementInstance
  596. ? element.correspondingUseElement
  597. : element);
  598. }
  599. function getWindow (node) {
  600. if (isWindow(node)) {
  601. return node;
  602. }
  603. var rootNode = (node.ownerDocument || node);
  604. return rootNode.defaultView || rootNode.parentWindow || window;
  605. }
  606. function getElementRect (element) {
  607. var scroll = isIOS7orLower
  608. ? { x: 0, y: 0 }
  609. : getScrollXY(getWindow(element)),
  610. clientRect = (element instanceof SVGElement)?
  611. element.getBoundingClientRect():
  612. element.getClientRects()[0];
  613. return clientRect && {
  614. left : clientRect.left + scroll.x,
  615. right : clientRect.right + scroll.x,
  616. top : clientRect.top + scroll.y,
  617. bottom: clientRect.bottom + scroll.y,
  618. width : clientRect.width || clientRect.right - clientRect.left,
  619. height: clientRect.heigh || clientRect.bottom - clientRect.top
  620. };
  621. }
  622. function getTouchPair (event) {
  623. var touches = [];
  624. // array of touches is supplied
  625. if (isArray(event)) {
  626. touches[0] = event[0];
  627. touches[1] = event[1];
  628. }
  629. // an event
  630. else {
  631. if (event.type === 'touchend') {
  632. if (event.touches.length === 1) {
  633. touches[0] = event.touches[0];
  634. touches[1] = event.changedTouches[0];
  635. }
  636. else if (event.touches.length === 0) {
  637. touches[0] = event.changedTouches[0];
  638. touches[1] = event.changedTouches[1];
  639. }
  640. }
  641. else {
  642. touches[0] = event.touches[0];
  643. touches[1] = event.touches[1];
  644. }
  645. }
  646. return touches;
  647. }
  648. function touchAverage (event) {
  649. var touches = getTouchPair(event);
  650. return {
  651. pageX: (touches[0].pageX + touches[1].pageX) / 2,
  652. pageY: (touches[0].pageY + touches[1].pageY) / 2,
  653. clientX: (touches[0].clientX + touches[1].clientX) / 2,
  654. clientY: (touches[0].clientY + touches[1].clientY) / 2
  655. };
  656. }
  657. function touchBBox (event) {
  658. if (!event.length && !(event.touches && event.touches.length > 1)) {
  659. return;
  660. }
  661. var touches = getTouchPair(event),
  662. minX = Math.min(touches[0].pageX, touches[1].pageX),
  663. minY = Math.min(touches[0].pageY, touches[1].pageY),
  664. maxX = Math.max(touches[0].pageX, touches[1].pageX),
  665. maxY = Math.max(touches[0].pageY, touches[1].pageY);
  666. return {
  667. x: minX,
  668. y: minY,
  669. left: minX,
  670. top: minY,
  671. width: maxX - minX,
  672. height: maxY - minY
  673. };
  674. }
  675. function touchDistance (event, deltaSource) {
  676. deltaSource = deltaSource || defaultOptions.deltaSource;
  677. var sourceX = deltaSource + 'X',
  678. sourceY = deltaSource + 'Y',
  679. touches = getTouchPair(event);
  680. var dx = touches[0][sourceX] - touches[1][sourceX],
  681. dy = touches[0][sourceY] - touches[1][sourceY];
  682. return hypot(dx, dy);
  683. }
  684. function touchAngle (event, prevAngle, deltaSource) {
  685. deltaSource = deltaSource || defaultOptions.deltaSource;
  686. var sourceX = deltaSource + 'X',
  687. sourceY = deltaSource + 'Y',
  688. touches = getTouchPair(event),
  689. dx = touches[0][sourceX] - touches[1][sourceX],
  690. dy = touches[0][sourceY] - touches[1][sourceY],
  691. angle = 180 * Math.atan(dy / dx) / Math.PI;
  692. if (isNumber(prevAngle)) {
  693. var dr = angle - prevAngle,
  694. drClamped = dr % 360;
  695. if (drClamped > 315) {
  696. angle -= 360 + (angle / 360)|0 * 360;
  697. }
  698. else if (drClamped > 135) {
  699. angle -= 180 + (angle / 360)|0 * 360;
  700. }
  701. else if (drClamped < -315) {
  702. angle += 360 + (angle / 360)|0 * 360;
  703. }
  704. else if (drClamped < -135) {
  705. angle += 180 + (angle / 360)|0 * 360;
  706. }
  707. }
  708. return angle;
  709. }
  710. function getOriginXY (interactable, element) {
  711. var origin = interactable
  712. ? interactable.options.origin
  713. : defaultOptions.origin;
  714. if (origin === 'parent') {
  715. origin = parentElement(element);
  716. }
  717. else if (origin === 'self') {
  718. origin = interactable.getRect(element);
  719. }
  720. else if (trySelector(origin)) {
  721. origin = closest(element, origin) || { x: 0, y: 0 };
  722. }
  723. if (isFunction(origin)) {
  724. origin = origin(interactable && element);
  725. }
  726. if (isElement(origin)) {
  727. origin = getElementRect(origin);
  728. }
  729. origin.x = ('x' in origin)? origin.x : origin.left;
  730. origin.y = ('y' in origin)? origin.y : origin.top;
  731. return origin;
  732. }
  733. // http://stackoverflow.com/a/5634528/2280888
  734. function _getQBezierValue(t, p1, p2, p3) {
  735. var iT = 1 - t;
  736. return iT * iT * p1 + 2 * iT * t * p2 + t * t * p3;
  737. }
  738. function getQuadraticCurvePoint(startX, startY, cpX, cpY, endX, endY, position) {
  739. return {
  740. x: _getQBezierValue(position, startX, cpX, endX),
  741. y: _getQBezierValue(position, startY, cpY, endY)
  742. };
  743. }
  744. // http://gizma.com/easing/
  745. function easeOutQuad (t, b, c, d) {
  746. t /= d;
  747. return -c * t*(t-2) + b;
  748. }
  749. function nodeContains (parent, child) {
  750. while (child) {
  751. if (child === parent) {
  752. return true;
  753. }
  754. child = child.parentNode;
  755. }
  756. return false;
  757. }
  758. function closest (child, selector) {
  759. var parent = parentElement(child);
  760. while (isElement(parent)) {
  761. if (matchesSelector(parent, selector)) { return parent; }
  762. parent = parentElement(parent);
  763. }
  764. return null;
  765. }
  766. function parentElement (node) {
  767. var parent = node.parentNode;
  768. if (isDocFrag(parent)) {
  769. // skip past #shado-root fragments
  770. while ((parent = parent.host) && isDocFrag(parent)) {}
  771. return parent;
  772. }
  773. return parent;
  774. }
  775. function inContext (interactable, element) {
  776. return interactable._context === element.ownerDocument
  777. || nodeContains(interactable._context, element);
  778. }
  779. function testIgnore (interactable, interactableElement, element) {
  780. var ignoreFrom = interactable.options.ignoreFrom;
  781. if (!ignoreFrom || !isElement(element)) { return false; }
  782. if (isString(ignoreFrom)) {
  783. return matchesUpTo(element, ignoreFrom, interactableElement);
  784. }
  785. else if (isElement(ignoreFrom)) {
  786. return nodeContains(ignoreFrom, element);
  787. }
  788. return false;
  789. }
  790. function testAllow (interactable, interactableElement, element) {
  791. var allowFrom = interactable.options.allowFrom;
  792. if (!allowFrom) { return true; }
  793. if (!isElement(element)) { return false; }
  794. if (isString(allowFrom)) {
  795. return matchesUpTo(element, allowFrom, interactableElement);
  796. }
  797. else if (isElement(allowFrom)) {
  798. return nodeContains(allowFrom, element);
  799. }
  800. return false;
  801. }
  802. function checkAxis (axis, interactable) {
  803. if (!interactable) { return false; }
  804. var thisAxis = interactable.options.drag.axis;
  805. return (axis === 'xy' || thisAxis === 'xy' || thisAxis === axis);
  806. }
  807. function checkSnap (interactable, action) {
  808. var options = interactable.options;
  809. if (/^resize/.test(action)) {
  810. action = 'resize';
  811. }
  812. return options[action].snap && options[action].snap.enabled;
  813. }
  814. function checkRestrict (interactable, action) {
  815. var options = interactable.options;
  816. if (/^resize/.test(action)) {
  817. action = 'resize';
  818. }
  819. return options[action].restrict && options[action].restrict.enabled;
  820. }
  821. function checkAutoScroll (interactable, action) {
  822. var options = interactable.options;
  823. if (/^resize/.test(action)) {
  824. action = 'resize';
  825. }
  826. return options[action].autoScroll && options[action].autoScroll.enabled;
  827. }
  828. function withinInteractionLimit (interactable, element, action) {
  829. var options = interactable.options,
  830. maxActions = options[action.name].max,
  831. maxPerElement = options[action.name].maxPerElement,
  832. activeInteractions = 0,
  833. targetCount = 0,
  834. targetElementCount = 0;
  835. for (var i = 0, len = interactions.length; i < len; i++) {
  836. var interaction = interactions[i],
  837. otherAction = interaction.prepared.name,
  838. active = interaction.interacting();
  839. if (!active) { continue; }
  840. activeInteractions++;
  841. if (activeInteractions >= maxInteractions) {
  842. return false;
  843. }
  844. if (interaction.target !== interactable) { continue; }
  845. targetCount += (otherAction === action.name)|0;
  846. if (targetCount >= maxActions) {
  847. return false;
  848. }
  849. if (interaction.element === element) {
  850. targetElementCount++;
  851. if (otherAction !== action.name || targetElementCount >= maxPerElement) {
  852. return false;
  853. }
  854. }
  855. }
  856. return maxInteractions > 0;
  857. }
  858. // Test for the element that's "above" all other qualifiers
  859. function indexOfDeepestElement (elements) {
  860. var dropzone,
  861. deepestZone = elements[0],
  862. index = deepestZone? 0: -1,
  863. parent,
  864. deepestZoneParents = [],
  865. dropzoneParents = [],
  866. child,
  867. i,
  868. n;
  869. for (i = 1; i < elements.length; i++) {
  870. dropzone = elements[i];
  871. // an element might belong to multiple selector dropzones
  872. if (!dropzone || dropzone === deepestZone) {
  873. continue;
  874. }
  875. if (!deepestZone) {
  876. deepestZone = dropzone;
  877. index = i;
  878. continue;
  879. }
  880. // check if the deepest or current are document.documentElement or document.rootElement
  881. // - if the current dropzone is, do nothing and continue
  882. if (dropzone.parentNode === dropzone.ownerDocument) {
  883. continue;
  884. }
  885. // - if deepest is, update with the current dropzone and continue to next
  886. else if (deepestZone.parentNode === dropzone.ownerDocument) {
  887. deepestZone = dropzone;
  888. index = i;
  889. continue;
  890. }
  891. if (!deepestZoneParents.length) {
  892. parent = deepestZone;
  893. while (parent.parentNode && parent.parentNode !== parent.ownerDocument) {
  894. deepestZoneParents.unshift(parent);
  895. parent = parent.parentNode;
  896. }
  897. }
  898. // if this element is an svg element and the current deepest is
  899. // an HTMLElement
  900. if (deepestZone instanceof HTMLElement
  901. && dropzone instanceof SVGElement
  902. && !(dropzone instanceof SVGSVGElement)) {
  903. if (dropzone === deepestZone.parentNode) {
  904. continue;
  905. }
  906. parent = dropzone.ownerSVGElement;
  907. }
  908. else {
  909. parent = dropzone;
  910. }
  911. dropzoneParents = [];
  912. while (parent.parentNode !== parent.ownerDocument) {
  913. dropzoneParents.unshift(parent);
  914. parent = parent.parentNode;
  915. }
  916. n = 0;
  917. // get (position of last common ancestor) + 1
  918. while (dropzoneParents[n] && dropzoneParents[n] === deepestZoneParents[n]) {
  919. n++;
  920. }
  921. var parents = [
  922. dropzoneParents[n - 1],
  923. dropzoneParents[n],
  924. deepestZoneParents[n]
  925. ];
  926. child = parents[0].lastChild;
  927. while (child) {
  928. if (child === parents[1]) {
  929. deepestZone = dropzone;
  930. index = i;
  931. deepestZoneParents = [];
  932. break;
  933. }
  934. else if (child === parents[2]) {
  935. break;
  936. }
  937. child = child.previousSibling;
  938. }
  939. }
  940. return index;
  941. }
  942. function Interaction () {
  943. this.target = null; // current interactable being interacted with
  944. this.element = null; // the target element of the interactable
  945. this.dropTarget = null; // the dropzone a drag target might be dropped into
  946. this.dropElement = null; // the element at the time of checking
  947. this.prevDropTarget = null; // the dropzone that was recently dragged away from
  948. this.prevDropElement = null; // the element at the time of checking
  949. this.prepared = { // action that's ready to be fired on next move event
  950. name : null,
  951. axis : null,
  952. edges: null
  953. };
  954. this.matches = []; // all selectors that are matched by target element
  955. this.matchElements = []; // corresponding elements
  956. this.inertiaStatus = {
  957. active : false,
  958. smoothEnd : false,
  959. startEvent: null,
  960. upCoords: {},
  961. xe: 0, ye: 0,
  962. sx: 0, sy: 0,
  963. t0: 0,
  964. vx0: 0, vys: 0,
  965. duration: 0,
  966. resumeDx: 0,
  967. resumeDy: 0,
  968. lambda_v0: 0,
  969. one_ve_v0: 0,
  970. i : null
  971. };
  972. if (isFunction(Function.prototype.bind)) {
  973. this.boundInertiaFrame = this.inertiaFrame.bind(this);
  974. this.boundSmoothEndFrame = this.smoothEndFrame.bind(this);
  975. }
  976. else {
  977. var that = this;
  978. this.boundInertiaFrame = function () { return that.inertiaFrame(); };
  979. this.boundSmoothEndFrame = function () { return that.smoothEndFrame(); };
  980. }
  981. this.activeDrops = {
  982. dropzones: [], // the dropzones that are mentioned below
  983. elements : [], // elements of dropzones that accept the target draggable
  984. rects : [] // the rects of the elements mentioned above
  985. };
  986. // keep track of added pointers
  987. this.pointers = [];
  988. this.pointerIds = [];
  989. this.downTargets = [];
  990. this.downTimes = [];
  991. this.holdTimers = [];
  992. // Previous native pointer move event coordinates
  993. this.prevCoords = {
  994. page : { x: 0, y: 0 },
  995. client : { x: 0, y: 0 },
  996. timeStamp: 0
  997. };
  998. // current native pointer move event coordinates
  999. this.curCoords = {
  1000. page : { x: 0, y: 0 },
  1001. client : { x: 0, y: 0 },
  1002. timeStamp: 0
  1003. };
  1004. // Starting InteractEvent pointer coordinates
  1005. this.startCoords = {
  1006. page : { x: 0, y: 0 },
  1007. client : { x: 0, y: 0 },
  1008. timeStamp: 0
  1009. };
  1010. // Change in coordinates and time of the pointer
  1011. this.pointerDelta = {
  1012. page : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
  1013. client : { x: 0, y: 0, vx: 0, vy: 0, speed: 0 },
  1014. timeStamp: 0
  1015. };
  1016. this.downEvent = null; // pointerdown/mousedown/touchstart event
  1017. this.downPointer = {};
  1018. this._eventTarget = null;
  1019. this._curEventTarget = null;
  1020. this.prevEvent = null; // previous action event
  1021. this.tapTime = 0; // time of the most recent tap event
  1022. this.prevTap = null;
  1023. this.startOffset = { left: 0, right: 0, top: 0, bottom: 0 };
  1024. this.restrictOffset = { left: 0, right: 0, top: 0, bottom: 0 };
  1025. this.snapOffsets = [];
  1026. this.gesture = {
  1027. start: { x: 0, y: 0 },
  1028. startDistance: 0, // distance between two touches of touchStart
  1029. prevDistance : 0,
  1030. distance : 0,
  1031. scale: 1, // gesture.distance / gesture.startDistance
  1032. startAngle: 0, // angle of line joining two touches
  1033. prevAngle : 0 // angle of the previous gesture event
  1034. };
  1035. this.snapStatus = {
  1036. x : 0, y : 0,
  1037. dx : 0, dy : 0,
  1038. realX : 0, realY : 0,
  1039. snappedX: 0, snappedY: 0,
  1040. targets : [],
  1041. locked : false,
  1042. changed : false
  1043. };
  1044. this.restrictStatus = {
  1045. dx : 0, dy : 0,
  1046. restrictedX: 0, restrictedY: 0,
  1047. snap : null,
  1048. restricted : false,
  1049. changed : false
  1050. };
  1051. this.restrictStatus.snap = this.snapStatus;
  1052. this.pointerIsDown = false;
  1053. this.pointerWasMoved = false;
  1054. this.gesturing = false;
  1055. this.dragging = false;
  1056. this.resizing = false;
  1057. this.resizeAxes = 'xy';
  1058. this.mouse = false;
  1059. interactions.push(this);
  1060. }
  1061. Interaction.prototype = {
  1062. getPageXY : function (pointer, xy) { return getPageXY(pointer, xy, this); },
  1063. getClientXY: function (pointer, xy) { return getClientXY(pointer, xy, this); },
  1064. setEventXY : function (target, ptr) { return setEventXY(target, ptr, this); },
  1065. pointerOver: function (pointer, event, eventTarget) {
  1066. if (this.prepared.name || !this.mouse) { return; }
  1067. var curMatches = [],
  1068. curMatchElements = [],
  1069. prevTargetElement = this.element;
  1070. this.addPointer(pointer);
  1071. if (this.target
  1072. && (testIgnore(this.target, this.element, eventTarget)
  1073. || !testAllow(this.target, this.element, eventTarget))) {
  1074. // if the eventTarget should be ignored or shouldn't be allowed
  1075. // clear the previous target
  1076. this.target = null;
  1077. this.element = null;
  1078. this.matches = [];
  1079. this.matchElements = [];
  1080. }
  1081. var elementInteractable = interactables.get(eventTarget),
  1082. elementAction = (elementInteractable
  1083. && !testIgnore(elementInteractable, eventTarget, eventTarget)
  1084. && testAllow(elementInteractable, eventTarget, eventTarget)
  1085. && validateAction(
  1086. elementInteractable.getAction(pointer, this, eventTarget),
  1087. elementInteractable));
  1088. if (elementAction && !withinInteractionLimit(elementInteractable, eventTarget, elementAction)) {
  1089. elementAction = null;
  1090. }
  1091. function pushCurMatches (interactable, selector) {
  1092. if (interactable
  1093. && inContext(interactable, eventTarget)
  1094. && !testIgnore(interactable, eventTarget, eventTarget)
  1095. && testAllow(interactable, eventTarget, eventTarget)
  1096. && matchesSelector(eventTarget, selector)) {
  1097. curMatches.push(interactable);
  1098. curMatchElements.push(eventTarget);
  1099. }
  1100. }
  1101. if (elementAction) {
  1102. this.target = elementInteractable;
  1103. this.element = eventTarget;
  1104. this.matches = [];
  1105. this.matchElements = [];
  1106. }
  1107. else {
  1108. interactables.forEachSelector(pushCurMatches);
  1109. if (this.validateSelector(pointer, curMatches, curMatchElements)) {
  1110. this.matches = curMatches;
  1111. this.matchElements = curMatchElements;
  1112. this.pointerHover(pointer, event, this.matches, this.matchElements);
  1113. events.add(eventTarget,
  1114. PointerEvent? pEventTypes.move : 'mousemove',
  1115. listeners.pointerHover);
  1116. }
  1117. else if (this.target) {
  1118. if (nodeContains(prevTargetElement, eventTarget)) {
  1119. this.pointerHover(pointer, event, this.matches, this.matchElements);
  1120. events.add(this.element,
  1121. PointerEvent? pEventTypes.move : 'mousemove',
  1122. listeners.pointerHover);
  1123. }
  1124. else {
  1125. this.target = null;
  1126. this.element = null;
  1127. this.matches = [];
  1128. this.matchElements = [];
  1129. }
  1130. }
  1131. }
  1132. },
  1133. // Check what action would be performed on pointerMove target if a mouse
  1134. // button were pressed and change the cursor accordingly
  1135. pointerHover: function (pointer, event, eventTarget, curEventTarget, matches, matchElements) {
  1136. var target = this.target;
  1137. if (!this.prepared.name && this.mouse) {
  1138. var action;
  1139. // update pointer coords for defaultActionChecker to use
  1140. this.setEventXY(this.curCoords, pointer);
  1141. if (matches) {
  1142. action = this.validateSelector(pointer, matches, matchElements);
  1143. }
  1144. else if (target) {
  1145. action = validateAction(target.getAction(this.pointers[0], this, this.element), this.target);
  1146. }
  1147. if (target && target.options.styleCursor) {
  1148. if (action) {
  1149. target._doc.documentElement.style.cursor = getActionCursor(action);
  1150. }
  1151. else {
  1152. target._doc.documentElement.style.cursor = '';
  1153. }
  1154. }
  1155. }
  1156. else if (this.prepared.name) {
  1157. this.checkAndPreventDefault(event, target, this.element);
  1158. }
  1159. },
  1160. pointerOut: function (pointer, event, eventTarget) {
  1161. if (this.prepared.name) { return; }
  1162. // Remove temporary event listeners for selector Interactables
  1163. if (!interactables.get(eventTarget)) {
  1164. events.remove(eventTarget,
  1165. PointerEvent? pEventTypes.move : 'mousemove',
  1166. listeners.pointerHover);
  1167. }
  1168. if (this.target && this.target.options.styleCursor && !this.interacting()) {
  1169. this.target._doc.documentElement.style.cursor = '';
  1170. }
  1171. },
  1172. selectorDown: function (pointer, event, eventTarget, curEventTarget) {
  1173. var that = this,
  1174. // copy event to be used in timeout for IE8
  1175. eventCopy = events.useAttachEvent? extend({}, event) : event,
  1176. element = eventTarget,
  1177. pointerIndex = this.addPointer(pointer),
  1178. action;
  1179. this.holdTimers[pointerIndex] = setTimeout(function () {
  1180. that.pointerHold(events.useAttachEvent? eventCopy : pointer, eventCopy, eventTarget, curEventTarget);
  1181. }, defaultOptions._holdDuration);
  1182. this.pointerIsDown = true;
  1183. // Check if the down event hits the current inertia target
  1184. if (this.inertiaStatus.active && this.target.selector) {
  1185. // climb up the DOM tree from the event target
  1186. while (isElement(element)) {
  1187. // if this element is the current inertia target element
  1188. if (element === this.element
  1189. // and the prospective action is the same as the ongoing one
  1190. && validateAction(this.target.getAction(pointer, this, this.element), this.target).name === this.prepared.name) {
  1191. // stop inertia so that the next move will be a normal one
  1192. cancelFrame(this.inertiaStatus.i);
  1193. this.inertiaStatus.active = false;
  1194. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1195. return;
  1196. }
  1197. element = parentElement(element);
  1198. }
  1199. }
  1200. // do nothing if interacting
  1201. if (this.interacting()) {
  1202. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1203. return;
  1204. }
  1205. function pushMatches (interactable, selector, context) {
  1206. var elements = ie8MatchesSelector
  1207. ? context.querySelectorAll(selector)
  1208. : undefined;
  1209. if (inContext(interactable, element)
  1210. && !testIgnore(interactable, element, eventTarget)
  1211. && testAllow(interactable, element, eventTarget)
  1212. && matchesSelector(element, selector, elements)) {
  1213. that.matches.push(interactable);
  1214. that.matchElements.push(element);
  1215. }
  1216. }
  1217. // update pointer coords for defaultActionChecker to use
  1218. this.setEventXY(this.curCoords, pointer);
  1219. while (isElement(element) && !action) {
  1220. this.matches = [];
  1221. this.matchElements = [];
  1222. interactables.forEachSelector(pushMatches);
  1223. action = this.validateSelector(pointer, this.matches, this.matchElements);
  1224. element = parentElement(element);
  1225. }
  1226. if (action) {
  1227. this.prepared.name = action.name;
  1228. this.prepared.axis = action.axis;
  1229. this.prepared.edges = action.edges;
  1230. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1231. return this.pointerDown(pointer, event, eventTarget, curEventTarget, action);
  1232. }
  1233. else {
  1234. // do these now since pointerDown isn't being called from here
  1235. this.downTimes[pointerIndex] = new Date().getTime();
  1236. this.downTargets[pointerIndex] = eventTarget;
  1237. this.downEvent = event;
  1238. extend(this.downPointer, pointer);
  1239. copyCoords(this.prevCoords, this.curCoords);
  1240. this.pointerWasMoved = false;
  1241. }
  1242. this.collectEventTargets(pointer, event, eventTarget, 'down');
  1243. },
  1244. // Determine action to be performed on next pointerMove and add appropriate
  1245. // style and event Listeners
  1246. pointerDown: function (pointer, event, eventTarget, curEventTarget, forceAction) {
  1247. if (!forceAction && !this.inertiaStatus.active && this.pointerWasMoved && this.prepared.name) {
  1248. this.checkAndPreventDefault(event, this.target, this.element);
  1249. return;
  1250. }
  1251. this.pointerIsDown = true;
  1252. var pointerIndex = this.addPointer(pointer),
  1253. action;
  1254. // If it is the second touch of a multi-touch gesture, keep the target
  1255. // the same if a target was set by the first touch
  1256. // Otherwise, set the target if there is no action prepared
  1257. if ((this.pointerIds.length < 2 && !this.target) || !this.prepared.name) {
  1258. var interactable = interactables.get(curEventTarget);
  1259. if (interactable
  1260. && !testIgnore(interactable, curEventTarget, eventTarget)
  1261. && testAllow(interactable, curEventTarget, eventTarget)
  1262. && (action = validateAction(forceAction || interactable.getAction(pointer, this, curEventTarget), interactable, eventTarget))
  1263. && withinInteractionLimit(interactable, curEventTarget, action)) {
  1264. this.target = interactable;
  1265. this.element = curEventTarget;
  1266. }
  1267. }
  1268. var target = this.target,
  1269. options = target && target.options;
  1270. if (target && !this.interacting()) {
  1271. action = action || validateAction(forceAction || target.getAction(pointer, this, curEventTarget), target, this.element);
  1272. this.setEventXY(this.startCoords);
  1273. if (!action) { return; }
  1274. if (options.styleCursor) {
  1275. target._doc.documentElement.style.cursor = getActionCursor(action);
  1276. }
  1277. this.resizeAxes = action.name === 'resize'? action.axis : null;
  1278. if (action === 'gesture' && this.pointerIds.length < 2) {
  1279. action = null;
  1280. }
  1281. this.prepared.name = action.name;
  1282. this.prepared.axis = action.axis;
  1283. this.prepared.edges = action.edges;
  1284. this.snapStatus.snappedX = this.snapStatus.snappedY =
  1285. this.restrictStatus.restrictedX = this.restrictStatus.restrictedY = NaN;
  1286. this.downTimes[pointerIndex] = new Date().getTime();
  1287. this.downTargets[pointerIndex] = eventTarget;
  1288. this.downEvent = event;
  1289. extend(this.downPointer, pointer);
  1290. this.setEventXY(this.prevCoords);
  1291. this.pointerWasMoved = false;
  1292. this.checkAndPreventDefault(event, target, this.element);
  1293. }
  1294. // if inertia is active try to resume action
  1295. else if (this.inertiaStatus.active
  1296. && curEventTarget === this.element
  1297. && validateAction(target.getAction(pointer, this, this.element), target).name === this.prepared.name) {
  1298. cancelFrame(this.inertiaStatus.i);
  1299. this.inertiaStatus.active = false;
  1300. this.checkAndPreventDefault(event, target, this.element);
  1301. }
  1302. },
  1303. setModifications: function (coords, preEnd) {
  1304. var target = this.target,
  1305. shouldMove = true,
  1306. shouldSnap = checkSnap(target, this.prepared.name) && (!target.options[this.prepared.name].snap.endOnly || preEnd),
  1307. shouldRestrict = checkRestrict(target, this.prepared.name) && (!target.options[this.prepared.name].restrict.endOnly || preEnd);
  1308. if (shouldSnap ) { this.setSnapping (coords); } else { this.snapStatus .locked = false; }
  1309. if (shouldRestrict) { this.setRestriction(coords); } else { this.restrictStatus.restricted = false; }
  1310. if (shouldSnap && this.snapStatus.locked && !this.snapStatus.changed) {
  1311. shouldMove = shouldRestrict && this.restrictStatus.restricted && this.restrictStatus.changed;
  1312. }
  1313. else if (shouldRestrict && this.restrictStatus.restricted && !this.restrictStatus.changed) {
  1314. shouldMove = false;
  1315. }
  1316. return shouldMove;
  1317. },
  1318. setStartOffsets: function (action, interactable, element) {
  1319. var rect = interactable.getRect(element),
  1320. origin = getOriginXY(interactable, element),
  1321. snap = interactable.options[this.prepared.name].snap,
  1322. restrict = interactable.options[this.prepared.name].restrict,
  1323. width, height;
  1324. if (rect) {
  1325. this.startOffset.left = this.startCoords.page.x - rect.left;
  1326. this.startOffset.top = this.startCoords.page.y - rect.top;
  1327. this.startOffset.right = rect.right - this.startCoords.page.x;
  1328. this.startOffset.bottom = rect.bottom - this.startCoords.page.y;
  1329. if ('width' in rect) { width = rect.width; }
  1330. else { width = rect.right - rect.left; }
  1331. if ('height' in rect) { height = rect.height; }
  1332. else { height = rect.bottom - rect.top; }
  1333. }
  1334. else {
  1335. this.startOffset.left = this.startOffset.top = this.startOffset.right = this.startOffset.bottom = 0;
  1336. }
  1337. this.snapOffsets.splice(0);
  1338. var snapOffset = snap && snap.offset === 'startCoords'
  1339. ? {
  1340. x: this.startCoords.page.x - origin.x,
  1341. y: this.startCoords.page.y - origin.y
  1342. }
  1343. : snap && snap.offset || { x: 0, y: 0 };
  1344. if (rect && snap && snap.relativePoints && snap.relativePoints.length) {
  1345. for (var i = 0; i < snap.relativePoints.length; i++) {
  1346. this.snapOffsets.push({
  1347. x: this.startOffset.left - (width * snap.relativePoints[i].x) + snapOffset.x,
  1348. y: this.startOffset.top - (height * snap.relativePoints[i].y) + snapOffset.y
  1349. });
  1350. }
  1351. }
  1352. else {
  1353. this.snapOffsets.push(snapOffset);
  1354. }
  1355. if (rect && restrict.elementRect) {
  1356. this.restrictOffset.left = this.startOffset.left - (width * restrict.elementRect.left);
  1357. this.restrictOffset.top = this.startOffset.top - (height * restrict.elementRect.top);
  1358. this.restrictOffset.right = this.startOffset.right - (width * (1 - restrict.elementRect.right));
  1359. this.restrictOffset.bottom = this.startOffset.bottom - (height * (1 - restrict.elementRect.bottom));
  1360. }
  1361. else {
  1362. this.restrictOffset.left = this.restrictOffset.top = this.restrictOffset.right = this.restrictOffset.bottom = 0;
  1363. }
  1364. },
  1365. /*\
  1366. * Interaction.start
  1367. [ method ]
  1368. *
  1369. * Start an action with the given Interactable and Element as tartgets. The
  1370. * action must be enabled for the target Interactable and an appropriate number
  1371. * of pointers must be held down 1 for drag/resize, 2 for gesture.
  1372. *
  1373. * Use it with `interactable.<action>able({ manualStart: false })` to always
  1374. * [start actions manually](https://github.com/taye/interact.js/issues/114)
  1375. *
  1376. - action (object) The action to be performed - drag, resize, etc.
  1377. - interactable (Interactable) The Interactable to target
  1378. - element (Element) The DOM Element to target
  1379. = (object) interact
  1380. **
  1381. | interact(target)
  1382. | .draggable({
  1383. | // disable the default drag start by down->move
  1384. | manualStart: true
  1385. | })
  1386. | // start dragging after the user holds the pointer down
  1387. | .on('hold', function (event) {
  1388. | var interaction = event.interaction;
  1389. |
  1390. | if (!interaction.interacting()) {
  1391. | interaction.start({ name: 'drag' },
  1392. | event.interactable,
  1393. | event.currentTarget);
  1394. | }
  1395. | });
  1396. \*/
  1397. start: function (action, interactable, element) {
  1398. if (this.interacting()
  1399. || !this.pointerIsDown
  1400. || this.pointerIds.length < (action.name === 'gesture'? 2 : 1)) {
  1401. return;
  1402. }
  1403. // if this interaction had been removed after stopping
  1404. // add it back
  1405. if (indexOf(interactions, this) === -1) {
  1406. interactions.push(this);
  1407. }
  1408. this.prepared.name = action.name;
  1409. this.prepared.axis = action.axis;
  1410. this.prepared.edges = action.edges;
  1411. this.target = interactable;
  1412. this.element = element;
  1413. this.setStartOffsets(action.name, interactable, element);
  1414. this.setModifications(this.startCoords.page);
  1415. this.prevEvent = this[this.prepared.name + 'Start'](this.downEvent);
  1416. },
  1417. pointerMove: function (pointer, event, eventTarget, curEventTarget, preEnd) {
  1418. this.recordPointer(pointer);
  1419. this.setEventXY(this.curCoords, (pointer instanceof InteractEvent)
  1420. ? this.inertiaStatus.startEvent
  1421. : undefined);
  1422. var duplicateMove = (this.curCoords.page.x === this.prevCoords.page.x
  1423. && this.curCoords.page.y === this.prevCoords.page.y
  1424. && this.curCoords.client.x === this.prevCoords.client.x
  1425. && this.curCoords.client.y === this.prevCoords.client.y);
  1426. var dx, dy,
  1427. pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  1428. // register movement greater than pointerMoveTolerance
  1429. if (this.pointerIsDown && !this.pointerWasMoved) {
  1430. dx = this.curCoords.client.x - this.startCoords.client.x;
  1431. dy = this.curCoords.client.y - this.startCoords.client.y;
  1432. this.pointerWasMoved = hypot(dx, dy) > pointerMoveTolerance;
  1433. }
  1434. if (!duplicateMove && (!this.pointerIsDown || this.pointerWasMoved)) {
  1435. if (this.pointerIsDown) {
  1436. clearTimeout(this.holdTimers[pointerIndex]);
  1437. }
  1438. this.collectEventTargets(pointer, event, eventTarget, 'move');
  1439. }
  1440. if (!this.pointerIsDown) { return; }
  1441. if (duplicateMove && this.pointerWasMoved && !preEnd) {
  1442. this.checkAndPreventDefault(event, this.target, this.element);
  1443. return;
  1444. }
  1445. // set pointer coordinate, time changes and speeds
  1446. setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);
  1447. if (!this.prepared.name) { return; }
  1448. if (this.pointerWasMoved
  1449. // ignore movement while inertia is active
  1450. && (!this.inertiaStatus.active || (pointer instanceof InteractEvent && /inertiastart/.test(pointer.type)))) {
  1451. // if just starting an action, calculate the pointer speed now
  1452. if (!this.interacting()) {
  1453. setEventDeltas(this.pointerDelta, this.prevCoords, this.curCoords);
  1454. // check if a drag is in the correct axis
  1455. if (this.prepared.name === 'drag') {
  1456. var absX = Math.abs(dx),
  1457. absY = Math.abs(dy),
  1458. targetAxis = this.target.options.drag.axis,
  1459. axis = (absX > absY ? 'x' : absX < absY ? 'y' : 'xy');
  1460. // if the movement isn't in the axis of the interactable
  1461. if (axis !== 'xy' && targetAxis !== 'xy' && targetAxis !== axis) {
  1462. // cancel the prepared action
  1463. this.prepared.name = null;
  1464. // then try to get a drag from another ineractable
  1465. var element = eventTarget;
  1466. // check element interactables
  1467. while (isElement(element)) {
  1468. var elementInteractable = interactables.get(element);
  1469. if (elementInteractable
  1470. && elementInteractable !== this.target
  1471. && !elementInteractable.options.drag.manualStart
  1472. && elementInteractable.getAction(this.downPointer, this, element).name === 'drag'
  1473. && checkAxis(axis, elementInteractable)) {
  1474. this.prepared.name = 'drag';
  1475. this.target = elementInteractable;
  1476. this.element = element;
  1477. break;
  1478. }
  1479. element = parentElement(element);
  1480. }
  1481. // if there's no drag from element interactables,
  1482. // check the selector interactables
  1483. if (!this.prepared.name) {
  1484. var getDraggable = function (interactable, selector, context) {
  1485. var elements = ie8MatchesSelector
  1486. ? context.querySelectorAll(selector)
  1487. : undefined;
  1488. if (interactable === this.target) { return; }
  1489. if (inContext(interactable, eventTarget)
  1490. && !interactable.options.drag.manualStart
  1491. && !testIgnore(interactable, element, eventTarget)
  1492. && testAllow(interactable, element, eventTarget)
  1493. && matchesSelector(element, selector, elements)
  1494. && interactable.getAction(this.downPointer, this, element).name === 'drag'
  1495. && checkAxis(axis, interactable)
  1496. && withinInteractionLimit(interactable, element, 'drag')) {
  1497. return interactable;
  1498. }
  1499. };
  1500. element = eventTarget;
  1501. while (isElement(element)) {
  1502. var selectorInteractable = interactables.forEachSelector(getDraggable);
  1503. if (selectorInteractable) {
  1504. this.prepared.name = 'drag';
  1505. this.target = selectorInteractable;
  1506. this.element = element;
  1507. break;
  1508. }
  1509. element = parentElement(element);
  1510. }
  1511. }
  1512. }
  1513. }
  1514. }
  1515. var starting = !!this.prepared.name && !this.interacting();
  1516. if (starting
  1517. && (this.target.options[this.prepared.name].manualStart
  1518. || !withinInteractionLimit(this.target, this.element, this.prepared))) {
  1519. this.stop();
  1520. return;
  1521. }
  1522. if (this.prepared.name && this.target) {
  1523. if (starting) {
  1524. this.start(this.prepared, this.target, this.element);
  1525. }
  1526. var shouldMove = this.setModifications(this.curCoords.page, preEnd);
  1527. // move if snapping or restriction doesn't prevent it
  1528. if (shouldMove || starting) {
  1529. this.prevEvent = this[this.prepared.name + 'Move'](event);
  1530. }
  1531. this.checkAndPreventDefault(event, this.target, this.element);
  1532. }
  1533. }
  1534. copyCoords(this.prevCoords, this.curCoords);
  1535. if (this.dragging || this.resizing) {
  1536. autoScroll.edgeMove(event);
  1537. }
  1538. },
  1539. dragStart: function (event) {
  1540. var dragEvent = new InteractEvent(this, event, 'drag', 'start', this.element);
  1541. this.dragging = true;
  1542. this.target.fire(dragEvent);
  1543. // reset active dropzones
  1544. this.activeDrops.dropzones = [];
  1545. this.activeDrops.elements = [];
  1546. this.activeDrops.rects = [];
  1547. if (!this.dynamicDrop) {
  1548. this.setActiveDrops(this.element);
  1549. }
  1550. var dropEvents = this.getDropEvents(event, dragEvent);
  1551. if (dropEvents.activate) {
  1552. this.fireActiveDrops(dropEvents.activate);
  1553. }
  1554. return dragEvent;
  1555. },
  1556. dragMove: function (event) {
  1557. var target = this.target,
  1558. dragEvent = new InteractEvent(this, event, 'drag', 'move', this.element),
  1559. draggableElement = this.element,
  1560. drop = this.getDrop(dragEvent, draggableElement);
  1561. this.dropTarget = drop.dropzone;
  1562. this.dropElement = drop.element;
  1563. var dropEvents = this.getDropEvents(event, dragEvent);
  1564. target.fire(dragEvent);
  1565. if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }
  1566. if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }
  1567. if (dropEvents.move ) { this.dropTarget.fire(dropEvents.move ); }
  1568. this.prevDropTarget = this.dropTarget;
  1569. this.prevDropElement = this.dropElement;
  1570. return dragEvent;
  1571. },
  1572. resizeStart: function (event) {
  1573. var resizeEvent = new InteractEvent(this, event, 'resize', 'start', this.element);
  1574. if (this.prepared.edges) {
  1575. var startRect = this.target.getRect(this.element);
  1576. if (this.target.options.resize.square) {
  1577. var squareEdges = extend({}, this.prepared.edges);
  1578. squareEdges.top = squareEdges.top || (squareEdges.left && !squareEdges.bottom);
  1579. squareEdges.left = squareEdges.left || (squareEdges.top && !squareEdges.right );
  1580. squareEdges.bottom = squareEdges.bottom || (squareEdges.right && !squareEdges.top );
  1581. squareEdges.right = squareEdges.right || (squareEdges.bottom && !squareEdges.left );
  1582. this.prepared._squareEdges = squareEdges;
  1583. }
  1584. else {
  1585. this.prepared._squareEdges = null;
  1586. }
  1587. this.resizeRects = {
  1588. start : startRect,
  1589. current : extend({}, startRect),
  1590. restricted: extend({}, startRect),
  1591. previous : extend({}, startRect),
  1592. delta : {
  1593. left: 0, right : 0, width : 0,
  1594. top : 0, bottom: 0, height: 0
  1595. }
  1596. };
  1597. resizeEvent.rect = this.resizeRects.restricted;
  1598. resizeEvent.deltaRect = this.resizeRects.delta;
  1599. }
  1600. this.target.fire(resizeEvent);
  1601. this.resizing = true;
  1602. return resizeEvent;
  1603. },
  1604. resizeMove: function (event) {
  1605. var resizeEvent = new InteractEvent(this, event, 'resize', 'move', this.element);
  1606. var edges = this.prepared.edges,
  1607. invert = this.target.options.resize.invert,
  1608. invertible = invert === 'reposition' || invert === 'negate';
  1609. if (edges) {
  1610. var dx = resizeEvent.dx,
  1611. dy = resizeEvent.dy,
  1612. start = this.resizeRects.start,
  1613. current = this.resizeRects.current,
  1614. restricted = this.resizeRects.restricted,
  1615. delta = this.resizeRects.delta,
  1616. previous = extend(this.resizeRects.previous, restricted);
  1617. if (this.target.options.resize.square) {
  1618. var originalEdges = edges;
  1619. edges = this.prepared._squareEdges;
  1620. if ((originalEdges.left && originalEdges.bottom)
  1621. || (originalEdges.right && originalEdges.top)) {
  1622. dy = -dx;
  1623. }
  1624. else if (originalEdges.left || originalEdges.right) { dy = dx; }
  1625. else if (originalEdges.top || originalEdges.bottom) { dx = dy; }
  1626. }
  1627. // update the 'current' rect without modifications
  1628. if (edges.top ) { current.top += dy; }
  1629. if (edges.bottom) { current.bottom += dy; }
  1630. if (edges.left ) { current.left += dx; }
  1631. if (edges.right ) { current.right += dx; }
  1632. if (invertible) {
  1633. // if invertible, copy the current rect
  1634. extend(restricted, current);
  1635. if (invert === 'reposition') {
  1636. // swap edge values if necessary to keep width/height positive
  1637. var swap;
  1638. if (restricted.top > restricted.bottom) {
  1639. swap = restricted.top;
  1640. restricted.top = restricted.bottom;
  1641. restricted.bottom = swap;
  1642. }
  1643. if (restricted.left > restricted.right) {
  1644. swap = restricted.left;
  1645. restricted.left = restricted.right;
  1646. restricted.right = swap;
  1647. }
  1648. }
  1649. }
  1650. else {
  1651. // if not invertible, restrict to minimum of 0x0 rect
  1652. restricted.top = Math.min(current.top, start.bottom);
  1653. restricted.bottom = Math.max(current.bottom, start.top);
  1654. restricted.left = Math.min(current.left, start.right);
  1655. restricted.right = Math.max(current.right, start.left);
  1656. }
  1657. restricted.width = restricted.right - restricted.left;
  1658. restricted.height = restricted.bottom - restricted.top ;
  1659. for (var edge in restricted) {
  1660. delta[edge] = restricted[edge] - previous[edge];
  1661. }
  1662. resizeEvent.edges = this.prepared.edges;
  1663. resizeEvent.rect = restricted;
  1664. resizeEvent.deltaRect = delta;
  1665. }
  1666. this.target.fire(resizeEvent);
  1667. return resizeEvent;
  1668. },
  1669. gestureStart: function (event) {
  1670. var gestureEvent = new InteractEvent(this, event, 'gesture', 'start', this.element);
  1671. gestureEvent.ds = 0;
  1672. this.gesture.startDistance = this.gesture.prevDistance = gestureEvent.distance;
  1673. this.gesture.startAngle = this.gesture.prevAngle = gestureEvent.angle;
  1674. this.gesture.scale = 1;
  1675. this.gesturing = true;
  1676. this.target.fire(gestureEvent);
  1677. return gestureEvent;
  1678. },
  1679. gestureMove: function (event) {
  1680. if (!this.pointerIds.length) {
  1681. return this.prevEvent;
  1682. }
  1683. var gestureEvent;
  1684. gestureEvent = new InteractEvent(this, event, 'gesture', 'move', this.element);
  1685. gestureEvent.ds = gestureEvent.scale - this.gesture.scale;
  1686. this.target.fire(gestureEvent);
  1687. this.gesture.prevAngle = gestureEvent.angle;
  1688. this.gesture.prevDistance = gestureEvent.distance;
  1689. if (gestureEvent.scale !== Infinity &&
  1690. gestureEvent.scale !== null &&
  1691. gestureEvent.scale !== undefined &&
  1692. !isNaN(gestureEvent.scale)) {
  1693. this.gesture.scale = gestureEvent.scale;
  1694. }
  1695. return gestureEvent;
  1696. },
  1697. pointerHold: function (pointer, event, eventTarget) {
  1698. this.collectEventTargets(pointer, event, eventTarget, 'hold');
  1699. },
  1700. pointerUp: function (pointer, event, eventTarget, curEventTarget) {
  1701. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  1702. clearTimeout(this.holdTimers[pointerIndex]);
  1703. this.collectEventTargets(pointer, event, eventTarget, 'up' );
  1704. this.collectEventTargets(pointer, event, eventTarget, 'tap');
  1705. this.pointerEnd(pointer, event, eventTarget, curEventTarget);
  1706. this.removePointer(pointer);
  1707. },
  1708. pointerCancel: function (pointer, event, eventTarget, curEventTarget) {
  1709. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  1710. clearTimeout(this.holdTimers[pointerIndex]);
  1711. this.collectEventTargets(pointer, event, eventTarget, 'cancel');
  1712. this.pointerEnd(pointer, event, eventTarget, curEventTarget);
  1713. this.removePointer(pointer);
  1714. },
  1715. // http://www.quirksmode.org/dom/events/click.html
  1716. // >Events leading to dblclick
  1717. //
  1718. // IE8 doesn't fire down event before dblclick.
  1719. // This workaround tries to fire a tap and doubletap after dblclick
  1720. ie8Dblclick: function (pointer, event, eventTarget) {
  1721. if (this.prevTap
  1722. && event.clientX === this.prevTap.clientX
  1723. && event.clientY === this.prevTap.clientY
  1724. && eventTarget === this.prevTap.target) {
  1725. this.downTargets[0] = eventTarget;
  1726. this.downTimes[0] = new Date().getTime();
  1727. this.collectEventTargets(pointer, event, eventTarget, 'tap');
  1728. }
  1729. },
  1730. // End interact move events and stop auto-scroll unless inertia is enabled
  1731. pointerEnd: function (pointer, event, eventTarget, curEventTarget) {
  1732. var endEvent,
  1733. target = this.target,
  1734. options = target && target.options,
  1735. inertiaOptions = options && this.prepared.name && options[this.prepared.name].inertia,
  1736. inertiaStatus = this.inertiaStatus;
  1737. if (this.interacting()) {
  1738. if (inertiaStatus.active) { return; }
  1739. var pointerSpeed,
  1740. now = new Date().getTime(),
  1741. inertiaPossible = false,
  1742. inertia = false,
  1743. smoothEnd = false,
  1744. endSnap = checkSnap(target, this.prepared.name) && options[this.prepared.name].snap.endOnly,
  1745. endRestrict = checkRestrict(target, this.prepared.name) && options[this.prepared.name].restrict.endOnly,
  1746. dx = 0,
  1747. dy = 0,
  1748. startEvent;
  1749. if (this.dragging) {
  1750. if (options.drag.axis === 'x' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vx); }
  1751. else if (options.drag.axis === 'y' ) { pointerSpeed = Math.abs(this.pointerDelta.client.vy); }
  1752. else /*options.drag.axis === 'xy'*/{ pointerSpeed = this.pointerDelta.client.speed; }
  1753. }
  1754. else {
  1755. pointerSpeed = this.pointerDelta.client.speed;
  1756. }
  1757. // check if inertia should be started
  1758. inertiaPossible = (inertiaOptions && inertiaOptions.enabled
  1759. && this.prepared.name !== 'gesture'
  1760. && event !== inertiaStatus.startEvent);
  1761. inertia = (inertiaPossible
  1762. && (now - this.curCoords.timeStamp) < 50
  1763. && pointerSpeed > inertiaOptions.minSpeed
  1764. && pointerSpeed > inertiaOptions.endSpeed);
  1765. if (inertiaPossible && !inertia && (endSnap || endRestrict)) {
  1766. var snapRestrict = {};
  1767. snapRestrict.snap = snapRestrict.restrict = snapRestrict;
  1768. if (endSnap) {
  1769. this.setSnapping(this.curCoords.page, snapRestrict);
  1770. if (snapRestrict.locked) {
  1771. dx += snapRestrict.dx;
  1772. dy += snapRestrict.dy;
  1773. }
  1774. }
  1775. if (endRestrict) {
  1776. this.setRestriction(this.curCoords.page, snapRestrict);
  1777. if (snapRestrict.restricted) {
  1778. dx += snapRestrict.dx;
  1779. dy += snapRestrict.dy;
  1780. }
  1781. }
  1782. if (dx || dy) {
  1783. smoothEnd = true;
  1784. }
  1785. }
  1786. if (inertia || smoothEnd) {
  1787. copyCoords(inertiaStatus.upCoords, this.curCoords);
  1788. this.pointers[0] = inertiaStatus.startEvent = startEvent =
  1789. new InteractEvent(this, event, this.prepared.name, 'inertiastart', this.element);
  1790. inertiaStatus.t0 = now;
  1791. target.fire(inertiaStatus.startEvent);
  1792. if (inertia) {
  1793. inertiaStatus.vx0 = this.pointerDelta.client.vx;
  1794. inertiaStatus.vy0 = this.pointerDelta.client.vy;
  1795. inertiaStatus.v0 = pointerSpeed;
  1796. this.calcInertia(inertiaStatus);
  1797. var page = extend({}, this.curCoords.page),
  1798. origin = getOriginXY(target, this.element),
  1799. statusObject;
  1800. page.x = page.x + inertiaStatus.xe - origin.x;
  1801. page.y = page.y + inertiaStatus.ye - origin.y;
  1802. statusObject = {
  1803. useStatusXY: true,
  1804. x: page.x,
  1805. y: page.y,
  1806. dx: 0,
  1807. dy: 0,
  1808. snap: null
  1809. };
  1810. statusObject.snap = statusObject;
  1811. dx = dy = 0;
  1812. if (endSnap) {
  1813. var snap = this.setSnapping(this.curCoords.page, statusObject);
  1814. if (snap.locked) {
  1815. dx += snap.dx;
  1816. dy += snap.dy;
  1817. }
  1818. }
  1819. if (endRestrict) {
  1820. var restrict = this.setRestriction(this.curCoords.page, statusObject);
  1821. if (restrict.restricted) {
  1822. dx += restrict.dx;
  1823. dy += restrict.dy;
  1824. }
  1825. }
  1826. inertiaStatus.modifiedXe += dx;
  1827. inertiaStatus.modifiedYe += dy;
  1828. inertiaStatus.i = reqFrame(this.boundInertiaFrame);
  1829. }
  1830. else {
  1831. inertiaStatus.smoothEnd = true;
  1832. inertiaStatus.xe = dx;
  1833. inertiaStatus.ye = dy;
  1834. inertiaStatus.sx = inertiaStatus.sy = 0;
  1835. inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
  1836. }
  1837. inertiaStatus.active = true;
  1838. return;
  1839. }
  1840. if (endSnap || endRestrict) {
  1841. // fire a move event at the snapped coordinates
  1842. this.pointerMove(pointer, event, eventTarget, curEventTarget, true);
  1843. }
  1844. }
  1845. if (this.dragging) {
  1846. endEvent = new InteractEvent(this, event, 'drag', 'end', this.element);
  1847. var draggableElement = this.element,
  1848. drop = this.getDrop(endEvent, draggableElement);
  1849. this.dropTarget = drop.dropzone;
  1850. this.dropElement = drop.element;
  1851. var dropEvents = this.getDropEvents(event, endEvent);
  1852. if (dropEvents.leave) { this.prevDropTarget.fire(dropEvents.leave); }
  1853. if (dropEvents.enter) { this.dropTarget.fire(dropEvents.enter); }
  1854. if (dropEvents.drop ) { this.dropTarget.fire(dropEvents.drop ); }
  1855. if (dropEvents.deactivate) {
  1856. this.fireActiveDrops(dropEvents.deactivate);
  1857. }
  1858. target.fire(endEvent);
  1859. }
  1860. else if (this.resizing) {
  1861. endEvent = new InteractEvent(this, event, 'resize', 'end', this.element);
  1862. target.fire(endEvent);
  1863. }
  1864. else if (this.gesturing) {
  1865. endEvent = new InteractEvent(this, event, 'gesture', 'end', this.element);
  1866. target.fire(endEvent);
  1867. }
  1868. this.stop(event);
  1869. },
  1870. collectDrops: function (element) {
  1871. var drops = [],
  1872. elements = [],
  1873. i;
  1874. element = element || this.element;
  1875. // collect all dropzones and their elements which qualify for a drop
  1876. for (i = 0; i < interactables.length; i++) {
  1877. if (!interactables[i].options.drop.enabled) { continue; }
  1878. var current = interactables[i],
  1879. accept = current.options.drop.accept;
  1880. // test the draggable element against the dropzone's accept setting
  1881. if ((isElement(accept) && accept !== element)
  1882. || (isString(accept)
  1883. && !matchesSelector(element, accept))) {
  1884. continue;
  1885. }
  1886. // query for new elements if necessary
  1887. var dropElements = current.selector? current._context.querySelectorAll(current.selector) : [current._element];
  1888. for (var j = 0, len = dropElements.length; j < len; j++) {
  1889. var currentElement = dropElements[j];
  1890. if (currentElement === element) {
  1891. continue;
  1892. }
  1893. drops.push(current);
  1894. elements.push(currentElement);
  1895. }
  1896. }
  1897. return {
  1898. dropzones: drops,
  1899. elements: elements
  1900. };
  1901. },
  1902. fireActiveDrops: function (event) {
  1903. var i,
  1904. current,
  1905. currentElement,
  1906. prevElement;
  1907. // loop through all active dropzones and trigger event
  1908. for (i = 0; i < this.activeDrops.dropzones.length; i++) {
  1909. current = this.activeDrops.dropzones[i];
  1910. currentElement = this.activeDrops.elements [i];
  1911. // prevent trigger of duplicate events on same element
  1912. if (currentElement !== prevElement) {
  1913. // set current element as event target
  1914. event.target = currentElement;
  1915. current.fire(event);
  1916. }
  1917. prevElement = currentElement;
  1918. }
  1919. },
  1920. // Collect a new set of possible drops and save them in activeDrops.
  1921. // setActiveDrops should always be called when a drag has just started or a
  1922. // drag event happens while dynamicDrop is true
  1923. setActiveDrops: function (dragElement) {
  1924. // get dropzones and their elements that could receive the draggable
  1925. var possibleDrops = this.collectDrops(dragElement, true);
  1926. this.activeDrops.dropzones = possibleDrops.dropzones;
  1927. this.activeDrops.elements = possibleDrops.elements;
  1928. this.activeDrops.rects = [];
  1929. for (var i = 0; i < this.activeDrops.dropzones.length; i++) {
  1930. this.activeDrops.rects[i] = this.activeDrops.dropzones[i].getRect(this.activeDrops.elements[i]);
  1931. }
  1932. },
  1933. getDrop: function (event, dragElement) {
  1934. var validDrops = [];
  1935. if (dynamicDrop) {
  1936. this.setActiveDrops(dragElement);
  1937. }
  1938. // collect all dropzones and their elements which qualify for a drop
  1939. for (var j = 0; j < this.activeDrops.dropzones.length; j++) {
  1940. var current = this.activeDrops.dropzones[j],
  1941. currentElement = this.activeDrops.elements [j],
  1942. rect = this.activeDrops.rects [j];
  1943. validDrops.push(current.dropCheck(this.pointers[0], this.target, dragElement, currentElement, rect)
  1944. ? currentElement
  1945. : null);
  1946. }
  1947. // get the most appropriate dropzone based on DOM depth and order
  1948. var dropIndex = indexOfDeepestElement(validDrops),
  1949. dropzone = this.activeDrops.dropzones[dropIndex] || null,
  1950. element = this.activeDrops.elements [dropIndex] || null;
  1951. return {
  1952. dropzone: dropzone,
  1953. element: element
  1954. };
  1955. },
  1956. getDropEvents: function (pointerEvent, dragEvent) {
  1957. var dropEvents = {
  1958. enter : null,
  1959. leave : null,
  1960. activate : null,
  1961. deactivate: null,
  1962. move : null,
  1963. drop : null
  1964. };
  1965. if (this.dropElement !== this.prevDropElement) {
  1966. // if there was a prevDropTarget, create a dragleave event
  1967. if (this.prevDropTarget) {
  1968. dropEvents.leave = {
  1969. target : this.prevDropElement,
  1970. dropzone : this.prevDropTarget,
  1971. relatedTarget: dragEvent.target,
  1972. draggable : dragEvent.interactable,
  1973. dragEvent : dragEvent,
  1974. interaction : this,
  1975. timeStamp : dragEvent.timeStamp,
  1976. type : 'dragleave'
  1977. };
  1978. dragEvent.dragLeave = this.prevDropElement;
  1979. dragEvent.prevDropzone = this.prevDropTarget;
  1980. }
  1981. // if the dropTarget is not null, create a dragenter event
  1982. if (this.dropTarget) {
  1983. dropEvents.enter = {
  1984. target : this.dropElement,
  1985. dropzone : this.dropTarget,
  1986. relatedTarget: dragEvent.target,
  1987. draggable : dragEvent.interactable,
  1988. dragEvent : dragEvent,
  1989. interaction : this,
  1990. timeStamp : dragEvent.timeStamp,
  1991. type : 'dragenter'
  1992. };
  1993. dragEvent.dragEnter = this.dropElement;
  1994. dragEvent.dropzone = this.dropTarget;
  1995. }
  1996. }
  1997. if (dragEvent.type === 'dragend' && this.dropTarget) {
  1998. dropEvents.drop = {
  1999. target : this.dropElement,
  2000. dropzone : this.dropTarget,
  2001. relatedTarget: dragEvent.target,
  2002. draggable : dragEvent.interactable,
  2003. dragEvent : dragEvent,
  2004. interaction : this,
  2005. timeStamp : dragEvent.timeStamp,
  2006. type : 'drop'
  2007. };
  2008. }
  2009. if (dragEvent.type === 'dragstart') {
  2010. dropEvents.activate = {
  2011. target : null,
  2012. dropzone : null,
  2013. relatedTarget: dragEvent.target,
  2014. draggable : dragEvent.interactable,
  2015. dragEvent : dragEvent,
  2016. interaction : this,
  2017. timeStamp : dragEvent.timeStamp,
  2018. type : 'dropactivate'
  2019. };
  2020. }
  2021. if (dragEvent.type === 'dragend') {
  2022. dropEvents.deactivate = {
  2023. target : null,
  2024. dropzone : null,
  2025. relatedTarget: dragEvent.target,
  2026. draggable : dragEvent.interactable,
  2027. dragEvent : dragEvent,
  2028. interaction : this,
  2029. timeStamp : dragEvent.timeStamp,
  2030. type : 'dropdeactivate'
  2031. };
  2032. }
  2033. if (dragEvent.type === 'dragmove' && this.dropTarget) {
  2034. dropEvents.move = {
  2035. target : this.dropElement,
  2036. dropzone : this.dropTarget,
  2037. relatedTarget: dragEvent.target,
  2038. draggable : dragEvent.interactable,
  2039. dragEvent : dragEvent,
  2040. interaction : this,
  2041. dragmove : dragEvent,
  2042. timeStamp : dragEvent.timeStamp,
  2043. type : 'dropmove'
  2044. };
  2045. dragEvent.dropzone = this.dropTarget;
  2046. }
  2047. return dropEvents;
  2048. },
  2049. currentAction: function () {
  2050. return (this.dragging && 'drag') || (this.resizing && 'resize') || (this.gesturing && 'gesture') || null;
  2051. },
  2052. interacting: function () {
  2053. return this.dragging || this.resizing || this.gesturing;
  2054. },
  2055. clearTargets: function () {
  2056. if (this.target && !this.target.selector) {
  2057. this.target = this.element = null;
  2058. }
  2059. this.dropTarget = this.dropElement = this.prevDropTarget = this.prevDropElement = null;
  2060. },
  2061. stop: function (event) {
  2062. if (this.interacting()) {
  2063. autoScroll.stop();
  2064. this.matches = [];
  2065. this.matchElements = [];
  2066. var target = this.target;
  2067. if (target.options.styleCursor) {
  2068. target._doc.documentElement.style.cursor = '';
  2069. }
  2070. // prevent Default only if were previously interacting
  2071. if (event && isFunction(event.preventDefault)) {
  2072. this.checkAndPreventDefault(event, target, this.element);
  2073. }
  2074. if (this.dragging) {
  2075. this.activeDrops.dropzones = this.activeDrops.elements = this.activeDrops.rects = null;
  2076. }
  2077. this.clearTargets();
  2078. }
  2079. this.pointerIsDown = this.snapStatus.locked = this.dragging = this.resizing = this.gesturing = false;
  2080. this.prepared.name = this.prevEvent = null;
  2081. this.inertiaStatus.resumeDx = this.inertiaStatus.resumeDy = 0;
  2082. // remove pointers if their ID isn't in this.pointerIds
  2083. for (var i = 0; i < this.pointers.length; i++) {
  2084. if (indexOf(this.pointerIds, getPointerId(this.pointers[i])) === -1) {
  2085. this.pointers.splice(i, 1);
  2086. }
  2087. }
  2088. // delete interaction if it's not the only one
  2089. if (interactions.length > 1) {
  2090. interactions.splice(indexOf(interactions, this), 1);
  2091. }
  2092. },
  2093. inertiaFrame: function () {
  2094. var inertiaStatus = this.inertiaStatus,
  2095. options = this.target.options[this.prepared.name].inertia,
  2096. lambda = options.resistance,
  2097. t = new Date().getTime() / 1000 - inertiaStatus.t0;
  2098. if (t < inertiaStatus.te) {
  2099. var progress = 1 - (Math.exp(-lambda * t) - inertiaStatus.lambda_v0) / inertiaStatus.one_ve_v0;
  2100. if (inertiaStatus.modifiedXe === inertiaStatus.xe && inertiaStatus.modifiedYe === inertiaStatus.ye) {
  2101. inertiaStatus.sx = inertiaStatus.xe * progress;
  2102. inertiaStatus.sy = inertiaStatus.ye * progress;
  2103. }
  2104. else {
  2105. var quadPoint = getQuadraticCurvePoint(
  2106. 0, 0,
  2107. inertiaStatus.xe, inertiaStatus.ye,
  2108. inertiaStatus.modifiedXe, inertiaStatus.modifiedYe,
  2109. progress);
  2110. inertiaStatus.sx = quadPoint.x;
  2111. inertiaStatus.sy = quadPoint.y;
  2112. }
  2113. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2114. inertiaStatus.i = reqFrame(this.boundInertiaFrame);
  2115. }
  2116. else {
  2117. inertiaStatus.sx = inertiaStatus.modifiedXe;
  2118. inertiaStatus.sy = inertiaStatus.modifiedYe;
  2119. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2120. inertiaStatus.active = false;
  2121. this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2122. }
  2123. },
  2124. smoothEndFrame: function () {
  2125. var inertiaStatus = this.inertiaStatus,
  2126. t = new Date().getTime() - inertiaStatus.t0,
  2127. duration = this.target.options[this.prepared.name].inertia.smoothEndDuration;
  2128. if (t < duration) {
  2129. inertiaStatus.sx = easeOutQuad(t, 0, inertiaStatus.xe, duration);
  2130. inertiaStatus.sy = easeOutQuad(t, 0, inertiaStatus.ye, duration);
  2131. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2132. inertiaStatus.i = reqFrame(this.boundSmoothEndFrame);
  2133. }
  2134. else {
  2135. inertiaStatus.sx = inertiaStatus.xe;
  2136. inertiaStatus.sy = inertiaStatus.ye;
  2137. this.pointerMove(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2138. inertiaStatus.active = false;
  2139. inertiaStatus.smoothEnd = false;
  2140. this.pointerEnd(inertiaStatus.startEvent, inertiaStatus.startEvent);
  2141. }
  2142. },
  2143. addPointer: function (pointer) {
  2144. var id = getPointerId(pointer),
  2145. index = this.mouse? 0 : indexOf(this.pointerIds, id);
  2146. if (index === -1) {
  2147. index = this.pointerIds.length;
  2148. }
  2149. this.pointerIds[index] = id;
  2150. this.pointers[index] = pointer;
  2151. return index;
  2152. },
  2153. removePointer: function (pointer) {
  2154. var id = getPointerId(pointer),
  2155. index = this.mouse? 0 : indexOf(this.pointerIds, id);
  2156. if (index === -1) { return; }
  2157. if (!this.interacting()) {
  2158. this.pointers.splice(index, 1);
  2159. }
  2160. this.pointerIds .splice(index, 1);
  2161. this.downTargets.splice(index, 1);
  2162. this.downTimes .splice(index, 1);
  2163. this.holdTimers .splice(index, 1);
  2164. },
  2165. recordPointer: function (pointer) {
  2166. // Do not update pointers while inertia is active.
  2167. // The inertia start event should be this.pointers[0]
  2168. if (this.inertiaStatus.active) { return; }
  2169. var index = this.mouse? 0: indexOf(this.pointerIds, getPointerId(pointer));
  2170. if (index === -1) { return; }
  2171. this.pointers[index] = pointer;
  2172. },
  2173. collectEventTargets: function (pointer, event, eventTarget, eventType) {
  2174. var pointerIndex = this.mouse? 0 : indexOf(this.pointerIds, getPointerId(pointer));
  2175. // do not fire a tap event if the pointer was moved before being lifted
  2176. if (eventType === 'tap' && (this.pointerWasMoved
  2177. // or if the pointerup target is different to the pointerdown target
  2178. || !(this.downTargets[pointerIndex] && this.downTargets[pointerIndex] === eventTarget))) {
  2179. return;
  2180. }
  2181. var targets = [],
  2182. elements = [],
  2183. element = eventTarget;
  2184. function collectSelectors (interactable, selector, context) {
  2185. var els = ie8MatchesSelector
  2186. ? context.querySelectorAll(selector)
  2187. : undefined;
  2188. if (interactable._iEvents[eventType]
  2189. && isElement(element)
  2190. && inContext(interactable, element)
  2191. && !testIgnore(interactable, element, eventTarget)
  2192. && testAllow(interactable, element, eventTarget)
  2193. && matchesSelector(element, selector, els)) {
  2194. targets.push(interactable);
  2195. elements.push(element);
  2196. }
  2197. }
  2198. while (element) {
  2199. if (interact.isSet(element) && interact(element)._iEvents[eventType]) {
  2200. targets.push(interact(element));
  2201. elements.push(element);
  2202. }
  2203. interactables.forEachSelector(collectSelectors);
  2204. element = parentElement(element);
  2205. }
  2206. // create the tap event even if there are no listeners so that
  2207. // doubletap can still be created and fired
  2208. if (targets.length || eventType === 'tap') {
  2209. this.firePointers(pointer, event, eventTarget, targets, elements, eventType);
  2210. }
  2211. },
  2212. firePointers: function (pointer, event, eventTarget, targets, elements, eventType) {
  2213. var pointerIndex = this.mouse? 0 : indexOf(getPointerId(pointer)),
  2214. pointerEvent = {},
  2215. i,
  2216. // for tap events
  2217. interval, createNewDoubleTap;
  2218. // if it's a doubletap then the event properties would have been
  2219. // copied from the tap event and provided as the pointer argument
  2220. if (eventType === 'doubletap') {
  2221. pointerEvent = pointer;
  2222. }
  2223. else {
  2224. extend(pointerEvent, event);
  2225. if (event !== pointer) {
  2226. extend(pointerEvent, pointer);
  2227. }
  2228. pointerEvent.preventDefault = preventOriginalDefault;
  2229. pointerEvent.stopPropagation = InteractEvent.prototype.stopPropagation;
  2230. pointerEvent.stopImmediatePropagation = InteractEvent.prototype.stopImmediatePropagation;
  2231. pointerEvent.interaction = this;
  2232. pointerEvent.timeStamp = new Date().getTime();
  2233. pointerEvent.originalEvent = event;
  2234. pointerEvent.type = eventType;
  2235. pointerEvent.pointerId = getPointerId(pointer);
  2236. pointerEvent.pointerType = this.mouse? 'mouse' : !supportsPointerEvent? 'touch'
  2237. : isString(pointer.pointerType)
  2238. ? pointer.pointerType
  2239. : [,,'touch', 'pen', 'mouse'][pointer.pointerType];
  2240. }
  2241. if (eventType === 'tap') {
  2242. pointerEvent.dt = pointerEvent.timeStamp - this.downTimes[pointerIndex];
  2243. interval = pointerEvent.timeStamp - this.tapTime;
  2244. createNewDoubleTap = !!(this.prevTap && this.prevTap.type !== 'doubletap'
  2245. && this.prevTap.target === pointerEvent.target
  2246. && interval < 500);
  2247. pointerEvent.double = createNewDoubleTap;
  2248. this.tapTime = pointerEvent.timeStamp;
  2249. }
  2250. for (i = 0; i < targets.length; i++) {
  2251. pointerEvent.currentTarget = elements[i];
  2252. pointerEvent.interactable = targets[i];
  2253. targets[i].fire(pointerEvent);
  2254. if (pointerEvent.immediatePropagationStopped
  2255. ||(pointerEvent.propagationStopped && elements[i + 1] !== pointerEvent.currentTarget)) {
  2256. break;
  2257. }
  2258. }
  2259. if (createNewDoubleTap) {
  2260. var doubleTap = {};
  2261. extend(doubleTap, pointerEvent);
  2262. doubleTap.dt = interval;
  2263. doubleTap.type = 'doubletap';
  2264. this.collectEventTargets(doubleTap, event, eventTarget, 'doubletap');
  2265. this.prevTap = doubleTap;
  2266. }
  2267. else if (eventType === 'tap') {
  2268. this.prevTap = pointerEvent;
  2269. }
  2270. },
  2271. validateSelector: function (pointer, matches, matchElements) {
  2272. for (var i = 0, len = matches.length; i < len; i++) {
  2273. var match = matches[i],
  2274. matchElement = matchElements[i],
  2275. action = validateAction(match.getAction(pointer, this, matchElement), match);
  2276. if (action && withinInteractionLimit(match, matchElement, action)) {
  2277. this.target = match;
  2278. this.element = matchElement;
  2279. return action;
  2280. }
  2281. }
  2282. },
  2283. setSnapping: function (pageCoords, status) {
  2284. var snap = this.target.options[this.prepared.name].snap,
  2285. targets = [],
  2286. target,
  2287. page,
  2288. i;
  2289. status = status || this.snapStatus;
  2290. if (status.useStatusXY) {
  2291. page = { x: status.x, y: status.y };
  2292. }
  2293. else {
  2294. var origin = getOriginXY(this.target, this.element);
  2295. page = extend({}, pageCoords);
  2296. page.x -= origin.x;
  2297. page.y -= origin.y;
  2298. }
  2299. status.realX = page.x;
  2300. status.realY = page.y;
  2301. page.x = page.x - this.inertiaStatus.resumeDx;
  2302. page.y = page.y - this.inertiaStatus.resumeDy;
  2303. var len = snap.targets? snap.targets.length : 0;
  2304. for (var relIndex = 0; relIndex < this.snapOffsets.length; relIndex++) {
  2305. var relative = {
  2306. x: page.x - this.snapOffsets[relIndex].x,
  2307. y: page.y - this.snapOffsets[relIndex].y
  2308. };
  2309. for (i = 0; i < len; i++) {
  2310. if (isFunction(snap.targets[i])) {
  2311. target = snap.targets[i](relative.x, relative.y, this);
  2312. }
  2313. else {
  2314. target = snap.targets[i];
  2315. }
  2316. if (!target) { continue; }
  2317. targets.push({
  2318. x: isNumber(target.x) ? (target.x + this.snapOffsets[relIndex].x) : relative.x,
  2319. y: isNumber(target.y) ? (target.y + this.snapOffsets[relIndex].y) : relative.y,
  2320. range: isNumber(target.range)? target.range: snap.range
  2321. });
  2322. }
  2323. }
  2324. var closest = {
  2325. target: null,
  2326. inRange: false,
  2327. distance: 0,
  2328. range: 0,
  2329. dx: 0,
  2330. dy: 0
  2331. };
  2332. for (i = 0, len = targets.length; i < len; i++) {
  2333. target = targets[i];
  2334. var range = target.range,
  2335. dx = target.x - page.x,
  2336. dy = target.y - page.y,
  2337. distance = hypot(dx, dy),
  2338. inRange = distance <= range;
  2339. // Infinite targets count as being out of range
  2340. // compared to non infinite ones that are in range
  2341. if (range === Infinity && closest.inRange && closest.range !== Infinity) {
  2342. inRange = false;
  2343. }
  2344. if (!closest.target || (inRange
  2345. // is the closest target in range?
  2346. ? (closest.inRange && range !== Infinity
  2347. // the pointer is relatively deeper in this target
  2348. ? distance / range < closest.distance / closest.range
  2349. // this target has Infinite range and the closest doesn't
  2350. : (range === Infinity && closest.range !== Infinity)
  2351. // OR this target is closer that the previous closest
  2352. || distance < closest.distance)
  2353. // The other is not in range and the pointer is closer to this target
  2354. : (!closest.inRange && distance < closest.distance))) {
  2355. if (range === Infinity) {
  2356. inRange = true;
  2357. }
  2358. closest.target = target;
  2359. closest.distance = distance;
  2360. closest.range = range;
  2361. closest.inRange = inRange;
  2362. closest.dx = dx;
  2363. closest.dy = dy;
  2364. status.range = range;
  2365. }
  2366. }
  2367. var snapChanged;
  2368. if (closest.target) {
  2369. snapChanged = (status.snappedX !== closest.target.x || status.snappedY !== closest.target.y);
  2370. status.snappedX = closest.target.x;
  2371. status.snappedY = closest.target.y;
  2372. }
  2373. else {
  2374. snapChanged = true;
  2375. status.snappedX = NaN;
  2376. status.snappedY = NaN;
  2377. }
  2378. status.dx = closest.dx;
  2379. status.dy = closest.dy;
  2380. status.changed = (snapChanged || (closest.inRange && !status.locked));
  2381. status.locked = closest.inRange;
  2382. return status;
  2383. },
  2384. setRestriction: function (pageCoords, status) {
  2385. var target = this.target,
  2386. restrict = target && target.options[this.prepared.name].restrict,
  2387. restriction = restrict && restrict.restriction,
  2388. page;
  2389. if (!restriction) {
  2390. return status;
  2391. }
  2392. status = status || this.restrictStatus;
  2393. page = status.useStatusXY
  2394. ? page = { x: status.x, y: status.y }
  2395. : page = extend({}, pageCoords);
  2396. if (status.snap && status.snap.locked) {
  2397. page.x += status.snap.dx || 0;
  2398. page.y += status.snap.dy || 0;
  2399. }
  2400. page.x -= this.inertiaStatus.resumeDx;
  2401. page.y -= this.inertiaStatus.resumeDy;
  2402. status.dx = 0;
  2403. status.dy = 0;
  2404. status.restricted = false;
  2405. var rect, restrictedX, restrictedY;
  2406. if (isString(restriction)) {
  2407. if (restriction === 'parent') {
  2408. restriction = parentElement(this.element);
  2409. }
  2410. else if (restriction === 'self') {
  2411. restriction = target.getRect(this.element);
  2412. }
  2413. else {
  2414. restriction = closest(this.element, restriction);
  2415. }
  2416. if (!restriction) { return status; }
  2417. }
  2418. if (isFunction(restriction)) {
  2419. restriction = restriction(page.x, page.y, this.element);
  2420. }
  2421. if (isElement(restriction)) {
  2422. restriction = getElementRect(restriction);
  2423. }
  2424. rect = restriction;
  2425. if (!restriction) {
  2426. restrictedX = page.x;
  2427. restrictedY = page.y;
  2428. }
  2429. // object is assumed to have
  2430. // x, y, width, height or
  2431. // left, top, right, bottom
  2432. else if ('x' in restriction && 'y' in restriction) {
  2433. restrictedX = Math.max(Math.min(rect.x + rect.width - this.restrictOffset.right , page.x), rect.x + this.restrictOffset.left);
  2434. restrictedY = Math.max(Math.min(rect.y + rect.height - this.restrictOffset.bottom, page.y), rect.y + this.restrictOffset.top );
  2435. }
  2436. else {
  2437. restrictedX = Math.max(Math.min(rect.right - this.restrictOffset.right , page.x), rect.left + this.restrictOffset.left);
  2438. restrictedY = Math.max(Math.min(rect.bottom - this.restrictOffset.bottom, page.y), rect.top + this.restrictOffset.top );
  2439. }
  2440. status.dx = restrictedX - page.x;
  2441. status.dy = restrictedY - page.y;
  2442. status.changed = status.restrictedX !== restrictedX || status.restrictedY !== restrictedY;
  2443. status.restricted = !!(status.dx || status.dy);
  2444. status.restrictedX = restrictedX;
  2445. status.restrictedY = restrictedY;
  2446. return status;
  2447. },
  2448. checkAndPreventDefault: function (event, interactable, element) {
  2449. if (!(interactable = interactable || this.target)) { return; }
  2450. var options = interactable.options,
  2451. prevent = options.preventDefault;
  2452. if (prevent === 'auto' && element && !/^input$|^textarea$/i.test(element.nodeName)) {
  2453. // do not preventDefault on pointerdown if the prepared action is a drag
  2454. // and dragging can only start from a certain direction - this allows
  2455. // a touch to pan the viewport if a drag isn't in the right direction
  2456. if (/down|start/i.test(event.type)
  2457. && this.prepared.name === 'drag' && options.drag.axis !== 'xy') {
  2458. return;
  2459. }
  2460. // with manualStart, only preventDefault while interacting
  2461. if (options[this.prepared.name] && options[this.prepared.name].manualStart
  2462. && !this.interacting()) {
  2463. return;
  2464. }
  2465. event.preventDefault();
  2466. return;
  2467. }
  2468. if (prevent === 'always') {
  2469. event.preventDefault();
  2470. return;
  2471. }
  2472. },
  2473. calcInertia: function (status) {
  2474. var inertiaOptions = this.target.options[this.prepared.name].inertia,
  2475. lambda = inertiaOptions.resistance,
  2476. inertiaDur = -Math.log(inertiaOptions.endSpeed / status.v0) / lambda;
  2477. status.x0 = this.prevEvent.pageX;
  2478. status.y0 = this.prevEvent.pageY;
  2479. status.t0 = status.startEvent.timeStamp / 1000;
  2480. status.sx = status.sy = 0;
  2481. status.modifiedXe = status.xe = (status.vx0 - inertiaDur) / lambda;
  2482. status.modifiedYe = status.ye = (status.vy0 - inertiaDur) / lambda;
  2483. status.te = inertiaDur;
  2484. status.lambda_v0 = lambda / status.v0;
  2485. status.one_ve_v0 = 1 - inertiaOptions.endSpeed / status.v0;
  2486. },
  2487. _updateEventTargets: function (target, currentTarget) {
  2488. this._eventTarget = target;
  2489. this._curEventTarget = currentTarget;
  2490. }
  2491. };
  2492. function getInteractionFromPointer (pointer, eventType, eventTarget) {
  2493. var i = 0, len = interactions.length,
  2494. mouseEvent = (/mouse/i.test(pointer.pointerType || eventType)
  2495. // MSPointerEvent.MSPOINTER_TYPE_MOUSE
  2496. || pointer.pointerType === 4),
  2497. interaction;
  2498. var id = getPointerId(pointer);
  2499. // try to resume inertia with a new pointer
  2500. if (/down|start/i.test(eventType)) {
  2501. for (i = 0; i < len; i++) {
  2502. interaction = interactions[i];
  2503. var element = eventTarget;
  2504. if (interaction.inertiaStatus.active && interaction.target.options[interaction.prepared.name].inertia.allowResume
  2505. && (interaction.mouse === mouseEvent)) {
  2506. while (element) {
  2507. // if the element is the interaction element
  2508. if (element === interaction.element) {
  2509. // update the interaction's pointer
  2510. if (interaction.pointers[0]) {
  2511. interaction.removePointer(interaction.pointers[0]);
  2512. }
  2513. interaction.addPointer(pointer);
  2514. return interaction;
  2515. }
  2516. element = parentElement(element);
  2517. }
  2518. }
  2519. }
  2520. }
  2521. // if it's a mouse interaction
  2522. if (mouseEvent || !(supportsTouch || supportsPointerEvent)) {
  2523. // find a mouse interaction that's not in inertia phase
  2524. for (i = 0; i < len; i++) {
  2525. if (interactions[i].mouse && !interactions[i].inertiaStatus.active) {
  2526. return interactions[i];
  2527. }
  2528. }
  2529. // find any interaction specifically for mouse.
  2530. // if the eventType is a mousedown, and inertia is active
  2531. // ignore the interaction
  2532. for (i = 0; i < len; i++) {
  2533. if (interactions[i].mouse && !(/down/.test(eventType) && interactions[i].inertiaStatus.active)) {
  2534. return interaction;
  2535. }
  2536. }
  2537. // create a new interaction for mouse
  2538. interaction = new Interaction();
  2539. interaction.mouse = true;
  2540. return interaction;
  2541. }
  2542. // get interaction that has this pointer
  2543. for (i = 0; i < len; i++) {
  2544. if (contains(interactions[i].pointerIds, id)) {
  2545. return interactions[i];
  2546. }
  2547. }
  2548. // at this stage, a pointerUp should not return an interaction
  2549. if (/up|end|out/i.test(eventType)) {
  2550. return null;
  2551. }
  2552. // get first idle interaction
  2553. for (i = 0; i < len; i++) {
  2554. interaction = interactions[i];
  2555. if ((!interaction.prepared.name || (interaction.target.options.gesture.enabled))
  2556. && !interaction.interacting()
  2557. && !(!mouseEvent && interaction.mouse)) {
  2558. interaction.addPointer(pointer);
  2559. return interaction;
  2560. }
  2561. }
  2562. return new Interaction();
  2563. }
  2564. function doOnInteractions (method) {
  2565. return (function (event) {
  2566. var interaction,
  2567. eventTarget = getActualElement(event.path
  2568. ? event.path[0]
  2569. : event.target),
  2570. curEventTarget = getActualElement(event.currentTarget),
  2571. i;
  2572. if (supportsTouch && /touch/.test(event.type)) {
  2573. prevTouchTime = new Date().getTime();
  2574. for (i = 0; i < event.changedTouches.length; i++) {
  2575. var pointer = event.changedTouches[i];
  2576. interaction = getInteractionFromPointer(pointer, event.type, eventTarget);
  2577. if (!interaction) { continue; }
  2578. interaction._updateEventTargets(eventTarget, curEventTarget);
  2579. interaction[method](pointer, event, eventTarget, curEventTarget);
  2580. }
  2581. }
  2582. else {
  2583. if (!supportsPointerEvent && /mouse/.test(event.type)) {
  2584. // ignore mouse events while touch interactions are active
  2585. for (i = 0; i < interactions.length; i++) {
  2586. if (!interactions[i].mouse && interactions[i].pointerIsDown) {
  2587. return;
  2588. }
  2589. }
  2590. // try to ignore mouse events that are simulated by the browser
  2591. // after a touch event
  2592. if (new Date().getTime() - prevTouchTime < 500) {
  2593. return;
  2594. }
  2595. }
  2596. interaction = getInteractionFromPointer(event, event.type, eventTarget);
  2597. if (!interaction) { return; }
  2598. interaction._updateEventTargets(eventTarget, curEventTarget);
  2599. interaction[method](event, event, eventTarget, curEventTarget);
  2600. }
  2601. });
  2602. }
  2603. function InteractEvent (interaction, event, action, phase, element, related) {
  2604. var client,
  2605. page,
  2606. target = interaction.target,
  2607. snapStatus = interaction.snapStatus,
  2608. restrictStatus = interaction.restrictStatus,
  2609. pointers = interaction.pointers,
  2610. deltaSource = (target && target.options || defaultOptions).deltaSource,
  2611. sourceX = deltaSource + 'X',
  2612. sourceY = deltaSource + 'Y',
  2613. options = target? target.options: defaultOptions,
  2614. origin = getOriginXY(target, element),
  2615. starting = phase === 'start',
  2616. ending = phase === 'end',
  2617. coords = starting? interaction.startCoords : interaction.curCoords;
  2618. element = element || interaction.element;
  2619. page = extend({}, coords.page);
  2620. client = extend({}, coords.client);
  2621. page.x -= origin.x;
  2622. page.y -= origin.y;
  2623. client.x -= origin.x;
  2624. client.y -= origin.y;
  2625. var relativePoints = options[action].snap && options[action].snap.relativePoints ;
  2626. if (checkSnap(target, action) && !(starting && relativePoints && relativePoints.length)) {
  2627. this.snap = {
  2628. range : snapStatus.range,
  2629. locked : snapStatus.locked,
  2630. x : snapStatus.snappedX,
  2631. y : snapStatus.snappedY,
  2632. realX : snapStatus.realX,
  2633. realY : snapStatus.realY,
  2634. dx : snapStatus.dx,
  2635. dy : snapStatus.dy
  2636. };
  2637. if (snapStatus.locked) {
  2638. page.x += snapStatus.dx;
  2639. page.y += snapStatus.dy;
  2640. client.x += snapStatus.dx;
  2641. client.y += snapStatus.dy;
  2642. }
  2643. }
  2644. if (checkRestrict(target, action) && !(starting && options[action].restrict.elementRect) && restrictStatus.restricted) {
  2645. page.x += restrictStatus.dx;
  2646. page.y += restrictStatus.dy;
  2647. client.x += restrictStatus.dx;
  2648. client.y += restrictStatus.dy;
  2649. this.restrict = {
  2650. dx: restrictStatus.dx,
  2651. dy: restrictStatus.dy
  2652. };
  2653. }
  2654. this.pageX = page.x;
  2655. this.pageY = page.y;
  2656. this.clientX = client.x;
  2657. this.clientY = client.y;
  2658. this.x0 = interaction.startCoords.page.x;
  2659. this.y0 = interaction.startCoords.page.y;
  2660. this.clientX0 = interaction.startCoords.client.x;
  2661. this.clientY0 = interaction.startCoords.client.y;
  2662. this.ctrlKey = event.ctrlKey;
  2663. this.altKey = event.altKey;
  2664. this.shiftKey = event.shiftKey;
  2665. this.metaKey = event.metaKey;
  2666. this.button = event.button;
  2667. this.target = element;
  2668. this.t0 = interaction.downTimes[0];
  2669. this.type = action + (phase || '');
  2670. this.interaction = interaction;
  2671. this.interactable = target;
  2672. var inertiaStatus = interaction.inertiaStatus;
  2673. if (inertiaStatus.active) {
  2674. this.detail = 'inertia';
  2675. }
  2676. if (related) {
  2677. this.relatedTarget = related;
  2678. }
  2679. // end event dx, dy is difference between start and end points
  2680. if (ending) {
  2681. if (deltaSource === 'client') {
  2682. this.dx = client.x - interaction.startCoords.client.x;
  2683. this.dy = client.y - interaction.startCoords.client.y;
  2684. }
  2685. else {
  2686. this.dx = page.x - interaction.startCoords.page.x;
  2687. this.dy = page.y - interaction.startCoords.page.y;
  2688. }
  2689. }
  2690. else if (starting) {
  2691. this.dx = 0;
  2692. this.dy = 0;
  2693. }
  2694. // copy properties from previousmove if starting inertia
  2695. else if (phase === 'inertiastart') {
  2696. this.dx = interaction.prevEvent.dx;
  2697. this.dy = interaction.prevEvent.dy;
  2698. }
  2699. else {
  2700. if (deltaSource === 'client') {
  2701. this.dx = client.x - interaction.prevEvent.clientX;
  2702. this.dy = client.y - interaction.prevEvent.clientY;
  2703. }
  2704. else {
  2705. this.dx = page.x - interaction.prevEvent.pageX;
  2706. this.dy = page.y - interaction.prevEvent.pageY;
  2707. }
  2708. }
  2709. if (interaction.prevEvent && interaction.prevEvent.detail === 'inertia'
  2710. && !inertiaStatus.active
  2711. && options[action].inertia && options[action].inertia.zeroResumeDelta) {
  2712. inertiaStatus.resumeDx += this.dx;
  2713. inertiaStatus.resumeDy += this.dy;
  2714. this.dx = this.dy = 0;
  2715. }
  2716. if (action === 'resize' && interaction.resizeAxes) {
  2717. if (options.resize.square) {
  2718. if (interaction.resizeAxes === 'y') {
  2719. this.dx = this.dy;
  2720. }
  2721. else {
  2722. this.dy = this.dx;
  2723. }
  2724. this.axes = 'xy';
  2725. }
  2726. else {
  2727. this.axes = interaction.resizeAxes;
  2728. if (interaction.resizeAxes === 'x') {
  2729. this.dy = 0;
  2730. }
  2731. else if (interaction.resizeAxes === 'y') {
  2732. this.dx = 0;
  2733. }
  2734. }
  2735. }
  2736. else if (action === 'gesture') {
  2737. this.touches = [pointers[0], pointers[1]];
  2738. if (starting) {
  2739. this.distance = touchDistance(pointers, deltaSource);
  2740. this.box = touchBBox(pointers);
  2741. this.scale = 1;
  2742. this.ds = 0;
  2743. this.angle = touchAngle(pointers, undefined, deltaSource);
  2744. this.da = 0;
  2745. }
  2746. else if (ending || event instanceof InteractEvent) {
  2747. this.distance = interaction.prevEvent.distance;
  2748. this.box = interaction.prevEvent.box;
  2749. this.scale = interaction.prevEvent.scale;
  2750. this.ds = this.scale - 1;
  2751. this.angle = interaction.prevEvent.angle;
  2752. this.da = this.angle - interaction.gesture.startAngle;
  2753. }
  2754. else {
  2755. this.distance = touchDistance(pointers, deltaSource);
  2756. this.box = touchBBox(pointers);
  2757. this.scale = this.distance / interaction.gesture.startDistance;
  2758. this.angle = touchAngle(pointers, interaction.gesture.prevAngle, deltaSource);
  2759. this.ds = this.scale - interaction.gesture.prevScale;
  2760. this.da = this.angle - interaction.gesture.prevAngle;
  2761. }
  2762. }
  2763. if (starting) {
  2764. this.timeStamp = interaction.downTimes[0];
  2765. this.dt = 0;
  2766. this.duration = 0;
  2767. this.speed = 0;
  2768. this.velocityX = 0;
  2769. this.velocityY = 0;
  2770. }
  2771. else if (phase === 'inertiastart') {
  2772. this.timeStamp = interaction.prevEvent.timeStamp;
  2773. this.dt = interaction.prevEvent.dt;
  2774. this.duration = interaction.prevEvent.duration;
  2775. this.speed = interaction.prevEvent.speed;
  2776. this.velocityX = interaction.prevEvent.velocityX;
  2777. this.velocityY = interaction.prevEvent.velocityY;
  2778. }
  2779. else {
  2780. this.timeStamp = new Date().getTime();
  2781. this.dt = this.timeStamp - interaction.prevEvent.timeStamp;
  2782. this.duration = this.timeStamp - interaction.downTimes[0];
  2783. if (event instanceof InteractEvent) {
  2784. var dx = this[sourceX] - interaction.prevEvent[sourceX],
  2785. dy = this[sourceY] - interaction.prevEvent[sourceY],
  2786. dt = this.dt / 1000;
  2787. this.speed = hypot(dx, dy) / dt;
  2788. this.velocityX = dx / dt;
  2789. this.velocityY = dy / dt;
  2790. }
  2791. // if normal move or end event, use previous user event coords
  2792. else {
  2793. // speed and velocity in pixels per second
  2794. this.speed = interaction.pointerDelta[deltaSource].speed;
  2795. this.velocityX = interaction.pointerDelta[deltaSource].vx;
  2796. this.velocityY = interaction.pointerDelta[deltaSource].vy;
  2797. }
  2798. }
  2799. if ((ending || phase === 'inertiastart')
  2800. && interaction.prevEvent.speed > 600 && this.timeStamp - interaction.prevEvent.timeStamp < 150) {
  2801. var angle = 180 * Math.atan2(interaction.prevEvent.velocityY, interaction.prevEvent.velocityX) / Math.PI,
  2802. overlap = 22.5;
  2803. if (angle < 0) {
  2804. angle += 360;
  2805. }
  2806. var left = 135 - overlap <= angle && angle < 225 + overlap,
  2807. up = 225 - overlap <= angle && angle < 315 + overlap,
  2808. right = !left && (315 - overlap <= angle || angle < 45 + overlap),
  2809. down = !up && 45 - overlap <= angle && angle < 135 + overlap;
  2810. this.swipe = {
  2811. up : up,
  2812. down : down,
  2813. left : left,
  2814. right: right,
  2815. angle: angle,
  2816. speed: interaction.prevEvent.speed,
  2817. velocity: {
  2818. x: interaction.prevEvent.velocityX,
  2819. y: interaction.prevEvent.velocityY
  2820. }
  2821. };
  2822. }
  2823. }
  2824. InteractEvent.prototype = {
  2825. preventDefault: blank,
  2826. stopImmediatePropagation: function () {
  2827. this.immediatePropagationStopped = this.propagationStopped = true;
  2828. },
  2829. stopPropagation: function () {
  2830. this.propagationStopped = true;
  2831. }
  2832. };
  2833. function preventOriginalDefault () {
  2834. this.originalEvent.preventDefault();
  2835. }
  2836. function getActionCursor (action) {
  2837. var cursor = '';
  2838. if (action.name === 'drag') {
  2839. cursor = actionCursors.drag;
  2840. }
  2841. if (action.name === 'resize') {
  2842. if (action.axis) {
  2843. cursor = actionCursors[action.name + action.axis];
  2844. }
  2845. else if (action.edges) {
  2846. var cursorKey = 'resize',
  2847. edgeNames = ['top', 'bottom', 'left', 'right'];
  2848. for (var i = 0; i < 4; i++) {
  2849. if (action.edges[edgeNames[i]]) {
  2850. cursorKey += edgeNames[i];
  2851. }
  2852. }
  2853. cursor = actionCursors[cursorKey];
  2854. }
  2855. }
  2856. return cursor;
  2857. }
  2858. function checkResizeEdge (name, value, page, element, interactableElement, rect) {
  2859. // false, '', undefined, null
  2860. if (!value) { return false; }
  2861. // true value, use pointer coords and element rect
  2862. if (value === true) {
  2863. // if dimensions are negative, "switch" edges
  2864. var width = isNumber(rect.width)? rect.width : rect.right - rect.left,
  2865. height = isNumber(rect.height)? rect.height : rect.bottom - rect.top;
  2866. if (width < 0) {
  2867. if (name === 'left' ) { name = 'right'; }
  2868. else if (name === 'right') { name = 'left' ; }
  2869. }
  2870. if (height < 0) {
  2871. if (name === 'top' ) { name = 'bottom'; }
  2872. else if (name === 'bottom') { name = 'top' ; }
  2873. }
  2874. if (name === 'left' ) { return page.x < ((width >= 0? rect.left: rect.right ) + margin); }
  2875. if (name === 'top' ) { return page.y < ((height >= 0? rect.top : rect.bottom) + margin); }
  2876. if (name === 'right' ) { return page.x > ((width >= 0? rect.right : rect.left) - margin); }
  2877. if (name === 'bottom') { return page.y > ((height >= 0? rect.bottom: rect.top ) - margin); }
  2878. }
  2879. // the remaining checks require an element
  2880. if (!isElement(element)) { return false; }
  2881. return isElement(value)
  2882. // the value is an element to use as a resize handle
  2883. ? value === element
  2884. // otherwise check if element matches value as selector
  2885. : matchesUpTo(element, value, interactableElement);
  2886. }
  2887. function defaultActionChecker (pointer, interaction, element) {
  2888. var rect = this.getRect(element),
  2889. shouldResize = false,
  2890. action = null,
  2891. resizeAxes = null,
  2892. resizeEdges,
  2893. page = extend({}, interaction.curCoords.page),
  2894. options = this.options;
  2895. if (!rect) { return null; }
  2896. if (actionIsEnabled.resize && options.resize.enabled) {
  2897. var resizeOptions = options.resize;
  2898. resizeEdges = {
  2899. left: false, right: false, top: false, bottom: false
  2900. };
  2901. // if using resize.edges
  2902. if (isObject(resizeOptions.edges)) {
  2903. for (var edge in resizeEdges) {
  2904. resizeEdges[edge] = checkResizeEdge(edge,
  2905. resizeOptions.edges[edge],
  2906. page,
  2907. interaction._eventTarget,
  2908. element,
  2909. rect);
  2910. }
  2911. resizeEdges.left = resizeEdges.left && !resizeEdges.right;
  2912. resizeEdges.top = resizeEdges.top && !resizeEdges.bottom;
  2913. shouldResize = resizeEdges.left || resizeEdges.right || resizeEdges.top || resizeEdges.bottom;
  2914. }
  2915. else {
  2916. var right = options.resize.axis !== 'y' && page.x > (rect.right - margin),
  2917. bottom = options.resize.axis !== 'x' && page.y > (rect.bottom - margin);
  2918. shouldResize = right || bottom;
  2919. resizeAxes = (right? 'x' : '') + (bottom? 'y' : '');
  2920. }
  2921. }
  2922. action = shouldResize
  2923. ? 'resize'
  2924. : actionIsEnabled.drag && options.drag.enabled
  2925. ? 'drag'
  2926. : null;
  2927. if (actionIsEnabled.gesture
  2928. && interaction.pointerIds.length >=2
  2929. && !(interaction.dragging || interaction.resizing)) {
  2930. action = 'gesture';
  2931. }
  2932. if (action) {
  2933. return {
  2934. name: action,
  2935. axis: resizeAxes,
  2936. edges: resizeEdges
  2937. };
  2938. }
  2939. return null;
  2940. }
  2941. // Check if action is enabled globally and the current target supports it
  2942. // If so, return the validated action. Otherwise, return null
  2943. function validateAction (action, interactable) {
  2944. if (!isObject(action)) { return null; }
  2945. var actionName = action.name,
  2946. options = interactable.options;
  2947. if (( (actionName === 'resize' && options.resize.enabled )
  2948. || (actionName === 'drag' && options.drag.enabled )
  2949. || (actionName === 'gesture' && options.gesture.enabled))
  2950. && actionIsEnabled[actionName]) {
  2951. if (actionName === 'resize' || actionName === 'resizeyx') {
  2952. actionName = 'resizexy';
  2953. }
  2954. return action;
  2955. }
  2956. return null;
  2957. }
  2958. var listeners = {},
  2959. interactionListeners = [
  2960. 'dragStart', 'dragMove', 'resizeStart', 'resizeMove', 'gestureStart', 'gestureMove',
  2961. 'pointerOver', 'pointerOut', 'pointerHover', 'selectorDown',
  2962. 'pointerDown', 'pointerMove', 'pointerUp', 'pointerCancel', 'pointerEnd',
  2963. 'addPointer', 'removePointer', 'recordPointer',
  2964. ];
  2965. for (var i = 0, len = interactionListeners.length; i < len; i++) {
  2966. var name = interactionListeners[i];
  2967. listeners[name] = doOnInteractions(name);
  2968. }
  2969. // bound to the interactable context when a DOM event
  2970. // listener is added to a selector interactable
  2971. function delegateListener (event, useCapture) {
  2972. var fakeEvent = {},
  2973. delegated = delegatedEvents[event.type],
  2974. eventTarget = getActualElement(event.path
  2975. ? event.path[0]
  2976. : event.target),
  2977. element = eventTarget;
  2978. useCapture = useCapture? true: false;
  2979. // duplicate the event so that currentTarget can be changed
  2980. for (var prop in event) {
  2981. fakeEvent[prop] = event[prop];
  2982. }
  2983. fakeEvent.originalEvent = event;
  2984. fakeEvent.preventDefault = preventOriginalDefault;
  2985. // climb up document tree looking for selector matches
  2986. while (isElement(element)) {
  2987. for (var i = 0; i < delegated.selectors.length; i++) {
  2988. var selector = delegated.selectors[i],
  2989. context = delegated.contexts[i];
  2990. if (matchesSelector(element, selector)
  2991. && nodeContains(context, eventTarget)
  2992. && nodeContains(context, element)) {
  2993. var listeners = delegated.listeners[i];
  2994. fakeEvent.currentTarget = element;
  2995. for (var j = 0; j < listeners.length; j++) {
  2996. if (listeners[j][1] === useCapture) {
  2997. listeners[j][0](fakeEvent);
  2998. }
  2999. }
  3000. }
  3001. }
  3002. element = parentElement(element);
  3003. }
  3004. }
  3005. function delegateUseCapture (event) {
  3006. return delegateListener.call(this, event, true);
  3007. }
  3008. interactables.indexOfElement = function indexOfElement (element, context) {
  3009. context = context || document;
  3010. for (var i = 0; i < this.length; i++) {
  3011. var interactable = this[i];
  3012. if ((interactable.selector === element
  3013. && (interactable._context === context))
  3014. || (!interactable.selector && interactable._element === element)) {
  3015. return i;
  3016. }
  3017. }
  3018. return -1;
  3019. };
  3020. interactables.get = function interactableGet (element, options) {
  3021. return this[this.indexOfElement(element, options && options.context)];
  3022. };
  3023. interactables.forEachSelector = function (callback) {
  3024. for (var i = 0; i < this.length; i++) {
  3025. var interactable = this[i];
  3026. if (!interactable.selector) {
  3027. continue;
  3028. }
  3029. var ret = callback(interactable, interactable.selector, interactable._context, i, this);
  3030. if (ret !== undefined) {
  3031. return ret;
  3032. }
  3033. }
  3034. };
  3035. /*\
  3036. * interact
  3037. [ method ]
  3038. *
  3039. * The methods of this variable can be used to set elements as
  3040. * interactables and also to change various default settings.
  3041. *
  3042. * Calling it as a function and passing an element or a valid CSS selector
  3043. * string returns an Interactable object which has various methods to
  3044. * configure it.
  3045. *
  3046. - element (Element | string) The HTML or SVG Element to interact with or CSS selector
  3047. = (object) An @Interactable
  3048. *
  3049. > Usage
  3050. | interact(document.getElementById('draggable')).draggable(true);
  3051. |
  3052. | var rectables = interact('rect');
  3053. | rectables
  3054. | .gesturable(true)
  3055. | .on('gesturemove', function (event) {
  3056. | // something cool...
  3057. | })
  3058. | .autoScroll(true);
  3059. \*/
  3060. function interact (element, options) {
  3061. return interactables.get(element, options) || new Interactable(element, options);
  3062. }
  3063. /*\
  3064. * Interactable
  3065. [ property ]
  3066. **
  3067. * Object type returned by @interact
  3068. \*/
  3069. function Interactable (element, options) {
  3070. this._element = element;
  3071. this._iEvents = this._iEvents || {};
  3072. var _window;
  3073. if (trySelector(element)) {
  3074. this.selector = element;
  3075. var context = options && options.context;
  3076. _window = context? getWindow(context) : window;
  3077. if (context && (_window.Node
  3078. ? context instanceof _window.Node
  3079. : (isElement(context) || context === _window.document))) {
  3080. this._context = context;
  3081. }
  3082. }
  3083. else {
  3084. _window = getWindow(element);
  3085. if (isElement(element, _window)) {
  3086. if (PointerEvent) {
  3087. events.add(this._element, pEventTypes.down, listeners.pointerDown );
  3088. events.add(this._element, pEventTypes.move, listeners.pointerHover);
  3089. }
  3090. else {
  3091. events.add(this._element, 'mousedown' , listeners.pointerDown );
  3092. events.add(this._element, 'mousemove' , listeners.pointerHover);
  3093. events.add(this._element, 'touchstart', listeners.pointerDown );
  3094. events.add(this._element, 'touchmove' , listeners.pointerHover);
  3095. }
  3096. }
  3097. }
  3098. this._doc = _window.document;
  3099. if (!contains(documents, this._doc)) {
  3100. listenToDocument(this._doc);
  3101. }
  3102. interactables.push(this);
  3103. this.set(options);
  3104. }
  3105. Interactable.prototype = {
  3106. setOnEvents: function (action, phases) {
  3107. if (action === 'drop') {
  3108. if (isFunction(phases.ondrop) ) { this.ondrop = phases.ondrop ; }
  3109. if (isFunction(phases.ondropactivate) ) { this.ondropactivate = phases.ondropactivate ; }
  3110. if (isFunction(phases.ondropdeactivate)) { this.ondropdeactivate = phases.ondropdeactivate; }
  3111. if (isFunction(phases.ondragenter) ) { this.ondragenter = phases.ondragenter ; }
  3112. if (isFunction(phases.ondragleave) ) { this.ondragleave = phases.ondragleave ; }
  3113. if (isFunction(phases.ondropmove) ) { this.ondropmove = phases.ondropmove ; }
  3114. }
  3115. else {
  3116. action = 'on' + action;
  3117. if (isFunction(phases.onstart) ) { this[action + 'start' ] = phases.onstart ; }
  3118. if (isFunction(phases.onmove) ) { this[action + 'move' ] = phases.onmove ; }
  3119. if (isFunction(phases.onend) ) { this[action + 'end' ] = phases.onend ; }
  3120. if (isFunction(phases.oninertiastart)) { this[action + 'inertiastart' ] = phases.oninertiastart ; }
  3121. }
  3122. return this;
  3123. },
  3124. /*\
  3125. * Interactable.draggable
  3126. [ method ]
  3127. *
  3128. * Gets or sets whether drag actions can be performed on the
  3129. * Interactable
  3130. *
  3131. = (boolean) Indicates if this can be the target of drag events
  3132. | var isDraggable = interact('ul li').draggable();
  3133. * or
  3134. - options (boolean | object) #optional true/false or An object with event listeners to be fired on drag events (object makes the Interactable draggable)
  3135. = (object) This Interactable
  3136. | interact(element).draggable({
  3137. | onstart: function (event) {},
  3138. | onmove : function (event) {},
  3139. | onend : function (event) {},
  3140. |
  3141. | // the axis in which the first movement must be
  3142. | // for the drag sequence to start
  3143. | // 'xy' by default - any direction
  3144. | axis: 'x' || 'y' || 'xy',
  3145. |
  3146. | // max number of drags that can happen concurrently
  3147. | // with elements of this Interactable. Infinity by default
  3148. | max: Infinity,
  3149. |
  3150. | // max number of drags that can target the same element+Interactable
  3151. | // 1 by default
  3152. | maxPerElement: 2
  3153. | });
  3154. \*/
  3155. draggable: function (options) {
  3156. if (isObject(options)) {
  3157. this.options.drag.enabled = options.enabled === false? false: true;
  3158. this.setPerAction('drag', options);
  3159. this.setOnEvents('drag', options);
  3160. if (/^x$|^y$|^xy$/.test(options.axis)) {
  3161. this.options.drag.axis = options.axis;
  3162. }
  3163. else if (options.axis === null) {
  3164. delete this.options.drag.axis;
  3165. }
  3166. return this;
  3167. }
  3168. if (isBool(options)) {
  3169. this.options.drag.enabled = options;
  3170. return this;
  3171. }
  3172. return this.options.drag;
  3173. },
  3174. setPerAction: function (action, options) {
  3175. // for all the default per-action options
  3176. for (var option in options) {
  3177. // if this option exists for this action
  3178. if (option in defaultOptions[action]) {
  3179. // if the option in the options arg is an object value
  3180. if (isObject(options[option])) {
  3181. // duplicate the object
  3182. this.options[action][option] = extend(this.options[action][option] || {}, options[option]);
  3183. if (isObject(defaultOptions.perAction[option]) && 'enabled' in defaultOptions.perAction[option]) {
  3184. this.options[action][option].enabled = options[option].enabled === false? false : true;
  3185. }
  3186. }
  3187. else if (isBool(options[option]) && isObject(defaultOptions.perAction[option])) {
  3188. this.options[action][option].enabled = options[option];
  3189. }
  3190. else if (options[option] !== undefined) {
  3191. // or if it's not undefined, do a plain assignment
  3192. this.options[action][option] = options[option];
  3193. }
  3194. }
  3195. }
  3196. },
  3197. /*\
  3198. * Interactable.dropzone
  3199. [ method ]
  3200. *
  3201. * Returns or sets whether elements can be dropped onto this
  3202. * Interactable to trigger drop events
  3203. *
  3204. * Dropzones can receive the following events:
  3205. * - `dropactivate` and `dropdeactivate` when an acceptable drag starts and ends
  3206. * - `dragenter` and `dragleave` when a draggable enters and leaves the dropzone
  3207. * - `dragmove` when a draggable that has entered the dropzone is moved
  3208. * - `drop` when a draggable is dropped into this dropzone
  3209. *
  3210. * Use the `accept` option to allow only elements that match the given CSS selector or element.
  3211. *
  3212. * Use the `overlap` option to set how drops are checked for. The allowed values are:
  3213. * - `'pointer'`, the pointer must be over the dropzone (default)
  3214. * - `'center'`, the draggable element's center must be over the dropzone
  3215. * - a number from 0-1 which is the `(intersection area) / (draggable area)`.
  3216. * e.g. `0.5` for drop to happen when half of the area of the
  3217. * draggable is over the dropzone
  3218. *
  3219. - options (boolean | object | null) #optional The new value to be set.
  3220. | interact('.drop').dropzone({
  3221. | accept: '.can-drop' || document.getElementById('single-drop'),
  3222. | overlap: 'pointer' || 'center' || zeroToOne
  3223. | }
  3224. = (boolean | object) The current setting or this Interactable
  3225. \*/
  3226. dropzone: function (options) {
  3227. if (isObject(options)) {
  3228. this.options.drop.enabled = options.enabled === false? false: true;
  3229. this.setOnEvents('drop', options);
  3230. this.accept(options.accept);
  3231. if (/^(pointer|center)$/.test(options.overlap)) {
  3232. this.options.drop.overlap = options.overlap;
  3233. }
  3234. else if (isNumber(options.overlap)) {
  3235. this.options.drop.overlap = Math.max(Math.min(1, options.overlap), 0);
  3236. }
  3237. return this;
  3238. }
  3239. if (isBool(options)) {
  3240. this.options.drop.enabled = options;
  3241. return this;
  3242. }
  3243. return this.options.drop;
  3244. },
  3245. dropCheck: function (pointer, draggable, draggableElement, dropElement, rect) {
  3246. var dropped = false;
  3247. // if the dropzone has no rect (eg. display: none)
  3248. // call the custom dropChecker or just return false
  3249. if (!(rect = rect || this.getRect(dropElement))) {
  3250. return (this.options.dropChecker
  3251. ? this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement)
  3252. : false);
  3253. }
  3254. var dropOverlap = this.options.drop.overlap;
  3255. if (dropOverlap === 'pointer') {
  3256. var page = getPageXY(pointer),
  3257. origin = getOriginXY(draggable, draggableElement),
  3258. horizontal,
  3259. vertical;
  3260. page.x += origin.x;
  3261. page.y += origin.y;
  3262. horizontal = (page.x > rect.left) && (page.x < rect.right);
  3263. vertical = (page.y > rect.top ) && (page.y < rect.bottom);
  3264. dropped = horizontal && vertical;
  3265. }
  3266. var dragRect = draggable.getRect(draggableElement);
  3267. if (dropOverlap === 'center') {
  3268. var cx = dragRect.left + dragRect.width / 2,
  3269. cy = dragRect.top + dragRect.height / 2;
  3270. dropped = cx >= rect.left && cx <= rect.right && cy >= rect.top && cy <= rect.bottom;
  3271. }
  3272. if (isNumber(dropOverlap)) {
  3273. var overlapArea = (Math.max(0, Math.min(rect.right , dragRect.right ) - Math.max(rect.left, dragRect.left))
  3274. * Math.max(0, Math.min(rect.bottom, dragRect.bottom) - Math.max(rect.top , dragRect.top ))),
  3275. overlapRatio = overlapArea / (dragRect.width * dragRect.height);
  3276. dropped = overlapRatio >= dropOverlap;
  3277. }
  3278. if (this.options.dropChecker) {
  3279. dropped = this.options.dropChecker(pointer, dropped, this, dropElement, draggable, draggableElement);
  3280. }
  3281. return dropped;
  3282. },
  3283. /*\
  3284. * Interactable.dropChecker
  3285. [ method ]
  3286. *
  3287. * Gets or sets the function used to check if a dragged element is
  3288. * over this Interactable. See @Interactable.dropCheck.
  3289. *
  3290. - checker (function) #optional
  3291. * The checker is a function which takes a mouseUp/touchEnd event as a
  3292. * parameter and returns true or false to indicate if the the current
  3293. * draggable can be dropped into this Interactable
  3294. *
  3295. - checker (function) The function that will be called when checking for a drop
  3296. * The checker function takes the following arguments:
  3297. *
  3298. - pointer (MouseEvent | PointerEvent | Touch) The pointer/event that ends a drag
  3299. - dropped (boolean) The value from the default drop check
  3300. - dropzone (Interactable) The dropzone interactable
  3301. - dropElement (Element) The dropzone element
  3302. - draggable (Interactable) The Interactable being dragged
  3303. - draggableElement (Element) The actual element that's being dragged
  3304. *
  3305. = (Function | Interactable) The checker function or this Interactable
  3306. \*/
  3307. dropChecker: function (checker) {
  3308. if (isFunction(checker)) {
  3309. this.options.dropChecker = checker;
  3310. return this;
  3311. }
  3312. if (checker === null) {
  3313. delete this.options.getRect;
  3314. return this;
  3315. }
  3316. return this.options.dropChecker;
  3317. },
  3318. /*\
  3319. * Interactable.accept
  3320. [ method ]
  3321. *
  3322. * Deprecated. add an `accept` property to the options object passed to
  3323. * @Interactable.dropzone instead.
  3324. *
  3325. * Gets or sets the Element or CSS selector match that this
  3326. * Interactable accepts if it is a dropzone.
  3327. *
  3328. - newValue (Element | string | null) #optional
  3329. * If it is an Element, then only that element can be dropped into this dropzone.
  3330. * If it is a string, the element being dragged must match it as a selector.
  3331. * If it is null, the accept options is cleared - it accepts any element.
  3332. *
  3333. = (string | Element | null | Interactable) The current accept option if given `undefined` or this Interactable
  3334. \*/
  3335. accept: function (newValue) {
  3336. if (isElement(newValue)) {
  3337. this.options.drop.accept = newValue;
  3338. return this;
  3339. }
  3340. // test if it is a valid CSS selector
  3341. if (trySelector(newValue)) {
  3342. this.options.drop.accept = newValue;
  3343. return this;
  3344. }
  3345. if (newValue === null) {
  3346. delete this.options.drop.accept;
  3347. return this;
  3348. }
  3349. return this.options.drop.accept;
  3350. },
  3351. /*\
  3352. * Interactable.resizable
  3353. [ method ]
  3354. *
  3355. * Gets or sets whether resize actions can be performed on the
  3356. * Interactable
  3357. *
  3358. = (boolean) Indicates if this can be the target of resize elements
  3359. | var isResizeable = interact('input[type=text]').resizable();
  3360. * or
  3361. - options (boolean | object) #optional true/false or An object with event listeners to be fired on resize events (object makes the Interactable resizable)
  3362. = (object) This Interactable
  3363. | interact(element).resizable({
  3364. | onstart: function (event) {},
  3365. | onmove : function (event) {},
  3366. | onend : function (event) {},
  3367. |
  3368. | edges: {
  3369. | top : true, // Use pointer coords to check for resize.
  3370. | left : false, // Disable resizing from left edge.
  3371. | bottom: '.resize-s',// Resize if pointer target matches selector
  3372. | right : handleEl // Resize if pointer target is the given Element
  3373. | },
  3374. |
  3375. | // a value of 'none' will limit the resize rect to a minimum of 0x0
  3376. | // 'negate' will allow the rect to have negative width/height
  3377. | // 'reposition' will keep the width/height positive by swapping
  3378. | // the top and bottom edges and/or swapping the left and right edges
  3379. | invert: 'none' || 'negate' || 'reposition'
  3380. |
  3381. | // limit multiple resizes.
  3382. | // See the explanation in the @Interactable.draggable example
  3383. | max: Infinity,
  3384. | maxPerElement: 1,
  3385. | });
  3386. \*/
  3387. resizable: function (options) {
  3388. if (isObject(options)) {
  3389. this.options.resize.enabled = options.enabled === false? false: true;
  3390. this.setPerAction('resize', options);
  3391. this.setOnEvents('resize', options);
  3392. if (/^x$|^y$|^xy$/.test(options.axis)) {
  3393. this.options.resize.axis = options.axis;
  3394. }
  3395. else if (options.axis === null) {
  3396. this.options.resize.axis = defaultOptions.resize.axis;
  3397. }
  3398. if (isBool(options.square)) {
  3399. this.options.resize.square = options.square;
  3400. }
  3401. return this;
  3402. }
  3403. if (isBool(options)) {
  3404. this.options.resize.enabled = options;
  3405. return this;
  3406. }
  3407. return this.options.resize;
  3408. },
  3409. /*\
  3410. * Interactable.squareResize
  3411. [ method ]
  3412. *
  3413. * Deprecated. Add a `square: true || false` property to @Interactable.resizable instead
  3414. *
  3415. * Gets or sets whether resizing is forced 1:1 aspect
  3416. *
  3417. = (boolean) Current setting
  3418. *
  3419. * or
  3420. *
  3421. - newValue (boolean) #optional
  3422. = (object) this Interactable
  3423. \*/
  3424. squareResize: function (newValue) {
  3425. if (isBool(newValue)) {
  3426. this.options.resize.square = newValue;
  3427. return this;
  3428. }
  3429. if (newValue === null) {
  3430. delete this.options.resize.square;
  3431. return this;
  3432. }
  3433. return this.options.resize.square;
  3434. },
  3435. /*\
  3436. * Interactable.gesturable
  3437. [ method ]
  3438. *
  3439. * Gets or sets whether multitouch gestures can be performed on the
  3440. * Interactable's element
  3441. *
  3442. = (boolean) Indicates if this can be the target of gesture events
  3443. | var isGestureable = interact(element).gesturable();
  3444. * or
  3445. - options (boolean | object) #optional true/false or An object with event listeners to be fired on gesture events (makes the Interactable gesturable)
  3446. = (object) this Interactable
  3447. | interact(element).gesturable({
  3448. | onstart: function (event) {},
  3449. | onmove : function (event) {},
  3450. | onend : function (event) {},
  3451. |
  3452. | // limit multiple gestures.
  3453. | // See the explanation in @Interactable.draggable example
  3454. | max: Infinity,
  3455. | maxPerElement: 1,
  3456. | });
  3457. \*/
  3458. gesturable: function (options) {
  3459. if (isObject(options)) {
  3460. this.options.gesture.enabled = options.enabled === false? false: true;
  3461. this.setPerAction('gesture', options);
  3462. this.setOnEvents('gesture', options);
  3463. return this;
  3464. }
  3465. if (isBool(options)) {
  3466. this.options.gesture.enabled = options;
  3467. return this;
  3468. }
  3469. return this.options.gesture;
  3470. },
  3471. /*\
  3472. * Interactable.autoScroll
  3473. [ method ]
  3474. **
  3475. * Deprecated. Add an `autoscroll` property to the options object
  3476. * passed to @Interactable.draggable or @Interactable.resizable instead.
  3477. *
  3478. * Returns or sets whether dragging and resizing near the edges of the
  3479. * window/container trigger autoScroll for this Interactable
  3480. *
  3481. = (object) Object with autoScroll properties
  3482. *
  3483. * or
  3484. *
  3485. - options (object | boolean) #optional
  3486. * options can be:
  3487. * - an object with margin, distance and interval properties,
  3488. * - true or false to enable or disable autoScroll or
  3489. = (Interactable) this Interactable
  3490. \*/
  3491. autoScroll: function (options) {
  3492. if (isObject(options)) {
  3493. options = extend({ actions: ['drag', 'resize']}, options);
  3494. }
  3495. else if (isBool(options)) {
  3496. options = { actions: ['drag', 'resize'], enabled: options };
  3497. }
  3498. return this.setOptions('autoScroll', options);
  3499. },
  3500. /*\
  3501. * Interactable.snap
  3502. [ method ]
  3503. **
  3504. * Deprecated. Add a `snap` property to the options object passed
  3505. * to @Interactable.draggable or @Interactable.resizable instead.
  3506. *
  3507. * Returns or sets if and how action coordinates are snapped. By
  3508. * default, snapping is relative to the pointer coordinates. You can
  3509. * change this by setting the
  3510. * [`elementOrigin`](https://github.com/taye/interact.js/pull/72).
  3511. **
  3512. = (boolean | object) `false` if snap is disabled; object with snap properties if snap is enabled
  3513. **
  3514. * or
  3515. **
  3516. - options (object | boolean | null) #optional
  3517. = (Interactable) this Interactable
  3518. > Usage
  3519. | interact(document.querySelector('#thing')).snap({
  3520. | targets: [
  3521. | // snap to this specific point
  3522. | {
  3523. | x: 100,
  3524. | y: 100,
  3525. | range: 25
  3526. | },
  3527. | // give this function the x and y page coords and snap to the object returned
  3528. | function (x, y) {
  3529. | return {
  3530. | x: x,
  3531. | y: (75 + 50 * Math.sin(x * 0.04)),
  3532. | range: 40
  3533. | };
  3534. | },
  3535. | // create a function that snaps to a grid
  3536. | interact.createSnapGrid({
  3537. | x: 50,
  3538. | y: 50,
  3539. | range: 10, // optional
  3540. | offset: { x: 5, y: 10 } // optional
  3541. | })
  3542. | ],
  3543. | // do not snap during normal movement.
  3544. | // Instead, trigger only one snapped move event
  3545. | // immediately before the end event.
  3546. | endOnly: true,
  3547. |
  3548. | relativePoints: [
  3549. | { x: 0, y: 0 }, // snap relative to the top left of the element
  3550. | { x: 1, y: 1 }, // and also to the bottom right
  3551. | ],
  3552. |
  3553. | // offset the snap target coordinates
  3554. | // can be an object with x/y or 'startCoords'
  3555. | offset: { x: 50, y: 50 }
  3556. | }
  3557. | });
  3558. \*/
  3559. snap: function (options) {
  3560. var ret = this.setOptions('snap', options);
  3561. if (ret === this) { return this; }
  3562. return ret.drag;
  3563. },
  3564. setOptions: function (option, options) {
  3565. var actions = options && isArray(options.actions)
  3566. ? options.actions
  3567. : ['drag'];
  3568. var i;
  3569. if (isObject(options) || isBool(options)) {
  3570. for (i = 0; i < actions.length; i++) {
  3571. var action = /resize/.test(actions[i])? 'resize' : actions[i];
  3572. if (!isObject(this.options[action])) { continue; }
  3573. var thisOption = this.options[action][option];
  3574. if (isObject(options)) {
  3575. extend(thisOption, options);
  3576. thisOption.enabled = options.enabled === false? false: true;
  3577. if (option === 'snap') {
  3578. if (thisOption.mode === 'grid') {
  3579. thisOption.targets = [
  3580. interact.createSnapGrid(extend({
  3581. offset: thisOption.gridOffset || { x: 0, y: 0 }
  3582. }, thisOption.grid || {}))
  3583. ];
  3584. }
  3585. else if (thisOption.mode === 'anchor') {
  3586. thisOption.targets = thisOption.anchors;
  3587. }
  3588. else if (thisOption.mode === 'path') {
  3589. thisOption.targets = thisOption.paths;
  3590. }
  3591. if ('elementOrigin' in options) {
  3592. thisOption.relativePoints = [options.elementOrigin];
  3593. }
  3594. }
  3595. }
  3596. else if (isBool(options)) {
  3597. thisOption.enabled = options;
  3598. }
  3599. }
  3600. return this;
  3601. }
  3602. var ret = {},
  3603. allActions = ['drag', 'resize', 'gesture'];
  3604. for (i = 0; i < allActions.length; i++) {
  3605. if (option in defaultOptions[allActions[i]]) {
  3606. ret[allActions[i]] = this.options[allActions[i]][option];
  3607. }
  3608. }
  3609. return ret;
  3610. },
  3611. /*\
  3612. * Interactable.inertia
  3613. [ method ]
  3614. **
  3615. * Deprecated. Add an `inertia` property to the options object passed
  3616. * to @Interactable.draggable or @Interactable.resizable instead.
  3617. *
  3618. * Returns or sets if and how events continue to run after the pointer is released
  3619. **
  3620. = (boolean | object) `false` if inertia is disabled; `object` with inertia properties if inertia is enabled
  3621. **
  3622. * or
  3623. **
  3624. - options (object | boolean | null) #optional
  3625. = (Interactable) this Interactable
  3626. > Usage
  3627. | // enable and use default settings
  3628. | interact(element).inertia(true);
  3629. |
  3630. | // enable and use custom settings
  3631. | interact(element).inertia({
  3632. | // value greater than 0
  3633. | // high values slow the object down more quickly
  3634. | resistance : 16,
  3635. |
  3636. | // the minimum launch speed (pixels per second) that results in inertia start
  3637. | minSpeed : 200,
  3638. |
  3639. | // inertia will stop when the object slows down to this speed
  3640. | endSpeed : 20,
  3641. |
  3642. | // boolean; should actions be resumed when the pointer goes down during inertia
  3643. | allowResume : true,
  3644. |
  3645. | // boolean; should the jump when resuming from inertia be ignored in event.dx/dy
  3646. | zeroResumeDelta: false,
  3647. |
  3648. | // if snap/restrict are set to be endOnly and inertia is enabled, releasing
  3649. | // the pointer without triggering inertia will animate from the release
  3650. | // point to the snaped/restricted point in the given amount of time (ms)
  3651. | smoothEndDuration: 300,
  3652. |
  3653. | // an array of action types that can have inertia (no gesture)
  3654. | actions : ['drag', 'resize']
  3655. | });
  3656. |
  3657. | // reset custom settings and use all defaults
  3658. | interact(element).inertia(null);
  3659. \*/
  3660. inertia: function (options) {
  3661. var ret = this.setOptions('inertia', options);
  3662. if (ret === this) { return this; }
  3663. return ret.drag;
  3664. },
  3665. getAction: function (pointer, interaction, element) {
  3666. var action = this.defaultActionChecker(pointer, interaction, element);
  3667. if (this.options.actionChecker) {
  3668. return this.options.actionChecker(pointer, action, this, element, interaction);
  3669. }
  3670. return action;
  3671. },
  3672. defaultActionChecker: defaultActionChecker,
  3673. /*\
  3674. * Interactable.actionChecker
  3675. [ method ]
  3676. *
  3677. * Gets or sets the function used to check action to be performed on
  3678. * pointerDown
  3679. *
  3680. - checker (function | null) #optional A function which takes a pointer event, defaultAction string, interactable, element and interaction as parameters and returns an object with name property 'drag' 'resize' or 'gesture' and optionally an `edges` object with boolean 'top', 'left', 'bottom' and right props.
  3681. = (Function | Interactable) The checker function or this Interactable
  3682. *
  3683. | interact('.resize-horiz').actionChecker(function (defaultAction, interactable) {
  3684. | return {
  3685. | // resize from the top and right edges
  3686. | name: 'resize',
  3687. | edges: { top: true, right: true }
  3688. | };
  3689. | });
  3690. \*/
  3691. actionChecker: function (checker) {
  3692. if (isFunction(checker)) {
  3693. this.options.actionChecker = checker;
  3694. return this;
  3695. }
  3696. if (checker === null) {
  3697. delete this.options.actionChecker;
  3698. return this;
  3699. }
  3700. return this.options.actionChecker;
  3701. },
  3702. /*\
  3703. * Interactable.getRect
  3704. [ method ]
  3705. *
  3706. * The default function to get an Interactables bounding rect. Can be
  3707. * overridden using @Interactable.rectChecker.
  3708. *
  3709. - element (Element) #optional The element to measure.
  3710. = (object) The object's bounding rectangle.
  3711. o {
  3712. o top : 0,
  3713. o left : 0,
  3714. o bottom: 0,
  3715. o right : 0,
  3716. o width : 0,
  3717. o height: 0
  3718. o }
  3719. \*/
  3720. getRect: function rectCheck (element) {
  3721. element = element || this._element;
  3722. if (this.selector && !(isElement(element))) {
  3723. element = this._context.querySelector(this.selector);
  3724. }
  3725. return getElementRect(element);
  3726. },
  3727. /*\
  3728. * Interactable.rectChecker
  3729. [ method ]
  3730. *
  3731. * Returns or sets the function used to calculate the interactable's
  3732. * element's rectangle
  3733. *
  3734. - checker (function) #optional A function which returns this Interactable's bounding rectangle. See @Interactable.getRect
  3735. = (function | object) The checker function or this Interactable
  3736. \*/
  3737. rectChecker: function (checker) {
  3738. if (isFunction(checker)) {
  3739. this.getRect = checker;
  3740. return this;
  3741. }
  3742. if (checker === null) {
  3743. delete this.options.getRect;
  3744. return this;
  3745. }
  3746. return this.getRect;
  3747. },
  3748. /*\
  3749. * Interactable.styleCursor
  3750. [ method ]
  3751. *
  3752. * Returns or sets whether the action that would be performed when the
  3753. * mouse on the element are checked on `mousemove` so that the cursor
  3754. * may be styled appropriately
  3755. *
  3756. - newValue (boolean) #optional
  3757. = (boolean | Interactable) The current setting or this Interactable
  3758. \*/
  3759. styleCursor: function (newValue) {
  3760. if (isBool(newValue)) {
  3761. this.options.styleCursor = newValue;
  3762. return this;
  3763. }
  3764. if (newValue === null) {
  3765. delete this.options.styleCursor;
  3766. return this;
  3767. }
  3768. return this.options.styleCursor;
  3769. },
  3770. /*\
  3771. * Interactable.preventDefault
  3772. [ method ]
  3773. *
  3774. * Returns or sets whether to prevent the browser's default behaviour
  3775. * in response to pointer events. Can be set to:
  3776. * - `'always'` to always prevent
  3777. * - `'never'` to never prevent
  3778. * - `'auto'` to let interact.js try to determine what would be best
  3779. *
  3780. - newValue (string) #optional `true`, `false` or `'auto'`
  3781. = (string | Interactable) The current setting or this Interactable
  3782. \*/
  3783. preventDefault: function (newValue) {
  3784. if (/^(always|never|auto)$/.test(newValue)) {
  3785. this.options.preventDefault = newValue;
  3786. return this;
  3787. }
  3788. if (isBool(newValue)) {
  3789. this.options.preventDefault = newValue? 'always' : 'never';
  3790. return this;
  3791. }
  3792. return this.options.preventDefault;
  3793. },
  3794. /*\
  3795. * Interactable.origin
  3796. [ method ]
  3797. *
  3798. * Gets or sets the origin of the Interactable's element. The x and y
  3799. * of the origin will be subtracted from action event coordinates.
  3800. *
  3801. - origin (object | string) #optional An object eg. { x: 0, y: 0 } or string 'parent', 'self' or any CSS selector
  3802. * OR
  3803. - origin (Element) #optional An HTML or SVG Element whose rect will be used
  3804. **
  3805. = (object) The current origin or this Interactable
  3806. \*/
  3807. origin: function (newValue) {
  3808. if (trySelector(newValue)) {
  3809. this.options.origin = newValue;
  3810. return this;
  3811. }
  3812. else if (isObject(newValue)) {
  3813. this.options.origin = newValue;
  3814. return this;
  3815. }
  3816. return this.options.origin;
  3817. },
  3818. /*\
  3819. * Interactable.deltaSource
  3820. [ method ]
  3821. *
  3822. * Returns or sets the mouse coordinate types used to calculate the
  3823. * movement of the pointer.
  3824. *
  3825. - newValue (string) #optional Use 'client' if you will be scrolling while interacting; Use 'page' if you want autoScroll to work
  3826. = (string | object) The current deltaSource or this Interactable
  3827. \*/
  3828. deltaSource: function (newValue) {
  3829. if (newValue === 'page' || newValue === 'client') {
  3830. this.options.deltaSource = newValue;
  3831. return this;
  3832. }
  3833. return this.options.deltaSource;
  3834. },
  3835. /*\
  3836. * Interactable.restrict
  3837. [ method ]
  3838. **
  3839. * Deprecated. Add a `restrict` property to the options object passed to
  3840. * @Interactable.draggable, @Interactable.resizable or @Interactable.gesturable instead.
  3841. *
  3842. * Returns or sets the rectangles within which actions on this
  3843. * interactable (after snap calculations) are restricted. By default,
  3844. * restricting is relative to the pointer coordinates. You can change
  3845. * this by setting the
  3846. * [`elementRect`](https://github.com/taye/interact.js/pull/72).
  3847. **
  3848. - options (object) #optional an object with keys drag, resize, and/or gesture whose values are rects, Elements, CSS selectors, or 'parent' or 'self'
  3849. = (object) The current restrictions object or this Interactable
  3850. **
  3851. | interact(element).restrict({
  3852. | // the rect will be `interact.getElementRect(element.parentNode)`
  3853. | drag: element.parentNode,
  3854. |
  3855. | // x and y are relative to the the interactable's origin
  3856. | resize: { x: 100, y: 100, width: 200, height: 200 }
  3857. | })
  3858. |
  3859. | interact('.draggable').restrict({
  3860. | // the rect will be the selected element's parent
  3861. | drag: 'parent',
  3862. |
  3863. | // do not restrict during normal movement.
  3864. | // Instead, trigger only one restricted move event
  3865. | // immediately before the end event.
  3866. | endOnly: true,
  3867. |
  3868. | // https://github.com/taye/interact.js/pull/72#issue-41813493
  3869. | elementRect: { top: 0, left: 0, bottom: 1, right: 1 }
  3870. | });
  3871. \*/
  3872. restrict: function (options) {
  3873. if (!isObject(options)) {
  3874. return this.setOptions('restrict', options);
  3875. }
  3876. var actions = ['drag', 'resize', 'gesture'],
  3877. ret;
  3878. for (var i = 0; i < actions.length; i++) {
  3879. var action = actions[i];
  3880. if (action in options) {
  3881. var perAction = extend({
  3882. actions: [action],
  3883. restriction: options[action]
  3884. }, options);
  3885. ret = this.setOptions('restrict', perAction);
  3886. }
  3887. }
  3888. return ret;
  3889. },
  3890. /*\
  3891. * Interactable.context
  3892. [ method ]
  3893. *
  3894. * Gets the selector context Node of the Interactable. The default is `window.document`.
  3895. *
  3896. = (Node) The context Node of this Interactable
  3897. **
  3898. \*/
  3899. context: function () {
  3900. return this._context;
  3901. },
  3902. _context: document,
  3903. /*\
  3904. * Interactable.ignoreFrom
  3905. [ method ]
  3906. *
  3907. * If the target of the `mousedown`, `pointerdown` or `touchstart`
  3908. * event or any of it's parents match the given CSS selector or
  3909. * Element, no drag/resize/gesture is started.
  3910. *
  3911. - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to not ignore any elements
  3912. = (string | Element | object) The current ignoreFrom value or this Interactable
  3913. **
  3914. | interact(element, { ignoreFrom: document.getElementById('no-action') });
  3915. | // or
  3916. | interact(element).ignoreFrom('input, textarea, a');
  3917. \*/
  3918. ignoreFrom: function (newValue) {
  3919. if (trySelector(newValue)) { // CSS selector to match event.target
  3920. this.options.ignoreFrom = newValue;
  3921. return this;
  3922. }
  3923. if (isElement(newValue)) { // specific element
  3924. this.options.ignoreFrom = newValue;
  3925. return this;
  3926. }
  3927. return this.options.ignoreFrom;
  3928. },
  3929. /*\
  3930. * Interactable.allowFrom
  3931. [ method ]
  3932. *
  3933. * A drag/resize/gesture is started only If the target of the
  3934. * `mousedown`, `pointerdown` or `touchstart` event or any of it's
  3935. * parents match the given CSS selector or Element.
  3936. *
  3937. - newValue (string | Element | null) #optional a CSS selector string, an Element or `null` to allow from any element
  3938. = (string | Element | object) The current allowFrom value or this Interactable
  3939. **
  3940. | interact(element, { allowFrom: document.getElementById('drag-handle') });
  3941. | // or
  3942. | interact(element).allowFrom('.handle');
  3943. \*/
  3944. allowFrom: function (newValue) {
  3945. if (trySelector(newValue)) { // CSS selector to match event.target
  3946. this.options.allowFrom = newValue;
  3947. return this;
  3948. }
  3949. if (isElement(newValue)) { // specific element
  3950. this.options.allowFrom = newValue;
  3951. return this;
  3952. }
  3953. return this.options.allowFrom;
  3954. },
  3955. /*\
  3956. * Interactable.element
  3957. [ method ]
  3958. *
  3959. * If this is not a selector Interactable, it returns the element this
  3960. * interactable represents
  3961. *
  3962. = (Element) HTML / SVG Element
  3963. \*/
  3964. element: function () {
  3965. return this._element;
  3966. },
  3967. /*\
  3968. * Interactable.fire
  3969. [ method ]
  3970. *
  3971. * Calls listeners for the given InteractEvent type bound globally
  3972. * and directly to this Interactable
  3973. *
  3974. - iEvent (InteractEvent) The InteractEvent object to be fired on this Interactable
  3975. = (Interactable) this Interactable
  3976. \*/
  3977. fire: function (iEvent) {
  3978. if (!(iEvent && iEvent.type) || !contains(eventTypes, iEvent.type)) {
  3979. return this;
  3980. }
  3981. var listeners,
  3982. i,
  3983. len,
  3984. onEvent = 'on' + iEvent.type,
  3985. funcName = '';
  3986. // Interactable#on() listeners
  3987. if (iEvent.type in this._iEvents) {
  3988. listeners = this._iEvents[iEvent.type];
  3989. for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {
  3990. funcName = listeners[i].name;
  3991. listeners[i](iEvent);
  3992. }
  3993. }
  3994. // interactable.onevent listener
  3995. if (isFunction(this[onEvent])) {
  3996. funcName = this[onEvent].name;
  3997. this[onEvent](iEvent);
  3998. }
  3999. // interact.on() listeners
  4000. if (iEvent.type in globalEvents && (listeners = globalEvents[iEvent.type])) {
  4001. for (i = 0, len = listeners.length; i < len && !iEvent.immediatePropagationStopped; i++) {
  4002. funcName = listeners[i].name;
  4003. listeners[i](iEvent);
  4004. }
  4005. }
  4006. return this;
  4007. },
  4008. /*\
  4009. * Interactable.on
  4010. [ method ]
  4011. *
  4012. * Binds a listener for an InteractEvent or DOM event.
  4013. *
  4014. - eventType (string | array | object) The types of events to listen for
  4015. - listener (function) The function to be called on the given event(s)
  4016. - useCapture (boolean) #optional useCapture flag for addEventListener
  4017. = (object) This Interactable
  4018. \*/
  4019. on: function (eventType, listener, useCapture) {
  4020. var i;
  4021. if (isString(eventType) && eventType.search(' ') !== -1) {
  4022. eventType = eventType.trim().split(/ +/);
  4023. }
  4024. if (isArray(eventType)) {
  4025. for (i = 0; i < eventType.length; i++) {
  4026. this.on(eventType[i], listener, useCapture);
  4027. }
  4028. return this;
  4029. }
  4030. if (isObject(eventType)) {
  4031. for (var prop in eventType) {
  4032. this.on(prop, eventType[prop], listener);
  4033. }
  4034. return this;
  4035. }
  4036. if (eventType === 'wheel') {
  4037. eventType = wheelEvent;
  4038. }
  4039. // convert to boolean
  4040. useCapture = useCapture? true: false;
  4041. if (contains(eventTypes, eventType)) {
  4042. // if this type of event was never bound to this Interactable
  4043. if (!(eventType in this._iEvents)) {
  4044. this._iEvents[eventType] = [listener];
  4045. }
  4046. else {
  4047. this._iEvents[eventType].push(listener);
  4048. }
  4049. }
  4050. // delegated event for selector
  4051. else if (this.selector) {
  4052. if (!delegatedEvents[eventType]) {
  4053. delegatedEvents[eventType] = {
  4054. selectors: [],
  4055. contexts : [],
  4056. listeners: []
  4057. };
  4058. // add delegate listener functions
  4059. for (i = 0; i < documents.length; i++) {
  4060. events.add(documents[i], eventType, delegateListener);
  4061. events.add(documents[i], eventType, delegateUseCapture, true);
  4062. }
  4063. }
  4064. var delegated = delegatedEvents[eventType],
  4065. index;
  4066. for (index = delegated.selectors.length - 1; index >= 0; index--) {
  4067. if (delegated.selectors[index] === this.selector
  4068. && delegated.contexts[index] === this._context) {
  4069. break;
  4070. }
  4071. }
  4072. if (index === -1) {
  4073. index = delegated.selectors.length;
  4074. delegated.selectors.push(this.selector);
  4075. delegated.contexts .push(this._context);
  4076. delegated.listeners.push([]);
  4077. }
  4078. // keep listener and useCapture flag
  4079. delegated.listeners[index].push([listener, useCapture]);
  4080. }
  4081. else {
  4082. events.add(this._element, eventType, listener, useCapture);
  4083. }
  4084. return this;
  4085. },
  4086. /*\
  4087. * Interactable.off
  4088. [ method ]
  4089. *
  4090. * Removes an InteractEvent or DOM event listener
  4091. *
  4092. - eventType (string | array | object) The types of events that were listened for
  4093. - listener (function) The listener function to be removed
  4094. - useCapture (boolean) #optional useCapture flag for removeEventListener
  4095. = (object) This Interactable
  4096. \*/
  4097. off: function (eventType, listener, useCapture) {
  4098. var i;
  4099. if (isString(eventType) && eventType.search(' ') !== -1) {
  4100. eventType = eventType.trim().split(/ +/);
  4101. }
  4102. if (isArray(eventType)) {
  4103. for (i = 0; i < eventType.length; i++) {
  4104. this.off(eventType[i], listener, useCapture);
  4105. }
  4106. return this;
  4107. }
  4108. if (isObject(eventType)) {
  4109. for (var prop in eventType) {
  4110. this.off(prop, eventType[prop], listener);
  4111. }
  4112. return this;
  4113. }
  4114. var eventList,
  4115. index = -1;
  4116. // convert to boolean
  4117. useCapture = useCapture? true: false;
  4118. if (eventType === 'wheel') {
  4119. eventType = wheelEvent;
  4120. }
  4121. // if it is an action event type
  4122. if (contains(eventTypes, eventType)) {
  4123. eventList = this._iEvents[eventType];
  4124. if (eventList && (index = indexOf(eventList, listener)) !== -1) {
  4125. this._iEvents[eventType].splice(index, 1);
  4126. }
  4127. }
  4128. // delegated event
  4129. else if (this.selector) {
  4130. var delegated = delegatedEvents[eventType],
  4131. matchFound = false;
  4132. if (!delegated) { return this; }
  4133. // count from last index of delegated to 0
  4134. for (index = delegated.selectors.length - 1; index >= 0; index--) {
  4135. // look for matching selector and context Node
  4136. if (delegated.selectors[index] === this.selector
  4137. && delegated.contexts[index] === this._context) {
  4138. var listeners = delegated.listeners[index];
  4139. // each item of the listeners array is an array: [function, useCaptureFlag]
  4140. for (i = listeners.length - 1; i >= 0; i--) {
  4141. var fn = listeners[i][0],
  4142. useCap = listeners[i][1];
  4143. // check if the listener functions and useCapture flags match
  4144. if (fn === listener && useCap === useCapture) {
  4145. // remove the listener from the array of listeners
  4146. listeners.splice(i, 1);
  4147. // if all listeners for this interactable have been removed
  4148. // remove the interactable from the delegated arrays
  4149. if (!listeners.length) {
  4150. delegated.selectors.splice(index, 1);
  4151. delegated.contexts .splice(index, 1);
  4152. delegated.listeners.splice(index, 1);
  4153. // remove delegate function from context
  4154. events.remove(this._context, eventType, delegateListener);
  4155. events.remove(this._context, eventType, delegateUseCapture, true);
  4156. // remove the arrays if they are empty
  4157. if (!delegated.selectors.length) {
  4158. delegatedEvents[eventType] = null;
  4159. }
  4160. }
  4161. // only remove one listener
  4162. matchFound = true;
  4163. break;
  4164. }
  4165. }
  4166. if (matchFound) { break; }
  4167. }
  4168. }
  4169. }
  4170. // remove listener from this Interatable's element
  4171. else {
  4172. events.remove(this._element, eventType, listener, useCapture);
  4173. }
  4174. return this;
  4175. },
  4176. /*\
  4177. * Interactable.set
  4178. [ method ]
  4179. *
  4180. * Reset the options of this Interactable
  4181. - options (object) The new settings to apply
  4182. = (object) This Interactablw
  4183. \*/
  4184. set: function (options) {
  4185. if (!isObject(options)) {
  4186. options = {};
  4187. }
  4188. this.options = extend({}, defaultOptions.base);
  4189. var i,
  4190. actions = ['drag', 'drop', 'resize', 'gesture'],
  4191. methods = ['draggable', 'dropzone', 'resizable', 'gesturable'],
  4192. perActions = extend(extend({}, defaultOptions.perAction), options[action] || {});
  4193. for (i = 0; i < actions.length; i++) {
  4194. var action = actions[i];
  4195. this.options[action] = extend({}, defaultOptions[action]);
  4196. this.setPerAction(action, perActions);
  4197. this[methods[i]](options[action]);
  4198. }
  4199. var settings = [
  4200. 'accept', 'actionChecker', 'allowFrom', 'deltaSource',
  4201. 'dropChecker', 'ignoreFrom', 'origin', 'preventDefault',
  4202. 'rectChecker'
  4203. ];
  4204. for (i = 0, len = settings.length; i < len; i++) {
  4205. var setting = settings[i];
  4206. this.options[setting] = defaultOptions.base[setting];
  4207. if (setting in options) {
  4208. this[setting](options[setting]);
  4209. }
  4210. }
  4211. return this;
  4212. },
  4213. /*\
  4214. * Interactable.unset
  4215. [ method ]
  4216. *
  4217. * Remove this interactable from the list of interactables and remove
  4218. * it's drag, drop, resize and gesture capabilities
  4219. *
  4220. = (object) @interact
  4221. \*/
  4222. unset: function () {
  4223. events.remove(this, 'all');
  4224. if (!isString(this.selector)) {
  4225. events.remove(this, 'all');
  4226. if (this.options.styleCursor) {
  4227. this._element.style.cursor = '';
  4228. }
  4229. }
  4230. else {
  4231. // remove delegated events
  4232. for (var type in delegatedEvents) {
  4233. var delegated = delegatedEvents[type];
  4234. for (var i = 0; i < delegated.selectors.length; i++) {
  4235. if (delegated.selectors[i] === this.selector
  4236. && delegated.contexts[i] === this._context) {
  4237. delegated.selectors.splice(i, 1);
  4238. delegated.contexts .splice(i, 1);
  4239. delegated.listeners.splice(i, 1);
  4240. // remove the arrays if they are empty
  4241. if (!delegated.selectors.length) {
  4242. delegatedEvents[type] = null;
  4243. }
  4244. }
  4245. events.remove(this._context, type, delegateListener);
  4246. events.remove(this._context, type, delegateUseCapture, true);
  4247. break;
  4248. }
  4249. }
  4250. }
  4251. this.dropzone(false);
  4252. interactables.splice(indexOf(interactables, this), 1);
  4253. return interact;
  4254. }
  4255. };
  4256. function warnOnce (method, message) {
  4257. var warned = false;
  4258. return function () {
  4259. if (!warned) {
  4260. window.console.warn(message);
  4261. warned = true;
  4262. }
  4263. return method.apply(this, arguments);
  4264. };
  4265. }
  4266. Interactable.prototype.snap = warnOnce(Interactable.prototype.snap,
  4267. 'Interactable#snap is deprecated. See the new documentation for snapping at http://interactjs.io/docs/snapping');
  4268. Interactable.prototype.restrict = warnOnce(Interactable.prototype.restrict,
  4269. 'Interactable#restrict is deprecated. See the new documentation for resticting at http://interactjs.io/docs/restriction');
  4270. Interactable.prototype.inertia = warnOnce(Interactable.prototype.inertia,
  4271. 'Interactable#inertia is deprecated. See the new documentation for inertia at http://interactjs.io/docs/inertia');
  4272. Interactable.prototype.autoScroll = warnOnce(Interactable.prototype.autoScroll,
  4273. 'Interactable#autoScroll is deprecated. See the new documentation for autoScroll at http://interactjs.io/docs/#autoscroll');
  4274. /*\
  4275. * interact.isSet
  4276. [ method ]
  4277. *
  4278. * Check if an element has been set
  4279. - element (Element) The Element being searched for
  4280. = (boolean) Indicates if the element or CSS selector was previously passed to interact
  4281. \*/
  4282. interact.isSet = function(element, options) {
  4283. return interactables.indexOfElement(element, options && options.context) !== -1;
  4284. };
  4285. /*\
  4286. * interact.on
  4287. [ method ]
  4288. *
  4289. * Adds a global listener for an InteractEvent or adds a DOM event to
  4290. * `document`
  4291. *
  4292. - type (string | array | object) The types of events to listen for
  4293. - listener (function) The function to be called on the given event(s)
  4294. - useCapture (boolean) #optional useCapture flag for addEventListener
  4295. = (object) interact
  4296. \*/
  4297. interact.on = function (type, listener, useCapture) {
  4298. if (isString(type) && type.search(' ') !== -1) {
  4299. type = type.trim().split(/ +/);
  4300. }
  4301. if (isArray(type)) {
  4302. for (var i = 0; i < type.length; i++) {
  4303. interact.on(type[i], listener, useCapture);
  4304. }
  4305. return interact;
  4306. }
  4307. if (isObject(type)) {
  4308. for (var prop in type) {
  4309. interact.on(prop, type[prop], listener);
  4310. }
  4311. return interact;
  4312. }
  4313. // if it is an InteractEvent type, add listener to globalEvents
  4314. if (contains(eventTypes, type)) {
  4315. // if this type of event was never bound
  4316. if (!globalEvents[type]) {
  4317. globalEvents[type] = [listener];
  4318. }
  4319. else {
  4320. globalEvents[type].push(listener);
  4321. }
  4322. }
  4323. // If non InteractEvent type, addEventListener to document
  4324. else {
  4325. events.add(document, type, listener, useCapture);
  4326. }
  4327. return interact;
  4328. };
  4329. /*\
  4330. * interact.off
  4331. [ method ]
  4332. *
  4333. * Removes a global InteractEvent listener or DOM event from `document`
  4334. *
  4335. - type (string | array | object) The types of events that were listened for
  4336. - listener (function) The listener function to be removed
  4337. - useCapture (boolean) #optional useCapture flag for removeEventListener
  4338. = (object) interact
  4339. \*/
  4340. interact.off = function (type, listener, useCapture) {
  4341. if (isString(type) && type.search(' ') !== -1) {
  4342. type = type.trim().split(/ +/);
  4343. }
  4344. if (isArray(type)) {
  4345. for (var i = 0; i < type.length; i++) {
  4346. interact.off(type[i], listener, useCapture);
  4347. }
  4348. return interact;
  4349. }
  4350. if (isObject(type)) {
  4351. for (var prop in type) {
  4352. interact.off(prop, type[prop], listener);
  4353. }
  4354. return interact;
  4355. }
  4356. if (!contains(eventTypes, type)) {
  4357. events.remove(document, type, listener, useCapture);
  4358. }
  4359. else {
  4360. var index;
  4361. if (type in globalEvents
  4362. && (index = indexOf(globalEvents[type], listener)) !== -1) {
  4363. globalEvents[type].splice(index, 1);
  4364. }
  4365. }
  4366. return interact;
  4367. };
  4368. /*\
  4369. * interact.enableDragging
  4370. [ method ]
  4371. *
  4372. * Deprecated.
  4373. *
  4374. * Returns or sets whether dragging is enabled for any Interactables
  4375. *
  4376. - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
  4377. = (boolean | object) The current setting or interact
  4378. \*/
  4379. interact.enableDragging = warnOnce(function (newValue) {
  4380. if (newValue !== null && newValue !== undefined) {
  4381. actionIsEnabled.drag = newValue;
  4382. return interact;
  4383. }
  4384. return actionIsEnabled.drag;
  4385. }, 'interact.enableDragging is deprecated and will soon be removed.');
  4386. /*\
  4387. * interact.enableResizing
  4388. [ method ]
  4389. *
  4390. * Deprecated.
  4391. *
  4392. * Returns or sets whether resizing is enabled for any Interactables
  4393. *
  4394. - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
  4395. = (boolean | object) The current setting or interact
  4396. \*/
  4397. interact.enableResizing = warnOnce(function (newValue) {
  4398. if (newValue !== null && newValue !== undefined) {
  4399. actionIsEnabled.resize = newValue;
  4400. return interact;
  4401. }
  4402. return actionIsEnabled.resize;
  4403. }, 'interact.enableResizing is deprecated and will soon be removed.');
  4404. /*\
  4405. * interact.enableGesturing
  4406. [ method ]
  4407. *
  4408. * Deprecated.
  4409. *
  4410. * Returns or sets whether gesturing is enabled for any Interactables
  4411. *
  4412. - newValue (boolean) #optional `true` to allow the action; `false` to disable action for all Interactables
  4413. = (boolean | object) The current setting or interact
  4414. \*/
  4415. interact.enableGesturing = warnOnce(function (newValue) {
  4416. if (newValue !== null && newValue !== undefined) {
  4417. actionIsEnabled.gesture = newValue;
  4418. return interact;
  4419. }
  4420. return actionIsEnabled.gesture;
  4421. }, 'interact.enableGesturing is deprecated and will soon be removed.');
  4422. interact.eventTypes = eventTypes;
  4423. /*\
  4424. * interact.debug
  4425. [ method ]
  4426. *
  4427. * Returns debugging data
  4428. = (object) An object with properties that outline the current state and expose internal functions and variables
  4429. \*/
  4430. interact.debug = function () {
  4431. var interaction = interactions[0] || new Interaction();
  4432. return {
  4433. interactions : interactions,
  4434. target : interaction.target,
  4435. dragging : interaction.dragging,
  4436. resizing : interaction.resizing,
  4437. gesturing : interaction.gesturing,
  4438. prepared : interaction.prepared,
  4439. matches : interaction.matches,
  4440. matchElements : interaction.matchElements,
  4441. prevCoords : interaction.prevCoords,
  4442. startCoords : interaction.startCoords,
  4443. pointerIds : interaction.pointerIds,
  4444. pointers : interaction.pointers,
  4445. addPointer : listeners.addPointer,
  4446. removePointer : listeners.removePointer,
  4447. recordPointer : listeners.recordPointer,
  4448. snap : interaction.snapStatus,
  4449. restrict : interaction.restrictStatus,
  4450. inertia : interaction.inertiaStatus,
  4451. downTime : interaction.downTimes[0],
  4452. downEvent : interaction.downEvent,
  4453. downPointer : interaction.downPointer,
  4454. prevEvent : interaction.prevEvent,
  4455. Interactable : Interactable,
  4456. interactables : interactables,
  4457. pointerIsDown : interaction.pointerIsDown,
  4458. defaultOptions : defaultOptions,
  4459. defaultActionChecker : defaultActionChecker,
  4460. actionCursors : actionCursors,
  4461. dragMove : listeners.dragMove,
  4462. resizeMove : listeners.resizeMove,
  4463. gestureMove : listeners.gestureMove,
  4464. pointerUp : listeners.pointerUp,
  4465. pointerDown : listeners.pointerDown,
  4466. pointerMove : listeners.pointerMove,
  4467. pointerHover : listeners.pointerHover,
  4468. events : events,
  4469. globalEvents : globalEvents,
  4470. delegatedEvents : delegatedEvents
  4471. };
  4472. };
  4473. // expose the functions used to calculate multi-touch properties
  4474. interact.getTouchAverage = touchAverage;
  4475. interact.getTouchBBox = touchBBox;
  4476. interact.getTouchDistance = touchDistance;
  4477. interact.getTouchAngle = touchAngle;
  4478. interact.getElementRect = getElementRect;
  4479. interact.matchesSelector = matchesSelector;
  4480. interact.closest = closest;
  4481. /*\
  4482. * interact.margin
  4483. [ method ]
  4484. *
  4485. * Returns or sets the margin for autocheck resizing used in
  4486. * @Interactable.getAction. That is the distance from the bottom and right
  4487. * edges of an element clicking in which will start resizing
  4488. *
  4489. - newValue (number) #optional
  4490. = (number | interact) The current margin value or interact
  4491. \*/
  4492. interact.margin = function (newvalue) {
  4493. if (isNumber(newvalue)) {
  4494. margin = newvalue;
  4495. return interact;
  4496. }
  4497. return margin;
  4498. };
  4499. /*\
  4500. * interact.supportsTouch
  4501. [ method ]
  4502. *
  4503. = (boolean) Whether or not the browser supports touch input
  4504. \*/
  4505. interact.supportsTouch = function () {
  4506. return supportsTouch;
  4507. };
  4508. /*\
  4509. * interact.supportsPointerEvent
  4510. [ method ]
  4511. *
  4512. = (boolean) Whether or not the browser supports PointerEvents
  4513. \*/
  4514. interact.supportsPointerEvent = function () {
  4515. return supportsPointerEvent;
  4516. };
  4517. /*\
  4518. * interact.stop
  4519. [ method ]
  4520. *
  4521. * Cancels all interactions (end events are not fired)
  4522. *
  4523. - event (Event) An event on which to call preventDefault()
  4524. = (object) interact
  4525. \*/
  4526. interact.stop = function (event) {
  4527. for (var i = interactions.length - 1; i > 0; i--) {
  4528. interactions[i].stop(event);
  4529. }
  4530. return interact;
  4531. };
  4532. /*\
  4533. * interact.dynamicDrop
  4534. [ method ]
  4535. *
  4536. * Returns or sets whether the dimensions of dropzone elements are
  4537. * calculated on every dragmove or only on dragstart for the default
  4538. * dropChecker
  4539. *
  4540. - newValue (boolean) #optional True to check on each move. False to check only before start
  4541. = (boolean | interact) The current setting or interact
  4542. \*/
  4543. interact.dynamicDrop = function (newValue) {
  4544. if (isBool(newValue)) {
  4545. //if (dragging && dynamicDrop !== newValue && !newValue) {
  4546. //calcRects(dropzones);
  4547. //}
  4548. dynamicDrop = newValue;
  4549. return interact;
  4550. }
  4551. return dynamicDrop;
  4552. };
  4553. /*\
  4554. * interact.pointerMoveTolerance
  4555. [ method ]
  4556. * Returns or sets the distance the pointer must be moved before an action
  4557. * sequence occurs. This also affects tolerance for tap events.
  4558. *
  4559. - newValue (number) #optional The movement from the start position must be greater than this value
  4560. = (number | Interactable) The current setting or interact
  4561. \*/
  4562. interact.pointerMoveTolerance = function (newValue) {
  4563. if (isNumber(newValue)) {
  4564. pointerMoveTolerance = newValue;
  4565. return this;
  4566. }
  4567. return pointerMoveTolerance;
  4568. };
  4569. /*\
  4570. * interact.maxInteractions
  4571. [ method ]
  4572. **
  4573. * Returns or sets the maximum number of concurrent interactions allowed.
  4574. * By default only 1 interaction is allowed at a time (for backwards
  4575. * compatibility). To allow multiple interactions on the same Interactables
  4576. * and elements, you need to enable it in the draggable, resizable and
  4577. * gesturable `'max'` and `'maxPerElement'` options.
  4578. **
  4579. - newValue (number) #optional Any number. newValue <= 0 means no interactions.
  4580. \*/
  4581. interact.maxInteractions = function (newValue) {
  4582. if (isNumber(newValue)) {
  4583. maxInteractions = newValue;
  4584. return this;
  4585. }
  4586. return maxInteractions;
  4587. };
  4588. interact.createSnapGrid = function (grid) {
  4589. return function (x, y) {
  4590. var offsetX = 0,
  4591. offsetY = 0;
  4592. if (isObject(grid.offset)) {
  4593. offsetX = grid.offset.x;
  4594. offsetY = grid.offset.y;
  4595. }
  4596. var gridx = Math.round((x - offsetX) / grid.x),
  4597. gridy = Math.round((y - offsetY) / grid.y),
  4598. newX = gridx * grid.x + offsetX,
  4599. newY = gridy * grid.y + offsetY;
  4600. return {
  4601. x: newX,
  4602. y: newY,
  4603. range: grid.range
  4604. };
  4605. };
  4606. };
  4607. function endAllInteractions (event) {
  4608. for (var i = 0; i < interactions.length; i++) {
  4609. interactions[i].pointerEnd(event, event);
  4610. }
  4611. }
  4612. function listenToDocument (doc) {
  4613. if (contains(documents, doc)) { return; }
  4614. var win = doc.defaultView || doc.parentWindow;
  4615. // add delegate event listener
  4616. for (var eventType in delegatedEvents) {
  4617. events.add(doc, eventType, delegateListener);
  4618. events.add(doc, eventType, delegateUseCapture, true);
  4619. }
  4620. if (PointerEvent) {
  4621. if (PointerEvent === win.MSPointerEvent) {
  4622. pEventTypes = {
  4623. up: 'MSPointerUp', down: 'MSPointerDown', over: 'mouseover',
  4624. out: 'mouseout', move: 'MSPointerMove', cancel: 'MSPointerCancel' };
  4625. }
  4626. else {
  4627. pEventTypes = {
  4628. up: 'pointerup', down: 'pointerdown', over: 'pointerover',
  4629. out: 'pointerout', move: 'pointermove', cancel: 'pointercancel' };
  4630. }
  4631. events.add(doc, pEventTypes.down , listeners.selectorDown );
  4632. events.add(doc, pEventTypes.move , listeners.pointerMove );
  4633. events.add(doc, pEventTypes.over , listeners.pointerOver );
  4634. events.add(doc, pEventTypes.out , listeners.pointerOut );
  4635. events.add(doc, pEventTypes.up , listeners.pointerUp );
  4636. events.add(doc, pEventTypes.cancel, listeners.pointerCancel);
  4637. // autoscroll
  4638. events.add(doc, pEventTypes.move, autoScroll.edgeMove);
  4639. }
  4640. else {
  4641. events.add(doc, 'mousedown', listeners.selectorDown);
  4642. events.add(doc, 'mousemove', listeners.pointerMove );
  4643. events.add(doc, 'mouseup' , listeners.pointerUp );
  4644. events.add(doc, 'mouseover', listeners.pointerOver );
  4645. events.add(doc, 'mouseout' , listeners.pointerOut );
  4646. events.add(doc, 'touchstart' , listeners.selectorDown );
  4647. events.add(doc, 'touchmove' , listeners.pointerMove );
  4648. events.add(doc, 'touchend' , listeners.pointerUp );
  4649. events.add(doc, 'touchcancel', listeners.pointerCancel);
  4650. // autoscroll
  4651. events.add(doc, 'mousemove', autoScroll.edgeMove);
  4652. events.add(doc, 'touchmove', autoScroll.edgeMove);
  4653. }
  4654. events.add(win, 'blur', endAllInteractions);
  4655. try {
  4656. if (win.frameElement) {
  4657. var parentDoc = win.frameElement.ownerDocument,
  4658. parentWindow = parentDoc.defaultView;
  4659. events.add(parentDoc , 'mouseup' , listeners.pointerEnd);
  4660. events.add(parentDoc , 'touchend' , listeners.pointerEnd);
  4661. events.add(parentDoc , 'touchcancel' , listeners.pointerEnd);
  4662. events.add(parentDoc , 'pointerup' , listeners.pointerEnd);
  4663. events.add(parentDoc , 'MSPointerUp' , listeners.pointerEnd);
  4664. events.add(parentWindow, 'blur' , endAllInteractions );
  4665. }
  4666. }
  4667. catch (error) {
  4668. interact.windowParentError = error;
  4669. }
  4670. if (events.useAttachEvent) {
  4671. // For IE's lack of Event#preventDefault
  4672. events.add(doc, 'selectstart', function (event) {
  4673. var interaction = interactions[0];
  4674. if (interaction.currentAction()) {
  4675. interaction.checkAndPreventDefault(event);
  4676. }
  4677. });
  4678. // For IE's bad dblclick event sequence
  4679. events.add(doc, 'dblclick', doOnInteractions('ie8Dblclick'));
  4680. }
  4681. documents.push(doc);
  4682. }
  4683. listenToDocument(document);
  4684. function indexOf (array, target) {
  4685. for (var i = 0, len = array.length; i < len; i++) {
  4686. if (array[i] === target) {
  4687. return i;
  4688. }
  4689. }
  4690. return -1;
  4691. }
  4692. function contains (array, target) {
  4693. return indexOf(array, target) !== -1;
  4694. }
  4695. function matchesSelector (element, selector, nodeList) {
  4696. if (ie8MatchesSelector) {
  4697. return ie8MatchesSelector(element, selector, nodeList);
  4698. }
  4699. // remove /deep/ from selectors if shadowDOM polyfill is used
  4700. if (window !== realWindow) {
  4701. selector = selector.replace(/\/deep\//g, ' ');
  4702. }
  4703. return element[prefixedMatchesSelector](selector);
  4704. }
  4705. function matchesUpTo (element, selector, limit) {
  4706. while (isElement(element)) {
  4707. if (matchesSelector(element, selector)) {
  4708. return true;
  4709. }
  4710. element = parentElement(element);
  4711. if (element === limit) {
  4712. return matchesSelector(element, selector);
  4713. }
  4714. }
  4715. return false;
  4716. }
  4717. // For IE8's lack of an Element#matchesSelector
  4718. // taken from http://tanalin.com/en/blog/2012/12/matches-selector-ie8/ and modified
  4719. if (!(prefixedMatchesSelector in Element.prototype) || !isFunction(Element.prototype[prefixedMatchesSelector])) {
  4720. ie8MatchesSelector = function (element, selector, elems) {
  4721. elems = elems || element.parentNode.querySelectorAll(selector);
  4722. for (var i = 0, len = elems.length; i < len; i++) {
  4723. if (elems[i] === element) {
  4724. return true;
  4725. }
  4726. }
  4727. return false;
  4728. };
  4729. }
  4730. // requestAnimationFrame polyfill
  4731. (function() {
  4732. var lastTime = 0,
  4733. vendors = ['ms', 'moz', 'webkit', 'o'];
  4734. for(var x = 0; x < vendors.length && !realWindow.requestAnimationFrame; ++x) {
  4735. reqFrame = realWindow[vendors[x]+'RequestAnimationFrame'];
  4736. cancelFrame = realWindow[vendors[x]+'CancelAnimationFrame'] || realWindow[vendors[x]+'CancelRequestAnimationFrame'];
  4737. }
  4738. if (!reqFrame) {
  4739. reqFrame = function(callback) {
  4740. var currTime = new Date().getTime(),
  4741. timeToCall = Math.max(0, 16 - (currTime - lastTime)),
  4742. id = setTimeout(function() { callback(currTime + timeToCall); },
  4743. timeToCall);
  4744. lastTime = currTime + timeToCall;
  4745. return id;
  4746. };
  4747. }
  4748. if (!cancelFrame) {
  4749. cancelFrame = function(id) {
  4750. clearTimeout(id);
  4751. };
  4752. }
  4753. }());
  4754. /* global exports: true, module, define */
  4755. // http://documentcloud.github.io/underscore/docs/underscore.html#section-11
  4756. if (typeof exports !== 'undefined') {
  4757. if (typeof module !== 'undefined' && module.exports) {
  4758. exports = module.exports = interact;
  4759. }
  4760. exports.interact = interact;
  4761. }
  4762. // AMD
  4763. else if (typeof define === 'function' && define.amd) {
  4764. define('interact', function() {
  4765. return interact;
  4766. });
  4767. }
  4768. else {
  4769. realWindow.interact = interact;
  4770. }
  4771. } (window));