UNPKG

@shopify/draggable

Version:

The JavaScript Drag & Drop library your grandparents warned you about.

751 lines (608 loc) 19.7 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("Collidable", [], factory); else if(typeof exports === 'object') exports["Collidable"] = factory(); else root["Collidable"] = factory(); })(window, function() { return /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ /******/ // Check if module is in cache /******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); /******/ /******/ // Flag the module as loaded /******/ module.l = true; /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; /******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; /******/ /******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { /******/ if(!__webpack_require__.o(exports, name)) { /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); /******/ } /******/ }; /******/ /******/ // define __esModule on exports /******/ __webpack_require__.r = function(exports) { /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); /******/ } /******/ Object.defineProperty(exports, '__esModule', { value: true }); /******/ }; /******/ /******/ // create a fake namespace object /******/ // mode & 1: value is a module id, require it /******/ // mode & 2: merge all properties of value into the ns /******/ // mode & 4: return value when already ns object /******/ // mode & 8|1: behave like require /******/ __webpack_require__.t = function(value, mode) { /******/ if(mode & 1) value = __webpack_require__(value); /******/ if(mode & 8) return value; /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; /******/ var ns = Object.create(null); /******/ __webpack_require__.r(ns); /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); /******/ return ns; /******/ }; /******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? /******/ function getDefault() { return module['default']; } : /******/ function getModuleExports() { return module; }; /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; /******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; /******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; /******/ /******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 12); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _CollidableEvent = __webpack_require__(11); Object.keys(_CollidableEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _CollidableEvent[key]; } }); }); /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = requestNextAnimationFrame; function requestNextAnimationFrame(callback) { return requestAnimationFrame(() => { requestAnimationFrame(callback); }); } /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _requestNextAnimationFrame = __webpack_require__(1); var _requestNextAnimationFrame2 = _interopRequireDefault(_requestNextAnimationFrame); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _requestNextAnimationFrame2.default; /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = closest; const matchFunction = Element.prototype.matches || Element.prototype.webkitMatchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector; /** * Get the closest parent element of a given element that matches the given * selector string or matching function * * @param {Element} element The child element to find a parent of * @param {String|Function} selector The string or function to use to match * the parent element * @return {Element|null} */ function closest(element, value) { if (!element) { return null; } const selector = value; const callback = value; const nodeList = value; const singleElement = value; const isSelector = Boolean(typeof value === 'string'); const isFunction = Boolean(typeof value === 'function'); const isNodeList = Boolean(value instanceof NodeList || value instanceof Array); const isElement = Boolean(value instanceof HTMLElement); function conditionFn(currentElement) { if (!currentElement) { return currentElement; } else if (isSelector) { return matchFunction.call(currentElement, selector); } else if (isNodeList) { return [...nodeList].includes(currentElement); } else if (isElement) { return singleElement === currentElement; } else if (isFunction) { return callback(currentElement); } else { return null; } } let current = element; do { current = current.correspondingUseElement || current.correspondingElement || current; if (conditionFn(current)) { return current; } current = current.parentNode; } while (current && current !== document.body && current !== document); return null; } /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _closest = __webpack_require__(3); var _closest2 = _interopRequireDefault(_closest); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _closest2.default; /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _closest = __webpack_require__(4); Object.defineProperty(exports, 'closest', { enumerable: true, get: function () { return _interopRequireDefault(_closest).default; } }); var _requestNextAnimationFrame = __webpack_require__(2); Object.defineProperty(exports, 'requestNextAnimationFrame', { enumerable: true, get: function () { return _interopRequireDefault(_requestNextAnimationFrame).default; } }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /***/ }), /* 6 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * All draggable plugins inherit from this class. * @abstract * @class AbstractPlugin * @module AbstractPlugin */ class AbstractPlugin { /** * AbstractPlugin constructor. * @constructs AbstractPlugin * @param {Draggable} draggable - Draggable instance */ constructor(draggable) { /** * Draggable instance * @property draggable * @type {Draggable} */ this.draggable = draggable; } /** * Override to add listeners * @abstract */ attach() { throw new Error('Not Implemented'); } /** * Override to remove listeners * @abstract */ detach() { throw new Error('Not Implemented'); } } exports.default = AbstractPlugin; /***/ }), /* 7 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _AbstractPlugin = __webpack_require__(6); var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _AbstractPlugin2.default; /***/ }), /* 8 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _AbstractPlugin = __webpack_require__(7); var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin); var _utils = __webpack_require__(5); var _CollidableEvent = __webpack_require__(0); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const onDragMove = Symbol('onDragMove'); const onDragStop = Symbol('onDragStop'); const onRequestAnimationFrame = Symbol('onRequestAnimationFrame'); /** * Collidable plugin which detects colliding elements while dragging * @class Collidable * @module Collidable * @extends AbstractPlugin */ class Collidable extends _AbstractPlugin2.default { /** * Collidable constructor. * @constructs Collidable * @param {Draggable} draggable - Draggable instance */ constructor(draggable) { super(draggable); /** * Keeps track of currently colliding elements * @property {HTMLElement|null} currentlyCollidingElement * @type {HTMLElement|null} */ this.currentlyCollidingElement = null; /** * Keeps track of currently colliding elements * @property {HTMLElement|null} lastCollidingElement * @type {HTMLElement|null} */ this.lastCollidingElement = null; /** * Animation frame for finding colliding elements * @property {Number|null} currentAnimationFrame * @type {Number|null} */ this.currentAnimationFrame = null; this[onDragMove] = this[onDragMove].bind(this); this[onDragStop] = this[onDragStop].bind(this); this[onRequestAnimationFrame] = this[onRequestAnimationFrame].bind(this); } /** * Attaches plugins event listeners */ attach() { this.draggable.on('drag:move', this[onDragMove]).on('drag:stop', this[onDragStop]); } /** * Detaches plugins event listeners */ detach() { this.draggable.off('drag:move', this[onDragMove]).off('drag:stop', this[onDragStop]); } /** * Returns current collidables based on `collidables` option * @return {HTMLElement[]} */ getCollidables() { const collidables = this.draggable.options.collidables; if (typeof collidables === 'string') { return Array.prototype.slice.call(document.querySelectorAll(collidables)); } else if (collidables instanceof NodeList || collidables instanceof Array) { return Array.prototype.slice.call(collidables); } else if (collidables instanceof HTMLElement) { return [collidables]; } else if (typeof collidables === 'function') { return collidables(); } else { return []; } } /** * Drag move handler * @private * @param {DragMoveEvent} event - Drag move event */ [onDragMove](event) { const target = event.sensorEvent.target; this.currentAnimationFrame = requestAnimationFrame(this[onRequestAnimationFrame](target)); if (this.currentlyCollidingElement) { event.cancel(); } const collidableInEvent = new _CollidableEvent.CollidableInEvent({ dragEvent: event, collidingElement: this.currentlyCollidingElement }); const collidableOutEvent = new _CollidableEvent.CollidableOutEvent({ dragEvent: event, collidingElement: this.lastCollidingElement }); const enteringCollidable = Boolean(this.currentlyCollidingElement && this.lastCollidingElement !== this.currentlyCollidingElement); const leavingCollidable = Boolean(!this.currentlyCollidingElement && this.lastCollidingElement); if (enteringCollidable) { if (this.lastCollidingElement) { this.draggable.trigger(collidableOutEvent); } this.draggable.trigger(collidableInEvent); } else if (leavingCollidable) { this.draggable.trigger(collidableOutEvent); } this.lastCollidingElement = this.currentlyCollidingElement; } /** * Drag stop handler * @private * @param {DragStopEvent} event - Drag stop event */ [onDragStop](event) { const lastCollidingElement = this.currentlyCollidingElement || this.lastCollidingElement; const collidableOutEvent = new _CollidableEvent.CollidableOutEvent({ dragEvent: event, collidingElement: lastCollidingElement }); if (lastCollidingElement) { this.draggable.trigger(collidableOutEvent); } this.lastCollidingElement = null; this.currentlyCollidingElement = null; } /** * Animation frame function * @private * @param {HTMLElement} target - Current move target * @return {Function} */ [onRequestAnimationFrame](target) { return () => { const collidables = this.getCollidables(); this.currentlyCollidingElement = (0, _utils.closest)(target, element => collidables.includes(element)); }; } } exports.default = Collidable; /***/ }), /* 9 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; const canceled = Symbol('canceled'); /** * All events fired by draggable inherit this class. You can call `cancel()` to * cancel a specific event or you can check if an event has been canceled by * calling `canceled()`. * @abstract * @class AbstractEvent * @module AbstractEvent */ class AbstractEvent { /** * AbstractEvent constructor. * @constructs AbstractEvent * @param {object} data - Event data */ /** * Event type * @static * @abstract * @property type * @type {String} */ constructor(data) { this[canceled] = false; this.data = data; } /** * Read-only type * @abstract * @return {String} */ /** * Event cancelable * @static * @abstract * @property cancelable * @type {Boolean} */ get type() { return this.constructor.type; } /** * Read-only cancelable * @abstract * @return {Boolean} */ get cancelable() { return this.constructor.cancelable; } /** * Cancels the event instance * @abstract */ cancel() { this[canceled] = true; } /** * Check if event has been canceled * @abstract * @return {Boolean} */ canceled() { return Boolean(this[canceled]); } /** * Returns new event instance with existing event data. * This method allows for overriding of event data. * @param {Object} data * @return {AbstractEvent} */ clone(data) { return new this.constructor(_extends({}, this.data, data)); } } exports.default = AbstractEvent; AbstractEvent.type = 'event'; AbstractEvent.cancelable = false; /***/ }), /* 10 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _AbstractEvent = __webpack_require__(9); var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _AbstractEvent2.default; /***/ }), /* 11 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.CollidableOutEvent = exports.CollidableInEvent = exports.CollidableEvent = undefined; var _AbstractEvent = __webpack_require__(10); var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /** * Base collidable event * @class CollidableEvent * @module CollidableEvent * @extends AbstractEvent */ class CollidableEvent extends _AbstractEvent2.default { /** * Drag event that triggered this colliable event * @property dragEvent * @type {DragEvent} * @readonly */ get dragEvent() { return this.data.dragEvent; } } exports.CollidableEvent = CollidableEvent; /** * Collidable in event * @class CollidableInEvent * @module CollidableInEvent * @extends CollidableEvent */ CollidableEvent.type = 'collidable'; class CollidableInEvent extends CollidableEvent { /** * Element you are currently colliding with * @property collidingElement * @type {HTMLElement} * @readonly */ get collidingElement() { return this.data.collidingElement; } } exports.CollidableInEvent = CollidableInEvent; /** * Collidable out event * @class CollidableOutEvent * @module CollidableOutEvent * @extends CollidableEvent */ CollidableInEvent.type = 'collidable:in'; class CollidableOutEvent extends CollidableEvent { /** * Element you were previously colliding with * @property collidingElement * @type {HTMLElement} * @readonly */ get collidingElement() { return this.data.collidingElement; } } exports.CollidableOutEvent = CollidableOutEvent; CollidableOutEvent.type = 'collidable:out'; /***/ }), /* 12 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _CollidableEvent = __webpack_require__(0); Object.keys(_CollidableEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _CollidableEvent[key]; } }); }); var _Collidable = __webpack_require__(8); var _Collidable2 = _interopRequireDefault(_Collidable); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _Collidable2.default; /***/ }) /******/ ]); });