UNPKG

@shopify/draggable

Version:

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

1,836 lines (1,480 loc) 130 kB
(function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(); else if(typeof define === 'function' && define.amd) define("Swappable", [], factory); else if(typeof exports === 'object') exports["Swappable"] = factory(); else root["Swappable"] = 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 = 44); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _SensorEvent = __webpack_require__(19); Object.keys(_SensorEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _SensorEvent[key]; } }); }); /***/ }), /* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _Sensor = __webpack_require__(22); var _Sensor2 = _interopRequireDefault(_Sensor); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _Sensor2.default; /***/ }), /* 2 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _closest = __webpack_require__(26); Object.defineProperty(exports, 'closest', { enumerable: true, get: function () { return _interopRequireDefault(_closest).default; } }); var _requestNextAnimationFrame = __webpack_require__(24); Object.defineProperty(exports, 'requestNextAnimationFrame', { enumerable: true, get: function () { return _interopRequireDefault(_requestNextAnimationFrame).default; } }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /***/ }), /* 3 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _AbstractEvent = __webpack_require__(42); var _AbstractEvent2 = _interopRequireDefault(_AbstractEvent); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _AbstractEvent2.default; /***/ }), /* 4 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _AbstractPlugin = __webpack_require__(35); var _AbstractPlugin2 = _interopRequireDefault(_AbstractPlugin); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _AbstractPlugin2.default; /***/ }), /* 5 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _Sensor = __webpack_require__(1); Object.defineProperty(exports, 'Sensor', { enumerable: true, get: function () { return _interopRequireDefault(_Sensor).default; } }); var _MouseSensor = __webpack_require__(21); Object.defineProperty(exports, 'MouseSensor', { enumerable: true, get: function () { return _interopRequireDefault(_MouseSensor).default; } }); var _TouchSensor = __webpack_require__(18); Object.defineProperty(exports, 'TouchSensor', { enumerable: true, get: function () { return _interopRequireDefault(_TouchSensor).default; } }); var _DragSensor = __webpack_require__(16); Object.defineProperty(exports, 'DragSensor', { enumerable: true, get: function () { return _interopRequireDefault(_DragSensor).default; } }); var _ForceTouchSensor = __webpack_require__(14); Object.defineProperty(exports, 'ForceTouchSensor', { enumerable: true, get: function () { return _interopRequireDefault(_ForceTouchSensor).default; } }); var _SensorEvent = __webpack_require__(0); Object.keys(_SensorEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _SensorEvent[key]; } }); }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /***/ }), /* 6 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _Announcement = __webpack_require__(37); Object.defineProperty(exports, 'Announcement', { enumerable: true, get: function () { return _interopRequireDefault(_Announcement).default; } }); Object.defineProperty(exports, 'defaultAnnouncementOptions', { enumerable: true, get: function () { return _Announcement.defaultOptions; } }); var _Focusable = __webpack_require__(34); Object.defineProperty(exports, 'Focusable', { enumerable: true, get: function () { return _interopRequireDefault(_Focusable).default; } }); var _Mirror = __webpack_require__(32); Object.defineProperty(exports, 'Mirror', { enumerable: true, get: function () { return _interopRequireDefault(_Mirror).default; } }); Object.defineProperty(exports, 'defaultMirrorOptions', { enumerable: true, get: function () { return _Mirror.defaultOptions; } }); var _Scrollable = __webpack_require__(28); Object.defineProperty(exports, 'Scrollable', { enumerable: true, get: function () { return _interopRequireDefault(_Scrollable).default; } }); Object.defineProperty(exports, 'defaultScrollableOptions', { enumerable: true, get: function () { return _Scrollable.defaultOptions; } }); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } /***/ }), /* 7 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _DraggableEvent = __webpack_require__(38); Object.keys(_DraggableEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _DraggableEvent[key]; } }); }); /***/ }), /* 8 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _DragEvent = __webpack_require__(39); Object.keys(_DragEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _DragEvent[key]; } }); }); /***/ }), /* 9 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _SwappableEvent = __webpack_require__(43); Object.keys(_SwappableEvent).forEach(function (key) { if (key === "default" || key === "__esModule") return; Object.defineProperty(exports, key, { enumerable: true, get: function () { return _SwappableEvent[key]; } }); }); /***/ }), /* 10 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * The Emitter is a simple emitter class that provides you with `on()`, `off()` and `trigger()` methods * @class Emitter * @module Emitter */ class Emitter { constructor() { this.callbacks = {}; } /** * Registers callbacks by event name * @param {String} type * @param {...Function} callbacks */ on(type, ...callbacks) { if (!this.callbacks[type]) { this.callbacks[type] = []; } this.callbacks[type].push(...callbacks); return this; } /** * Unregisters callbacks by event name * @param {String} type * @param {Function} callback */ off(type, callback) { if (!this.callbacks[type]) { return null; } const copy = this.callbacks[type].slice(0); for (let i = 0; i < copy.length; i++) { if (callback === copy[i]) { this.callbacks[type].splice(i, 1); } } return this; } /** * Triggers event callbacks by event object * @param {AbstractEvent} event */ trigger(event) { if (!this.callbacks[event.type]) { return null; } const callbacks = [...this.callbacks[event.type]]; const caughtErrors = []; for (let i = callbacks.length - 1; i >= 0; i--) { const callback = callbacks[i]; try { callback(event); } catch (error) { caughtErrors.push(error); } } if (caughtErrors.length) { /* eslint-disable no-console */ console.error(`Draggable caught errors while triggering '${event.type}'`, caughtErrors); /* eslint-disable no-console */ } return this; } } exports.default = Emitter; /***/ }), /* 11 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _Emitter = __webpack_require__(10); var _Emitter2 = _interopRequireDefault(_Emitter); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _Emitter2.default; /***/ }), /* 12 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultOptions = undefined; 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; }; var _utils = __webpack_require__(2); var _Plugins = __webpack_require__(6); var _Emitter = __webpack_require__(11); var _Emitter2 = _interopRequireDefault(_Emitter); var _Sensors = __webpack_require__(5); var _DraggableEvent = __webpack_require__(7); var _DragEvent = __webpack_require__(8); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const onDragStart = Symbol('onDragStart'); const onDragMove = Symbol('onDragMove'); const onDragStop = Symbol('onDragStop'); const onDragPressure = Symbol('onDragPressure'); /** * @const {Object} defaultAnnouncements * @const {Function} defaultAnnouncements['drag:start'] * @const {Function} defaultAnnouncements['drag:stop'] */ const defaultAnnouncements = { 'drag:start': event => `Picked up ${event.source.textContent.trim() || event.source.id || 'draggable element'}`, 'drag:stop': event => `Released ${event.source.textContent.trim() || event.source.id || 'draggable element'}` }; const defaultClasses = { 'container:dragging': 'draggable-container--is-dragging', 'source:dragging': 'draggable-source--is-dragging', 'source:placed': 'draggable-source--placed', 'container:placed': 'draggable-container--placed', 'body:dragging': 'draggable--is-dragging', 'draggable:over': 'draggable--over', 'container:over': 'draggable-container--over', 'source:original': 'draggable--original', mirror: 'draggable-mirror' }; const defaultOptions = exports.defaultOptions = { draggable: '.draggable-source', handle: null, delay: 100, placedTimeout: 800, plugins: [], sensors: [] }; /** * This is the core draggable library that does the heavy lifting * @class Draggable * @module Draggable */ class Draggable { /** * Draggable constructor. * @constructs Draggable * @param {HTMLElement[]|NodeList|HTMLElement} containers - Draggable containers * @param {Object} options - Options for draggable */ constructor(containers = [document.body], options = {}) { /** * Draggable containers * @property containers * @type {HTMLElement[]} */ if (containers instanceof NodeList || containers instanceof Array) { this.containers = [...containers]; } else if (containers instanceof HTMLElement) { this.containers = [containers]; } else { throw new Error('Draggable containers are expected to be of type `NodeList`, `HTMLElement[]` or `HTMLElement`'); } this.options = _extends({}, defaultOptions, options, { classes: _extends({}, defaultClasses, options.classes || {}), announcements: _extends({}, defaultAnnouncements, options.announcements || {}) }); /** * Draggables event emitter * @property emitter * @type {Emitter} */ this.emitter = new _Emitter2.default(); /** * Current drag state * @property dragging * @type {Boolean} */ this.dragging = false; /** * Active plugins * @property plugins * @type {Plugin[]} */ this.plugins = []; /** * Active sensors * @property sensors * @type {Sensor[]} */ this.sensors = []; this[onDragStart] = this[onDragStart].bind(this); this[onDragMove] = this[onDragMove].bind(this); this[onDragStop] = this[onDragStop].bind(this); this[onDragPressure] = this[onDragPressure].bind(this); document.addEventListener('drag:start', this[onDragStart], true); document.addEventListener('drag:move', this[onDragMove], true); document.addEventListener('drag:stop', this[onDragStop], true); document.addEventListener('drag:pressure', this[onDragPressure], true); const defaultPlugins = Object.values(Draggable.Plugins).map(Plugin => Plugin); const defaultSensors = [_Sensors.MouseSensor, _Sensors.TouchSensor]; this.addPlugin(...[...defaultPlugins, ...this.options.plugins]); this.addSensor(...[...defaultSensors, ...this.options.sensors]); const draggableInitializedEvent = new _DraggableEvent.DraggableInitializedEvent({ draggable: this }); this.on('mirror:created', ({ mirror }) => this.mirror = mirror); this.on('mirror:destroy', () => this.mirror = null); this.trigger(draggableInitializedEvent); } /** * Destroys Draggable instance. This removes all internal event listeners and * deactivates sensors and plugins */ /** * Default plugins draggable uses * @static * @property {Object} Plugins * @property {Announcement} Plugins.Announcement * @property {Focusable} Plugins.Focusable * @property {Mirror} Plugins.Mirror * @property {Scrollable} Plugins.Scrollable * @type {Object} */ destroy() { document.removeEventListener('drag:start', this[onDragStart], true); document.removeEventListener('drag:move', this[onDragMove], true); document.removeEventListener('drag:stop', this[onDragStop], true); document.removeEventListener('drag:pressure', this[onDragPressure], true); const draggableDestroyEvent = new _DraggableEvent.DraggableDestroyEvent({ draggable: this }); this.trigger(draggableDestroyEvent); this.removePlugin(...this.plugins.map(plugin => plugin.constructor)); this.removeSensor(...this.sensors.map(sensor => sensor.constructor)); } /** * Adds plugin to this draggable instance. This will end up calling the attach method of the plugin * @param {...typeof Plugin} plugins - Plugins that you want attached to draggable * @return {Draggable} * @example draggable.addPlugin(CustomA11yPlugin, CustomMirrorPlugin) */ addPlugin(...plugins) { const activePlugins = plugins.map(Plugin => new Plugin(this)); activePlugins.forEach(plugin => plugin.attach()); this.plugins = [...this.plugins, ...activePlugins]; return this; } /** * Removes plugins that are already attached to this draggable instance. This will end up calling * the detach method of the plugin * @param {...typeof Plugin} plugins - Plugins that you want detached from draggable * @return {Draggable} * @example draggable.removePlugin(MirrorPlugin, CustomMirrorPlugin) */ removePlugin(...plugins) { const removedPlugins = this.plugins.filter(plugin => plugins.includes(plugin.constructor)); removedPlugins.forEach(plugin => plugin.detach()); this.plugins = this.plugins.filter(plugin => !plugins.includes(plugin.constructor)); return this; } /** * Adds sensors to this draggable instance. This will end up calling the attach method of the sensor * @param {...typeof Sensor} sensors - Sensors that you want attached to draggable * @return {Draggable} * @example draggable.addSensor(ForceTouchSensor, CustomSensor) */ addSensor(...sensors) { const activeSensors = sensors.map(Sensor => new Sensor(this.containers, this.options)); activeSensors.forEach(sensor => sensor.attach()); this.sensors = [...this.sensors, ...activeSensors]; return this; } /** * Removes sensors that are already attached to this draggable instance. This will end up calling * the detach method of the sensor * @param {...typeof Sensor} sensors - Sensors that you want attached to draggable * @return {Draggable} * @example draggable.removeSensor(TouchSensor, DragSensor) */ removeSensor(...sensors) { const removedSensors = this.sensors.filter(sensor => sensors.includes(sensor.constructor)); removedSensors.forEach(sensor => sensor.detach()); this.sensors = this.sensors.filter(sensor => !sensors.includes(sensor.constructor)); return this; } /** * Adds container to this draggable instance * @param {...HTMLElement} containers - Containers you want to add to draggable * @return {Draggable} * @example draggable.addContainer(document.body) */ addContainer(...containers) { this.containers = [...this.containers, ...containers]; this.sensors.forEach(sensor => sensor.addContainer(...containers)); return this; } /** * Removes container from this draggable instance * @param {...HTMLElement} containers - Containers you want to remove from draggable * @return {Draggable} * @example draggable.removeContainer(document.body) */ removeContainer(...containers) { this.containers = this.containers.filter(container => !containers.includes(container)); this.sensors.forEach(sensor => sensor.removeContainer(...containers)); return this; } /** * Adds listener for draggable events * @param {String} type - Event name * @param {...Function} callbacks - Event callbacks * @return {Draggable} * @example draggable.on('drag:start', (dragEvent) => dragEvent.cancel()); */ on(type, ...callbacks) { this.emitter.on(type, ...callbacks); return this; } /** * Removes listener from draggable * @param {String} type - Event name * @param {Function} callback - Event callback * @return {Draggable} * @example draggable.off('drag:start', handlerFunction); */ off(type, callback) { this.emitter.off(type, callback); return this; } /** * Triggers draggable event * @param {AbstractEvent} event - Event instance * @return {Draggable} * @example draggable.trigger(event); */ trigger(event) { this.emitter.trigger(event); return this; } /** * Returns class name for class identifier * @param {String} name - Name of class identifier * @return {String|null} */ getClassNameFor(name) { return this.options.classes[name]; } /** * Returns true if this draggable instance is currently dragging * @return {Boolean} */ isDragging() { return Boolean(this.dragging); } /** * Returns all draggable elements * @return {HTMLElement[]} */ getDraggableElements() { return this.containers.reduce((current, container) => { return [...current, ...this.getDraggableElementsForContainer(container)]; }, []); } /** * Returns draggable elements for a given container, excluding the mirror and * original source element if present * @param {HTMLElement} container * @return {HTMLElement[]} */ getDraggableElementsForContainer(container) { const allDraggableElements = container.querySelectorAll(this.options.draggable); return [...allDraggableElements].filter(childElement => { return childElement !== this.originalSource && childElement !== this.mirror; }); } /** * Drag start handler * @private * @param {Event} event - DOM Drag event */ [onDragStart](event) { const sensorEvent = getSensorEvent(event); const { target, container } = sensorEvent; if (!this.containers.includes(container)) { return; } if (this.options.handle && target && !(0, _utils.closest)(target, this.options.handle)) { sensorEvent.cancel(); return; } // Find draggable source element this.originalSource = (0, _utils.closest)(target, this.options.draggable); this.sourceContainer = container; if (!this.originalSource) { sensorEvent.cancel(); return; } if (this.lastPlacedSource && this.lastPlacedContainer) { clearTimeout(this.placedTimeoutID); this.lastPlacedSource.classList.remove(this.getClassNameFor('source:placed')); this.lastPlacedContainer.classList.remove(this.getClassNameFor('container:placed')); } this.source = this.originalSource.cloneNode(true); this.originalSource.parentNode.insertBefore(this.source, this.originalSource); this.originalSource.style.display = 'none'; const dragEvent = new _DragEvent.DragStartEvent({ source: this.source, originalSource: this.originalSource, sourceContainer: container, sensorEvent }); this.trigger(dragEvent); this.dragging = !dragEvent.canceled(); if (dragEvent.canceled()) { this.source.parentNode.removeChild(this.source); this.originalSource.style.display = null; return; } this.originalSource.classList.add(this.getClassNameFor('source:original')); this.source.classList.add(this.getClassNameFor('source:dragging')); this.sourceContainer.classList.add(this.getClassNameFor('container:dragging')); document.body.classList.add(this.getClassNameFor('body:dragging')); applyUserSelect(document.body, 'none'); requestAnimationFrame(() => { const oldSensorEvent = getSensorEvent(event); const newSensorEvent = oldSensorEvent.clone({ target: this.source }); this[onDragMove](_extends({}, event, { detail: newSensorEvent })); }); } /** * Drag move handler * @private * @param {Event} event - DOM Drag event */ [onDragMove](event) { if (!this.dragging) { return; } const sensorEvent = getSensorEvent(event); const { container } = sensorEvent; let target = sensorEvent.target; const dragMoveEvent = new _DragEvent.DragMoveEvent({ source: this.source, originalSource: this.originalSource, sourceContainer: container, sensorEvent }); this.trigger(dragMoveEvent); if (dragMoveEvent.canceled()) { sensorEvent.cancel(); } target = (0, _utils.closest)(target, this.options.draggable); const withinCorrectContainer = (0, _utils.closest)(sensorEvent.target, this.containers); const overContainer = sensorEvent.overContainer || withinCorrectContainer; const isLeavingContainer = this.currentOverContainer && overContainer !== this.currentOverContainer; const isLeavingDraggable = this.currentOver && target !== this.currentOver; const isOverContainer = overContainer && this.currentOverContainer !== overContainer; const isOverDraggable = withinCorrectContainer && target && this.currentOver !== target; if (isLeavingDraggable) { const dragOutEvent = new _DragEvent.DragOutEvent({ source: this.source, originalSource: this.originalSource, sourceContainer: container, sensorEvent, over: this.currentOver }); this.currentOver.classList.remove(this.getClassNameFor('draggable:over')); this.currentOver = null; this.trigger(dragOutEvent); } if (isLeavingContainer) { const dragOutContainerEvent = new _DragEvent.DragOutContainerEvent({ source: this.source, originalSource: this.originalSource, sourceContainer: container, sensorEvent, overContainer: this.currentOverContainer }); this.currentOverContainer.classList.remove(this.getClassNameFor('container:over')); this.currentOverContainer = null; this.trigger(dragOutContainerEvent); } if (isOverContainer) { overContainer.classList.add(this.getClassNameFor('container:over')); const dragOverContainerEvent = new _DragEvent.DragOverContainerEvent({ source: this.source, originalSource: this.originalSource, sourceContainer: container, sensorEvent, overContainer }); this.currentOverContainer = overContainer; this.trigger(dragOverContainerEvent); } if (isOverDraggable) { target.classList.add(this.getClassNameFor('draggable:over')); const dragOverEvent = new _DragEvent.DragOverEvent({ source: this.source, originalSource: this.originalSource, sourceContainer: container, sensorEvent, overContainer, over: target }); this.currentOver = target; this.trigger(dragOverEvent); } } /** * Drag stop handler * @private * @param {Event} event - DOM Drag event */ [onDragStop](event) { if (!this.dragging) { return; } this.dragging = false; const dragStopEvent = new _DragEvent.DragStopEvent({ source: this.source, originalSource: this.originalSource, sensorEvent: event.sensorEvent, sourceContainer: this.sourceContainer }); this.trigger(dragStopEvent); this.source.parentNode.insertBefore(this.originalSource, this.source); this.source.parentNode.removeChild(this.source); this.originalSource.style.display = ''; this.source.classList.remove(this.getClassNameFor('source:dragging')); this.originalSource.classList.remove(this.getClassNameFor('source:original')); this.originalSource.classList.add(this.getClassNameFor('source:placed')); this.sourceContainer.classList.add(this.getClassNameFor('container:placed')); this.sourceContainer.classList.remove(this.getClassNameFor('container:dragging')); document.body.classList.remove(this.getClassNameFor('body:dragging')); applyUserSelect(document.body, ''); if (this.currentOver) { this.currentOver.classList.remove(this.getClassNameFor('draggable:over')); } if (this.currentOverContainer) { this.currentOverContainer.classList.remove(this.getClassNameFor('container:over')); } this.lastPlacedSource = this.originalSource; this.lastPlacedContainer = this.sourceContainer; this.placedTimeoutID = setTimeout(() => { if (this.lastPlacedSource) { this.lastPlacedSource.classList.remove(this.getClassNameFor('source:placed')); } if (this.lastPlacedContainer) { this.lastPlacedContainer.classList.remove(this.getClassNameFor('container:placed')); } this.lastPlacedSource = null; this.lastPlacedContainer = null; }, this.options.placedTimeout); this.source = null; this.originalSource = null; this.currentOverContainer = null; this.currentOver = null; this.sourceContainer = null; } /** * Drag pressure handler * @private * @param {Event} event - DOM Drag event */ [onDragPressure](event) { if (!this.dragging) { return; } const sensorEvent = getSensorEvent(event); const source = this.source || (0, _utils.closest)(sensorEvent.originalEvent.target, this.options.draggable); const dragPressureEvent = new _DragEvent.DragPressureEvent({ sensorEvent, source, pressure: sensorEvent.pressure }); this.trigger(dragPressureEvent); } } exports.default = Draggable; Draggable.Plugins = { Announcement: _Plugins.Announcement, Focusable: _Plugins.Focusable, Mirror: _Plugins.Mirror, Scrollable: _Plugins.Scrollable }; function getSensorEvent(event) { return event.detail; } function applyUserSelect(element, value) { element.style.webkitUserSelect = value; element.style.mozUserSelect = value; element.style.msUserSelect = value; element.style.oUserSelect = value; element.style.userSelect = value; } /***/ }), /* 13 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _Sensor = __webpack_require__(1); var _Sensor2 = _interopRequireDefault(_Sensor); var _SensorEvent = __webpack_require__(0); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const onMouseForceWillBegin = Symbol('onMouseForceWillBegin'); const onMouseForceDown = Symbol('onMouseForceDown'); const onMouseDown = Symbol('onMouseDown'); const onMouseForceChange = Symbol('onMouseForceChange'); const onMouseMove = Symbol('onMouseMove'); const onMouseUp = Symbol('onMouseUp'); const onMouseForceGlobalChange = Symbol('onMouseForceGlobalChange'); /** * This sensor picks up native force touch events and dictates drag operations * @class ForceTouchSensor * @module ForceTouchSensor * @extends Sensor */ class ForceTouchSensor extends _Sensor2.default { /** * ForceTouchSensor constructor. * @constructs ForceTouchSensor * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers * @param {Object} options - Options */ constructor(containers = [], options = {}) { super(containers, options); /** * Draggable element needs to be remembered to unset the draggable attribute after drag operation has completed * @property mightDrag * @type {Boolean} */ this.mightDrag = false; this[onMouseForceWillBegin] = this[onMouseForceWillBegin].bind(this); this[onMouseForceDown] = this[onMouseForceDown].bind(this); this[onMouseDown] = this[onMouseDown].bind(this); this[onMouseForceChange] = this[onMouseForceChange].bind(this); this[onMouseMove] = this[onMouseMove].bind(this); this[onMouseUp] = this[onMouseUp].bind(this); } /** * Attaches sensors event listeners to the DOM */ attach() { for (const container of this.containers) { container.addEventListener('webkitmouseforcewillbegin', this[onMouseForceWillBegin], false); container.addEventListener('webkitmouseforcedown', this[onMouseForceDown], false); container.addEventListener('mousedown', this[onMouseDown], true); container.addEventListener('webkitmouseforcechanged', this[onMouseForceChange], false); } document.addEventListener('mousemove', this[onMouseMove]); document.addEventListener('mouseup', this[onMouseUp]); } /** * Detaches sensors event listeners to the DOM */ detach() { for (const container of this.containers) { container.removeEventListener('webkitmouseforcewillbegin', this[onMouseForceWillBegin], false); container.removeEventListener('webkitmouseforcedown', this[onMouseForceDown], false); container.removeEventListener('mousedown', this[onMouseDown], true); container.removeEventListener('webkitmouseforcechanged', this[onMouseForceChange], false); } document.removeEventListener('mousemove', this[onMouseMove]); document.removeEventListener('mouseup', this[onMouseUp]); } /** * Mouse force will begin handler * @private * @param {Event} event - Mouse force will begin event */ [onMouseForceWillBegin](event) { event.preventDefault(); this.mightDrag = true; } /** * Mouse force down handler * @private * @param {Event} event - Mouse force down event */ [onMouseForceDown](event) { if (this.dragging) { return; } const target = document.elementFromPoint(event.clientX, event.clientY); const container = event.currentTarget; const dragStartEvent = new _SensorEvent.DragStartSensorEvent({ clientX: event.clientX, clientY: event.clientY, target, container, originalEvent: event }); this.trigger(container, dragStartEvent); this.currentContainer = container; this.dragging = !dragStartEvent.canceled(); this.mightDrag = false; } /** * Mouse up handler * @private * @param {Event} event - Mouse up event */ [onMouseUp](event) { if (!this.dragging) { return; } const dragStopEvent = new _SensorEvent.DragStopSensorEvent({ clientX: event.clientX, clientY: event.clientY, target: null, container: this.currentContainer, originalEvent: event }); this.trigger(this.currentContainer, dragStopEvent); this.currentContainer = null; this.dragging = false; this.mightDrag = false; } /** * Mouse down handler * @private * @param {Event} event - Mouse down event */ [onMouseDown](event) { if (!this.mightDrag) { return; } // Need workaround for real click // Cancel potential drag events event.stopPropagation(); event.stopImmediatePropagation(); event.preventDefault(); } /** * Mouse move handler * @private * @param {Event} event - Mouse force will begin event */ [onMouseMove](event) { if (!this.dragging) { return; } const target = document.elementFromPoint(event.clientX, event.clientY); const dragMoveEvent = new _SensorEvent.DragMoveSensorEvent({ clientX: event.clientX, clientY: event.clientY, target, container: this.currentContainer, originalEvent: event }); this.trigger(this.currentContainer, dragMoveEvent); } /** * Mouse force change handler * @private * @param {Event} event - Mouse force change event */ [onMouseForceChange](event) { if (this.dragging) { return; } const target = event.target; const container = event.currentTarget; const dragPressureEvent = new _SensorEvent.DragPressureSensorEvent({ pressure: event.webkitForce, clientX: event.clientX, clientY: event.clientY, target, container, originalEvent: event }); this.trigger(container, dragPressureEvent); } /** * Mouse force global change handler * @private * @param {Event} event - Mouse force global change event */ [onMouseForceGlobalChange](event) { if (!this.dragging) { return; } const target = event.target; const dragPressureEvent = new _SensorEvent.DragPressureSensorEvent({ pressure: event.webkitForce, clientX: event.clientX, clientY: event.clientY, target, container: this.currentContainer, originalEvent: event }); this.trigger(this.currentContainer, dragPressureEvent); } } exports.default = ForceTouchSensor; /***/ }), /* 14 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _ForceTouchSensor = __webpack_require__(13); var _ForceTouchSensor2 = _interopRequireDefault(_ForceTouchSensor); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _ForceTouchSensor2.default; /***/ }), /* 15 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _utils = __webpack_require__(2); var _Sensor = __webpack_require__(1); var _Sensor2 = _interopRequireDefault(_Sensor); var _SensorEvent = __webpack_require__(0); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const onMouseDown = Symbol('onMouseDown'); const onMouseUp = Symbol('onMouseUp'); const onDragStart = Symbol('onDragStart'); const onDragOver = Symbol('onDragOver'); const onDragEnd = Symbol('onDragEnd'); const onDrop = Symbol('onDrop'); const reset = Symbol('reset'); /** * This sensor picks up native browser drag events and dictates drag operations * @class DragSensor * @module DragSensor * @extends Sensor */ class DragSensor extends _Sensor2.default { /** * DragSensor constructor. * @constructs DragSensor * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers * @param {Object} options - Options */ constructor(containers = [], options = {}) { super(containers, options); /** * Mouse down timer which will end up setting the draggable attribute, unless canceled * @property mouseDownTimeout * @type {Number} */ this.mouseDownTimeout = null; /** * Draggable element needs to be remembered to unset the draggable attribute after drag operation has completed * @property draggableElement * @type {HTMLElement} */ this.draggableElement = null; /** * Native draggable element could be links or images, their draggable state will be disabled during drag operation * @property nativeDraggableElement * @type {HTMLElement} */ this.nativeDraggableElement = null; this[onMouseDown] = this[onMouseDown].bind(this); this[onMouseUp] = this[onMouseUp].bind(this); this[onDragStart] = this[onDragStart].bind(this); this[onDragOver] = this[onDragOver].bind(this); this[onDragEnd] = this[onDragEnd].bind(this); this[onDrop] = this[onDrop].bind(this); } /** * Attaches sensors event listeners to the DOM */ attach() { document.addEventListener('mousedown', this[onMouseDown], true); } /** * Detaches sensors event listeners to the DOM */ detach() { document.removeEventListener('mousedown', this[onMouseDown], true); } /** * Drag start handler * @private * @param {Event} event - Drag start event */ [onDragStart](event) { // Need for firefox. "text" key is needed for IE event.dataTransfer.setData('text', ''); event.dataTransfer.effectAllowed = this.options.type; const target = document.elementFromPoint(event.clientX, event.clientY); this.currentContainer = (0, _utils.closest)(event.target, this.containers); if (!this.currentContainer) { return; } const dragStartEvent = new _SensorEvent.DragStartSensorEvent({ clientX: event.clientX, clientY: event.clientY, target, container: this.currentContainer, originalEvent: event }); // Workaround setTimeout(() => { this.trigger(this.currentContainer, dragStartEvent); if (dragStartEvent.canceled()) { this.dragging = false; } else { this.dragging = true; } }, 0); } /** * Drag over handler * @private * @param {Event} event - Drag over event */ [onDragOver](event) { if (!this.dragging) { return; } const target = document.elementFromPoint(event.clientX, event.clientY); const container = this.currentContainer; const dragMoveEvent = new _SensorEvent.DragMoveSensorEvent({ clientX: event.clientX, clientY: event.clientY, target, container, originalEvent: event }); this.trigger(container, dragMoveEvent); if (!dragMoveEvent.canceled()) { event.preventDefault(); event.dataTransfer.dropEffect = this.options.type; } } /** * Drag end handler * @private * @param {Event} event - Drag end event */ [onDragEnd](event) { if (!this.dragging) { return; } document.removeEventListener('mouseup', this[onMouseUp], true); const target = document.elementFromPoint(event.clientX, event.clientY); const container = this.currentContainer; const dragStopEvent = new _SensorEvent.DragStopSensorEvent({ clientX: event.clientX, clientY: event.clientY, target, container, originalEvent: event }); this.trigger(container, dragStopEvent); this.dragging = false; this[reset](); } /** * Drop handler * @private * @param {Event} event - Drop event */ [onDrop](event) { // eslint-disable-line class-methods-use-this event.preventDefault(); } /** * Mouse down handler * @private * @param {Event} event - Mouse down event */ [onMouseDown](event) { // Firefox bug for inputs within draggables https://bugzilla.mozilla.org/show_bug.cgi?id=739071 if (event.target && (event.target.form || event.target.contenteditable)) { return; } const nativeDraggableElement = (0, _utils.closest)(event.target, element => element.draggable); if (nativeDraggableElement) { nativeDraggableElement.draggable = false; this.nativeDraggableElement = nativeDraggableElement; } document.addEventListener('mouseup', this[onMouseUp], true); document.addEventListener('dragstart', this[onDragStart], false); document.addEventListener('dragover', this[onDragOver], false); document.addEventListener('dragend', this[onDragEnd], false); document.addEventListener('drop', this[onDrop], false); const target = (0, _utils.closest)(event.target, this.options.draggable); if (!target) { return; } this.mouseDownTimeout = setTimeout(() => { target.draggable = true; this.draggableElement = target; }, this.options.delay); } /** * Mouse up handler * @private * @param {Event} event - Mouse up event */ [onMouseUp]() { this[reset](); } /** * Mouse up handler * @private * @param {Event} event - Mouse up event */ [reset]() { clearTimeout(this.mouseDownTimeout); document.removeEventListener('mouseup', this[onMouseUp], true); document.removeEventListener('dragstart', this[onDragStart], false); document.removeEventListener('dragover', this[onDragOver], false); document.removeEventListener('dragend', this[onDragEnd], false); document.removeEventListener('drop', this[onDrop], false); if (this.nativeDraggableElement) { this.nativeDraggableElement.draggable = true; this.nativeDraggableElement = null; } if (this.draggableElement) { this.draggableElement.draggable = false; this.draggableElement = null; } } } exports.default = DragSensor; /***/ }), /* 16 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _DragSensor = __webpack_require__(15); var _DragSensor2 = _interopRequireDefault(_DragSensor); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } exports.default = _DragSensor2.default; /***/ }), /* 17 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var _utils = __webpack_require__(2); var _Sensor = __webpack_require__(1); var _Sensor2 = _interopRequireDefault(_Sensor); var _SensorEvent = __webpack_require__(0); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } const onTouchStart = Symbol('onTouchStart'); const onTouchHold = Symbol('onTouchHold'); const onTouchEnd = Symbol('onTouchEnd'); const onTouchMove = Symbol('onTouchMove'); /** * Prevents scrolling when set to true * @var {Boolean} preventScrolling */ let preventScrolling = false; // WebKit requires cancelable `touchmove` events to be added as early as possible window.addEventListener('touchmove', event => { if (!preventScrolling) { return; } // Prevent scrolling event.preventDefault(); }, { passive: false }); /** * This sensor picks up native browser touch events and dictates drag operations * @class TouchSensor * @module TouchSensor * @extends Sensor */ class TouchSensor extends _Sensor2.default { /** * TouchSensor constructor. * @constructs TouchSensor * @param {HTMLElement[]|NodeList|HTMLElement} containers - Containers * @param {Object} options - Options */ constructor(containers = [], options = {}) { super(containers, options); /** * Closest scrollable container so accidental scroll can cancel long touch * @property currentScrollableParent * @type {HTMLElement} */ this.currentScrollableParent = null; /** * TimeoutID for long touch * @property tapTimeout * @type {Number} */ this.tapTimeout = null; /** * touchMoved indicates if touch has moved during tapTimeout * @property touchMoved * @type {Boolean} */ this.touchMoved = false; this[onTouchStart] = this[onTouchStart].bind(this); this[onTouchHold] = this[onTouchHold].bind(this); this[onTouchEnd] = this[onTouchEnd].bind(this); this[onTouchMove] = this[onTouchMove].bind(this); } /** * Attaches sensors event listeners to the DOM */ attach() { document.addEventListener('touchstart', this[onTouchStart]); } /** * Detaches sensors event listeners to the DOM */ detach() { document.removeEventListener('touchstart', this[onTouchStart]); } /** * Touch start handler * @private * @param {Event} event - Touch start event */ [onTouchStart](event) { const container = (0, _utils.closest)(event.target, this.containers); if (!container) { return; } document.addEventListener('touchmove', this[onTouchMove]); document.addEventListener('touchend', this[onTouchEnd]); document.addEventListener('touchcancel', this[onTouchEnd]); container.addEventListener('contextmenu', onContextMenu); this.currentContainer = container; this.tapTimeout = setTimeout(this[onTouchHold](event, container), this.options.delay); } /** * Touch hold handler * @private * @param {Event} event - Touch start event * @param {HTMLElement} container - Container element */ [onTouchHold](event, container) { return () => { if (this.touchMoved) { return; } const touch = event.touches[0] || event.changedTouches[0]; const target = event.target; const dragStartEvent = new _SensorEvent.DragStartSensorEvent({ clientX: touch.pageX, clientY: touch.pageY, target, container, originalEvent: event }); this.trigger(container, dragStartEvent); this.dragging = !dragStartEvent.canceled(); preventScrolling = this.dragging; }; } /** * Touch move handler * @private * @param {Event} event - Touc