diff --git a/.gitignore b/.gitignore index 9c628283..ba2a97b5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,2 @@ node_modules coverage -dist diff --git a/dist/html5sortable.amd.js b/dist/html5sortable.amd.js new file mode 100644 index 00000000..8dc1046d --- /dev/null +++ b/dist/html5sortable.amd.js @@ -0,0 +1,1213 @@ +/* + * HTML5Sortable package + * https://github.com/lukasoppermann/html5sortable + * + * Maintained by Lukas Oppermann + * + * Released under the MIT license. + */ +define(function () { 'use strict'; + + /** + * Get or set data on element + * @param {HTMLElement} element + * @param {string} key + * @param {any} value + * @return {*} + */ + function addData(element, key, value) { + if (value === undefined) { + return element && element.h5s && element.h5s.data && element.h5s.data[key]; + } + else { + element.h5s = element.h5s || {}; + element.h5s.data = element.h5s.data || {}; + element.h5s.data[key] = value; + } + } + /** + * Remove data from element + * @param {HTMLElement} element + */ + function removeData(element) { + if (element.h5s) { + delete element.h5s.data; + } + } + + /* eslint-env browser */ + /** + * Filter only wanted nodes + * @param {NodeList|HTMLCollection|Array} nodes + * @param {String} selector + * @returns {Array} + */ + var _filter = (function (nodes, selector) { + if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) { + throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.'); + } + if (typeof selector !== 'string') { + return Array.from(nodes); + } + return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); }); + }); + + /* eslint-env browser */ + var stores = new Map(); + /** + * Stores data & configurations per Sortable + * @param {Object} config + */ + var Store = /** @class */ (function () { + function Store() { + this._config = new Map(); // eslint-disable-line no-undef + this._placeholder = undefined; // eslint-disable-line no-undef + this._data = new Map(); // eslint-disable-line no-undef + } + Object.defineProperty(Store.prototype, "config", { + /** + * get the configuration map of a class instance + * @method config + * @return {object} + */ + get: function () { + // transform Map to object + var config = {}; + this._config.forEach(function (value, key) { + config[key] = value; + }); + // return object + return config; + }, + /** + * set the configuration of a class instance + * @method config + * @param {object} config object of configurations + */ + set: function (config) { + if (typeof config !== 'object') { + throw new Error('You must provide a valid configuration object to the config setter.'); + } + // combine config with default + var mergedConfig = Object.assign({}, config); + // add config to map + this._config = new Map(Object.entries(mergedConfig)); + }, + enumerable: true, + configurable: true + }); + /** + * set individual configuration of a class instance + * @method setConfig + * @param key valid configuration key + * @param value any value + * @return void + */ + Store.prototype.setConfig = function (key, value) { + if (!this._config.has(key)) { + throw new Error("Trying to set invalid configuration item: " + key); + } + // set config + this._config.set(key, value); + }; + /** + * get an individual configuration of a class instance + * @method getConfig + * @param key valid configuration key + * @return any configuration value + */ + Store.prototype.getConfig = function (key) { + if (!this._config.has(key)) { + throw new Error("Invalid configuration item requested: " + key); + } + return this._config.get(key); + }; + Object.defineProperty(Store.prototype, "placeholder", { + /** + * get the placeholder for a class instance + * @method placeholder + * @return {HTMLElement|null} + */ + get: function () { + return this._placeholder; + }, + /** + * set the placeholder for a class instance + * @method placeholder + * @param {HTMLElement} placeholder + * @return {void} + */ + set: function (placeholder) { + if (!(placeholder instanceof HTMLElement) && placeholder !== null) { + throw new Error('A placeholder must be an html element or null.'); + } + this._placeholder = placeholder; + }, + enumerable: true, + configurable: true + }); + /** + * set an data entry + * @method setData + * @param {string} key + * @param {any} value + * @return {void} + */ + Store.prototype.setData = function (key, value) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + this._data.set(key, value); + }; + /** + * get an data entry + * @method getData + * @param {string} key an existing key + * @return {any} + */ + Store.prototype.getData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.get(key); + }; + /** + * delete an data entry + * @method deleteData + * @param {string} key an existing key + * @return {boolean} + */ + Store.prototype.deleteData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.delete(key); + }; + return Store; + }()); + /** + * @param {HTMLElement} sortableElement + * @returns {Class: Store} + */ + var store = (function (sortableElement) { + // if sortableElement is wrong type + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('Please provide a sortable to the store function.'); + } + // create new instance if not avilable + if (!stores.has(sortableElement)) { + stores.set(sortableElement, new Store()); + } + // return instance + return stores.get(sortableElement); + }); + + /** + * @param {Array|HTMLElement} element + * @param {Function} callback + * @param {string} event + */ + function addEventListener(element, eventName, callback) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addEventListener(element[i], eventName, callback); + } + return; + } + element.addEventListener(eventName, callback); + store(element).setData("event" + eventName, callback); + } + /** + * @param {Array|HTMLElement} element + * @param {string} eventName + */ + function removeEventListener(element, eventName) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeEventListener(element[i], eventName); + } + return; + } + element.removeEventListener(eventName, store(element).getData("event" + eventName)); + store(element).deleteData("event" + eventName); + } + + /** + * @param {Array|HTMLElement} element + * @param {string} attribute + * @param {string} value + */ + function addAttribute(element, attribute, value) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addAttribute(element[i], attribute, value); + } + return; + } + element.setAttribute(attribute, value); + } + /** + * @param {Array|HTMLElement} element + * @param {string} attribute + */ + function removeAttribute(element, attribute) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeAttribute(element[i], attribute); + } + return; + } + element.removeAttribute(attribute); + } + + /** + * @param {HTMLElement} element + * @returns {Object} + */ + var _offset = (function (element) { + if (!element.parentElement || element.getClientRects().length === 0) { + throw new Error('target element must be part of the dom'); + } + var rect = element.getClientRects()[0]; + return { + left: rect.left + window.pageXOffset, + right: rect.right + window.pageXOffset, + top: rect.top + window.pageYOffset, + bottom: rect.bottom + window.pageYOffset + }; + }); + + /** + * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed + * @param {Function} func to debounce + * @param {number} time to wait before calling function with latest arguments, 0 - no debounce + * @returns {function} - debounced function + */ + var _debounce = (function (func, wait) { + if (wait === void 0) { wait = 0; } + var timeout; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + clearTimeout(timeout); + timeout = setTimeout(function () { + func.apply(void 0, args); + }, wait); + }; + }); + + /* eslint-env browser */ + /** + * Get position of the element relatively to its sibling elements + * @param {HTMLElement} element + * @returns {number} + */ + var _index = (function (element, elementList) { + if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) { + throw new Error('You must provide an element and a list of elements.'); + } + return Array.from(elementList).indexOf(element); + }); + + /* eslint-env browser */ + /** + * Test whether element is in DOM + * @param {HTMLElement} element + * @returns {boolean} + */ + var isInDom = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('Element is not a node element.'); + } + return element.parentNode !== null; + }); + + /* eslint-env browser */ + /** + * Insert node before or after target + * @param {HTMLElement} referenceNode - reference element + * @param {HTMLElement} newElement - element to be inserted + * @param {String} position - insert before or after reference element + */ + var insertNode = function (referenceNode, newElement, position) { + if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) { + throw new Error('target and element must be a node'); + } + referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling)); + }; + /** + * Insert before target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ + var insertBefore = function (target, element) { return insertNode(target, element, 'before'); }; + /** + * Insert after target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ + var insertAfter = function (target, element) { return insertNode(target, element, 'after'); }; + + /* eslint-env browser */ + /** + * Filter only wanted nodes + * @param {HTMLElement} sortableContainer + * @param {Function} customSerializer + * @returns {Array} + */ + var _serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) { + if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; } + if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; } + // check for valid sortableContainer + if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) { + throw new Error('You need to provide a sortableContainer to be serialized.'); + } + // check for valid serializers + if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') { + throw new Error('You need to provide a valid serializer for items and the container.'); + } + // get options + var options = addData(sortableContainer, 'opts'); + var item = options.items; + // serialize container + var items = _filter(sortableContainer.children, item); + var serializedItems = items.map(function (item) { + return { + parent: sortableContainer, + node: item, + html: item.outerHTML, + index: _index(item, items) + }; + }); + // serialize container + var container = { + node: sortableContainer, + itemCount: serializedItems.length + }; + return { + container: customContainerSerializer(container), + items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); }) + }; + }); + + /* eslint-env browser */ + /** + * create a placeholder element + * @param {HTMLElement} sortableElement a single sortable + * @param {string|undefined} placeholder a string representing an html element + * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder + */ + var _makePlaceholder = (function (sortableElement, placeholder, placeholderClass) { + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + var _a; + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('You must provide a valid element as a sortable.'); + } + // if placeholder is not an element + if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) { + throw new Error('You must provide a valid element as a placeholder or set ot to undefined.'); + } + // if no placeholder element is given + if (placeholder === undefined) { + if (['UL', 'OL'].includes(sortableElement.tagName)) { + placeholder = document.createElement('li'); + } + else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) { + placeholder = document.createElement('tr'); + // set colspan to always all rows, otherwise the item can only be dropped in first column + placeholder.innerHTML = ''; + } + else { + placeholder = document.createElement('div'); + } + } + // add classes to placeholder + if (typeof placeholderClass === 'string') { + (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' ')); + } + return placeholder; + }); + + /* eslint-env browser */ + /** + * Get height of an element including padding + * @param {HTMLElement} element an dom element + */ + var _getElementHeight = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('You must provide a valid dom element'); + } + // get calculated style of element + var style = window.getComputedStyle(element); + // pick applicable properties, convert to int and reduce by adding + return ['height', 'padding-top', 'padding-bottom'] + .map(function (key) { + var int = parseInt(style.getPropertyValue(key), 10); + return isNaN(int) ? 0 : int; + }) + .reduce(function (sum, value) { return sum + value; }); + }); + + /* eslint-env browser */ + /** + * get handle or return item + * @param {Array} items + * @param {string} selector + */ + var _getHandles = (function (items, selector) { + if (!(items instanceof Array)) { + throw new Error('You must provide a Array of HTMLElements to be filtered.'); + } + if (typeof selector !== 'string') { + return items; + } + return items + // remove items without handle from array + .filter(function (item) { + return item.querySelector(selector) instanceof HTMLElement || + (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement); + }) + // replace item with handle in array + .map(function (item) { + return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector)); + }); + }); + + /** + * @param {Event} event + * @returns {HTMLElement} + */ + var getEventTarget = (function (event) { + return (event.composedPath && event.composedPath()[0]) || event.target; + }); + + /* eslint-env browser */ + /** + * defaultDragImage returns the current item as dragged image + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {object} elementOffset - an object with the offsets top, left, right & bottom + * @param {Event} event - the original drag event object + * @return {object} with element, posX and posY properties + */ + var defaultDragImage = function (draggedElement, elementOffset, event) { + return { + element: draggedElement, + posX: event.pageX - elementOffset.left, + posY: event.pageY - elementOffset.top + }; + }; + /** + * attaches an element as the drag image to an event + * @param {Event} event - the original drag event object + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {Function} customDragImage - function to create a custom dragImage + * @return void + */ + var setDragImage = (function (event, draggedElement, customDragImage) { + // check if event is provided + if (!(event instanceof Event)) { + throw new Error('setDragImage requires a DragEvent as the first argument.'); + } + // check if draggedElement is provided + if (!(draggedElement instanceof HTMLElement)) { + throw new Error('setDragImage requires the dragged element as the second argument.'); + } + // set default function of none provided + if (!customDragImage) { + customDragImage = defaultDragImage; + } + // check if setDragImage method is available + if (event.dataTransfer && event.dataTransfer.setDragImage) { + // get the elements offset + var elementOffset = _offset(draggedElement); + // get the dragImage + var dragImage = customDragImage(draggedElement, elementOffset, event); + // check if custom function returns correct values + if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') { + throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].'); + } + // needs to be set for HTML5 drag & drop to work + event.dataTransfer.effectAllowed = 'copyMove'; + // Firefox requires it to use the event target's id for the data + event.dataTransfer.setData('text/plain', getEventTarget(event).id); + // set the drag image on the event + event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY); + } + }); + + /** + * Check if curList accepts items from destList + * @param {sortable} destination the container an item is move to + * @param {sortable} origin the container an item comes from + */ + var _listsConnected = (function (destination, origin) { + // check if valid sortable + if (destination.isSortable === true) { + var acceptFrom = store(destination).getConfig('acceptFrom'); + // check if acceptFrom is valid + if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') { + throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.'); + } + if (acceptFrom !== null) { + return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) { + return sel.length > 0 && origin.matches(sel); + }).length > 0; + } + // drop in same list + if (destination === origin) { + return true; + } + // check if lists are connected with connectWith + if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) { + return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith'); + } + } + return false; + }); + + /** + * default configurations + */ + var defaultConfiguration = { + items: null, + // deprecated + connectWith: null, + // deprecated + disableIEFix: null, + acceptFrom: null, + copy: false, + placeholder: null, + placeholderClass: 'sortable-placeholder', + draggingClass: 'sortable-dragging', + hoverClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null + }; + + /** + * make sure a function is only called once within the given amount of time + * @param {Function} fn the function to throttle + * @param {number} threshold time limit for throttling + */ + // must use function to keep this context + function _throttle (fn, threshold) { + var _this = this; + if (threshold === void 0) { threshold = 250; } + // check function + if (typeof fn !== 'function') { + throw new Error('You must provide a function as the first argument for throttle.'); + } + // check threshold + if (typeof threshold !== 'number') { + throw new Error('You must provide a number as the second argument for throttle.'); + } + var lastEventTimestamp = null; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var now = Date.now(); + if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) { + lastEventTimestamp = now; + fn.apply(_this, args); + } + }; + } + + /* eslint-env browser */ + /** + * enable or disable hoverClass on mouseenter/leave if container Items + * @param {sortable} sortableContainer a valid sortableContainer + * @param {boolean} enable enable or disable event + */ + // export default (sortableContainer: sortable, enable: boolean) => { + var enableHoverClass = (function (sortableContainer, enable) { + if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') { + var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' '); + // add class on hover + if (enable === true) { + addEventListener(sortableContainer, 'mousemove', _throttle(function (event) { + // check of no mouse button was pressed when mousemove started == no drag + if (event.buttons === 0) { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a, _b; + if (item !== event.target) { + (_a = item.classList).remove.apply(_a, hoverClasses_1); + } + else { + (_b = item.classList).add.apply(_b, hoverClasses_1); + } + }); + } + }, store(sortableContainer).getConfig('throttleTime'))); + // remove class on leave + addEventListener(sortableContainer, 'mouseleave', function () { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a; + (_a = item.classList).remove.apply(_a, hoverClasses_1); + }); + }); + // remove events + } + else { + removeEventListener(sortableContainer, 'mousemove'); + removeEventListener(sortableContainer, 'mouseleave'); + } + } + }); + + /* eslint-env browser */ + /* + * variables global to the plugin + */ + var dragging; + var draggingHeight; + /* + * Keeps track of the initialy selected list, where 'dragstart' event was triggered + * It allows us to move the data in between individual Sortable List instances + */ + // Origin List - data from before any item was changed + var originContainer; + var originIndex; + var originElementIndex; + var originItemsBeforeUpdate; + // Previous Sortable Container - we dispatch as sortenter event when a + // dragged item enters a sortableContainer for the first time + var previousContainer; + // Destination List - data from before any item was changed + var destinationItemsBeforeUpdate; + /** + * remove event handlers from items + * @param {Array|NodeList} items + */ + var _removeItemEvents = function (items) { + removeEventListener(items, 'dragstart'); + removeEventListener(items, 'dragend'); + removeEventListener(items, 'dragover'); + removeEventListener(items, 'dragenter'); + removeEventListener(items, 'drop'); + removeEventListener(items, 'mouseenter'); + removeEventListener(items, 'mouseleave'); + }; + /** + * _getDragging returns the current element to drag or + * a copy of the element. + * Is Copy Active for sortable + * @param {HTMLElement} draggedItem - the item that the user drags + * @param {HTMLElement} sortable a single sortable + */ + var _getDragging = function (draggedItem, sortable) { + var ditem = draggedItem; + if (store(sortable).getConfig('copy') === true) { + ditem = draggedItem.cloneNode(true); + addAttribute(ditem, 'aria-copied', 'true'); + draggedItem.parentElement.appendChild(ditem); + ditem.style.display = 'none'; + ditem.oldDisplay = draggedItem.style.display; + } + return ditem; + }; + /** + * Remove data from sortable + * @param {HTMLElement} sortable a single sortable + */ + var _removeSortableData = function (sortable) { + removeData(sortable); + removeAttribute(sortable, 'aria-dropeffect'); + }; + /** + * Remove data from items + * @param {Array|HTMLElement} items + */ + var _removeItemData = function (items) { + removeAttribute(items, 'aria-grabbed'); + removeAttribute(items, 'aria-copied'); + removeAttribute(items, 'draggable'); + removeAttribute(items, 'role'); + }; + /** + * find sortable from element. travels up parent element until found or null. + * @param {HTMLElement} element a single sortable + * @param {Event} event - the current event. We need to pass it to be able to + * find Sortable whith shadowRoot (document fragment has no parent) + */ + function findSortable(element, event) { + if (event.composedPath) { + return event.composedPath().find(function (el) { return el.isSortable; }); + } + while (element.isSortable !== true) { + element = element.parentElement; + } + return element; + } + /** + * Dragging event is on the sortable element. finds the top child that + * contains the element. + * @param {HTMLElement} sortableElement a single sortable + * @param {HTMLElement} element is that being dragged + */ + function findDragElement(sortableElement, element) { + var options = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, options.items); + var itemlist = items.filter(function (ele) { + return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element)); + }); + return itemlist.length > 0 ? itemlist[0] : element; + } + /** + * Destroy the sortable + * @param {HTMLElement} sortableElement a single sortable + */ + var _destroySortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts') || {}; + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + _removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + _removeItemEvents(items); + _removeItemData(items); + }; + /** + * Enable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ + var _enableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'move'); + addData(sortableElement, '_disabled', 'false'); + addAttribute(handles, 'draggable', 'true'); + // @todo: remove this fix + // IE FIX for ghost + // can be disabled as it has the side effect that other events + // (e.g. click) will be ignored + if (opts.disableIEFix === false) { + var spanEl = (document || window.document).createElement('span'); + if (typeof spanEl.dragDrop === 'function') { + addEventListener(handles, 'mousedown', function () { + if (items.indexOf(this) !== -1) { + this.dragDrop(); + } + else { + var parent = this.parentElement; + while (items.indexOf(parent) === -1) { + parent = parent.parentElement; + } + parent.dragDrop(); + } + }); + } + } + }; + /** + * Disable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ + var _disableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'none'); + addData(sortableElement, '_disabled', 'true'); + addAttribute(handles, 'draggable', 'false'); + removeEventListener(handles, 'mousedown'); + }; + /** + * Reload the sortable + * @param {HTMLElement} sortableElement a single sortable + * @description events need to be removed to not be double bound + */ + var _reloadSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addData(sortableElement, '_disabled', 'false'); + // remove event handlers from items + _removeItemEvents(items); + removeEventListener(handles, 'mousedown'); + // remove event handlers from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); + }; + /** + * Public sortable object + * @param {Array|NodeList} sortableElements + * @param {object|string} options|method + */ + function sortable(sortableElements, options) { + // get method string to see if a method is called + var method = String(options); + options = options || {}; + // check if the user provided a selector instead of an element + if (typeof sortableElements === 'string') { + sortableElements = document.querySelectorAll(sortableElements); + } + // if the user provided an element, return it in an array to keep the return value consistant + if (sortableElements instanceof HTMLElement) { + sortableElements = [sortableElements]; + } + sortableElements = Array.prototype.slice.call(sortableElements); + if (/serialize/.test(method)) { + return sortableElements.map(function (sortableContainer) { + var opts = addData(sortableContainer, 'opts'); + return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer); + }); + } + sortableElements.forEach(function (sortableElement) { + if (/enable|disable|destroy/.test(method)) { + return sortable[method](sortableElement); + } + // log deprecation + ['connectWith', 'disableIEFix'].forEach(function (configKey) { + if (options.hasOwnProperty(configKey) && options[configKey] !== null) { + console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating."); + } + }); + // merge options with default options + options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options); + // init data store for sortable + store(sortableElement).config = options; + // set options on sortable + addData(sortableElement, 'opts', options); + // property to define as sortable + sortableElement.isSortable = true; + // reset sortable + _reloadSortable(sortableElement); + // initialize + var listItems = _filter(sortableElement.children, options.items); + // create element if user defined a placeholder element as a string + var customPlaceholder; + if (options.placeholder !== null && options.placeholder !== undefined) { + var tempContainer = document.createElement(sortableElement.tagName); + if (options.placeholder instanceof HTMLElement) { + tempContainer.appendChild(options.placeholder); + } + else { + tempContainer.innerHTML = options.placeholder; + } + customPlaceholder = tempContainer.children[0]; + } + // add placeholder + store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass); + addData(sortableElement, 'items', options.items); + if (options.acceptFrom) { + addData(sortableElement, 'acceptFrom', options.acceptFrom); + } + else if (options.connectWith) { + addData(sortableElement, 'connectWith', options.connectWith); + } + _enableSortable(sortableElement); + addAttribute(listItems, 'role', 'option'); + addAttribute(listItems, 'aria-grabbed', 'false'); + // enable hover class + enableHoverClass(sortableElement, true); + /* + Handle drag events on draggable items + Handle is set at the sortableElement level as it will bubble up + from the item + */ + addEventListener(sortableElement, 'dragstart', function (e) { + // ignore dragstart events + var target = getEventTarget(e); + if (target.isSortable === true) { + return; + } + e.stopImmediatePropagation(); + if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') { + return; + } + var sortableContainer = findSortable(target, e); + var dragItem = findDragElement(sortableContainer, target); + // grab values + originItemsBeforeUpdate = _filter(sortableContainer.children, options.items); + originIndex = originItemsBeforeUpdate.indexOf(dragItem); + originElementIndex = _index(dragItem, sortableContainer.children); + originContainer = sortableContainer; + // add transparent clone or other ghost to cursor + setDragImage(e, dragItem, options.customDragImage); + // cache selsection & add attr for dragging + draggingHeight = _getElementHeight(dragItem); + dragItem.classList.add(options.draggingClass); + dragging = _getDragging(dragItem, sortableContainer); + addAttribute(dragging, 'aria-grabbed', 'true'); + // dispatch sortstart event on each element in group + sortableContainer.dispatchEvent(new CustomEvent('sortstart', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging, + originalTarget: target + } + })); + }); + /* + We are capturing targetSortable before modifications with 'dragenter' event + */ + addEventListener(sortableElement, 'dragenter', function (e) { + var target = getEventTarget(e); + var sortableContainer = findSortable(target, e); + if (sortableContainer && sortableContainer !== previousContainer) { + destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items')) + .filter(function (item) { return item !== store(sortableElement).placeholder; }); + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + } + previousContainer = sortableContainer; + }); + /* + * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend + * Fires each time dragEvent end, or ESC pressed + * We are using it to clean up any draggable elements and placeholders + */ + addEventListener(sortableElement, 'dragend', function (e) { + if (!dragging) { + return; + } + dragging.classList.remove(options.draggingClass); + addAttribute(dragging, 'aria-grabbed', 'false'); + if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') { + dragging.remove(); + } + dragging.style.display = dragging.oldDisplay; + delete dragging.oldDisplay; + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; }) + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + .filter(isInDom)[0]; + if (visiblePlaceholder) { + visiblePlaceholder.remove(); + } + // dispatch sortstart event on each element in group + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + previousContainer = null; + dragging = null; + draggingHeight = null; + }); + /* + * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop + * Fires when valid drop target area is hit + */ + addEventListener(sortableElement, 'drop', function (e) { + if (!_listsConnected(sortableElement, dragging.parentElement)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + addData(dragging, 'dropped', 'true'); + // get the one placeholder that is currently visible + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { + return data.placeholder; + }) + // filter only HTMLElements + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + // filter only elements in DOM + .filter(isInDom)[0]; + // attach element after placeholder + insertAfter(visiblePlaceholder, dragging); + // remove placeholder from dom + visiblePlaceholder.remove(); + /* + * Fires Custom Event - 'sortstop' + */ + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + var placeholder = store(sortableElement).placeholder; + var originItems = _filter(originContainer.children, options.items) + .filter(function (item) { return item !== placeholder; }); + var destinationContainer = this.isSortable === true ? this : this.parentElement; + var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items')) + .filter(function (item) { return item !== placeholder; }); + var destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = _index(dragging, destinationItems); + /* + * When a list item changed container lists or index within a list + * Fires Custom Event - 'sortupdate' + */ + if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { + sortableElement.dispatchEvent(new CustomEvent('sortupdate', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer, + itemsBeforeUpdate: originItemsBeforeUpdate, + items: originItems + }, + destination: { + index: destinationIndex, + elementIndex: destinationElementIndex, + container: destinationContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate, + items: destinationItems + }, + item: dragging + } + })); + } + }); + var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + } + // if element the draggedItem is dragged onto is within the array of all elements in list + // (not only items, but also disabled, etc.) + if (Array.from(sortableElement.children).indexOf(element) > -1) { + var thisHeight = _getElementHeight(element); + var placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = _index(element, element.parentElement.children); + // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering + if (thisHeight > draggingHeight) { + // Dead zone? + var deadZone = thisHeight - draggingHeight; + var offsetTop = _offset(element).top; + if (placeholderIndex < thisIndex && pageY < offsetTop) { + return; + } + if (placeholderIndex > thisIndex && + pageY > offsetTop + thisHeight - deadZone) { + return; + } + } + if (dragging.oldDisplay === undefined) { + dragging.oldDisplay = dragging.style.display; + } + if (dragging.style.display !== 'none') { + dragging.style.display = 'none'; + } + // To avoid flicker, determine where to position the placeholder + // based on where the mouse pointer is relative to the elements + // vertical center. + var placeAfter = false; + try { + var elementMiddle = _offset(element).top + element.offsetHeight / 2; + placeAfter = pageY >= elementMiddle; + } + catch (e) { + placeAfter = placeholderIndex < thisIndex; + } + if (placeAfter) { + insertAfter(element, store(sortableElement).placeholder); + } + else { + insertBefore(element, store(sortableElement).placeholder); + } + // get placeholders from all stores & remove all but current one + Array.from(stores.values()) + // remove empty values + .filter(function (data) { return data.placeholder !== undefined; }) + // foreach placeholder in array if outside of current sorableContainer -> remove from DOM + .forEach(function (data) { + if (data.placeholder !== store(sortableElement).placeholder) { + data.placeholder.remove(); + } + }); + } + else { + // get all placeholders from store + var placeholders = Array.from(stores.values()) + .filter(function (data) { return data.placeholder !== undefined; }) + .map(function (data) { + return data.placeholder; + }); + // check if element is not in placeholders + if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) { + placeholders.forEach(function (element) { return element.remove(); }); + element.appendChild(store(sortableElement).placeholder); + } + } + }, options.debounce); + // Handle dragover and dragenter events on draggable items + var onDragOverEnter = function (e) { + var element = e.target; + var sortableElement = element.isSortable === true ? element : findSortable(element, e); + element = findDragElement(sortableElement, element); + if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') { + return; + } + var options = addData(sortableElement, 'opts'); + if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) { + return; + } + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move'; + debouncedDragOverEnter(sortableElement, element, e.pageY); + }; + addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter); + addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter); + }); + return sortableElements; + } + sortable.destroy = function (sortableElement) { + _destroySortable(sortableElement); + }; + sortable.enable = function (sortableElement) { + _enableSortable(sortableElement); + }; + sortable.disable = function (sortableElement) { + _disableSortable(sortableElement); + }; + /* START.TESTS_ONLY */ + sortable.__testing = { + // add internal methods here for testing purposes + _data: addData, + _removeItemEvents: _removeItemEvents, + _removeItemData: _removeItemData, + _removeSortableData: _removeSortableData + }; + + return sortable; + +}); diff --git a/dist/html5sortable.cjs.js b/dist/html5sortable.cjs.js new file mode 100644 index 00000000..d9ad61c9 --- /dev/null +++ b/dist/html5sortable.cjs.js @@ -0,0 +1,1211 @@ +/* + * HTML5Sortable package + * https://github.com/lukasoppermann/html5sortable + * + * Maintained by Lukas Oppermann + * + * Released under the MIT license. + */ +'use strict'; + +/** + * Get or set data on element + * @param {HTMLElement} element + * @param {string} key + * @param {any} value + * @return {*} + */ +function addData(element, key, value) { + if (value === undefined) { + return element && element.h5s && element.h5s.data && element.h5s.data[key]; + } + else { + element.h5s = element.h5s || {}; + element.h5s.data = element.h5s.data || {}; + element.h5s.data[key] = value; + } +} +/** + * Remove data from element + * @param {HTMLElement} element + */ +function removeData(element) { + if (element.h5s) { + delete element.h5s.data; + } +} + +/* eslint-env browser */ +/** + * Filter only wanted nodes + * @param {NodeList|HTMLCollection|Array} nodes + * @param {String} selector + * @returns {Array} + */ +var _filter = (function (nodes, selector) { + if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) { + throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.'); + } + if (typeof selector !== 'string') { + return Array.from(nodes); + } + return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); }); +}); + +/* eslint-env browser */ +var stores = new Map(); +/** + * Stores data & configurations per Sortable + * @param {Object} config + */ +var Store = /** @class */ (function () { + function Store() { + this._config = new Map(); // eslint-disable-line no-undef + this._placeholder = undefined; // eslint-disable-line no-undef + this._data = new Map(); // eslint-disable-line no-undef + } + Object.defineProperty(Store.prototype, "config", { + /** + * get the configuration map of a class instance + * @method config + * @return {object} + */ + get: function () { + // transform Map to object + var config = {}; + this._config.forEach(function (value, key) { + config[key] = value; + }); + // return object + return config; + }, + /** + * set the configuration of a class instance + * @method config + * @param {object} config object of configurations + */ + set: function (config) { + if (typeof config !== 'object') { + throw new Error('You must provide a valid configuration object to the config setter.'); + } + // combine config with default + var mergedConfig = Object.assign({}, config); + // add config to map + this._config = new Map(Object.entries(mergedConfig)); + }, + enumerable: true, + configurable: true + }); + /** + * set individual configuration of a class instance + * @method setConfig + * @param key valid configuration key + * @param value any value + * @return void + */ + Store.prototype.setConfig = function (key, value) { + if (!this._config.has(key)) { + throw new Error("Trying to set invalid configuration item: " + key); + } + // set config + this._config.set(key, value); + }; + /** + * get an individual configuration of a class instance + * @method getConfig + * @param key valid configuration key + * @return any configuration value + */ + Store.prototype.getConfig = function (key) { + if (!this._config.has(key)) { + throw new Error("Invalid configuration item requested: " + key); + } + return this._config.get(key); + }; + Object.defineProperty(Store.prototype, "placeholder", { + /** + * get the placeholder for a class instance + * @method placeholder + * @return {HTMLElement|null} + */ + get: function () { + return this._placeholder; + }, + /** + * set the placeholder for a class instance + * @method placeholder + * @param {HTMLElement} placeholder + * @return {void} + */ + set: function (placeholder) { + if (!(placeholder instanceof HTMLElement) && placeholder !== null) { + throw new Error('A placeholder must be an html element or null.'); + } + this._placeholder = placeholder; + }, + enumerable: true, + configurable: true + }); + /** + * set an data entry + * @method setData + * @param {string} key + * @param {any} value + * @return {void} + */ + Store.prototype.setData = function (key, value) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + this._data.set(key, value); + }; + /** + * get an data entry + * @method getData + * @param {string} key an existing key + * @return {any} + */ + Store.prototype.getData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.get(key); + }; + /** + * delete an data entry + * @method deleteData + * @param {string} key an existing key + * @return {boolean} + */ + Store.prototype.deleteData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.delete(key); + }; + return Store; +}()); +/** + * @param {HTMLElement} sortableElement + * @returns {Class: Store} + */ +var store = (function (sortableElement) { + // if sortableElement is wrong type + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('Please provide a sortable to the store function.'); + } + // create new instance if not avilable + if (!stores.has(sortableElement)) { + stores.set(sortableElement, new Store()); + } + // return instance + return stores.get(sortableElement); +}); + +/** + * @param {Array|HTMLElement} element + * @param {Function} callback + * @param {string} event + */ +function addEventListener(element, eventName, callback) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addEventListener(element[i], eventName, callback); + } + return; + } + element.addEventListener(eventName, callback); + store(element).setData("event" + eventName, callback); +} +/** + * @param {Array|HTMLElement} element + * @param {string} eventName + */ +function removeEventListener(element, eventName) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeEventListener(element[i], eventName); + } + return; + } + element.removeEventListener(eventName, store(element).getData("event" + eventName)); + store(element).deleteData("event" + eventName); +} + +/** + * @param {Array|HTMLElement} element + * @param {string} attribute + * @param {string} value + */ +function addAttribute(element, attribute, value) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addAttribute(element[i], attribute, value); + } + return; + } + element.setAttribute(attribute, value); +} +/** + * @param {Array|HTMLElement} element + * @param {string} attribute + */ +function removeAttribute(element, attribute) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeAttribute(element[i], attribute); + } + return; + } + element.removeAttribute(attribute); +} + +/** + * @param {HTMLElement} element + * @returns {Object} + */ +var _offset = (function (element) { + if (!element.parentElement || element.getClientRects().length === 0) { + throw new Error('target element must be part of the dom'); + } + var rect = element.getClientRects()[0]; + return { + left: rect.left + window.pageXOffset, + right: rect.right + window.pageXOffset, + top: rect.top + window.pageYOffset, + bottom: rect.bottom + window.pageYOffset + }; +}); + +/** + * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed + * @param {Function} func to debounce + * @param {number} time to wait before calling function with latest arguments, 0 - no debounce + * @returns {function} - debounced function + */ +var _debounce = (function (func, wait) { + if (wait === void 0) { wait = 0; } + var timeout; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + clearTimeout(timeout); + timeout = setTimeout(function () { + func.apply(void 0, args); + }, wait); + }; +}); + +/* eslint-env browser */ +/** + * Get position of the element relatively to its sibling elements + * @param {HTMLElement} element + * @returns {number} + */ +var _index = (function (element, elementList) { + if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) { + throw new Error('You must provide an element and a list of elements.'); + } + return Array.from(elementList).indexOf(element); +}); + +/* eslint-env browser */ +/** + * Test whether element is in DOM + * @param {HTMLElement} element + * @returns {boolean} + */ +var isInDom = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('Element is not a node element.'); + } + return element.parentNode !== null; +}); + +/* eslint-env browser */ +/** + * Insert node before or after target + * @param {HTMLElement} referenceNode - reference element + * @param {HTMLElement} newElement - element to be inserted + * @param {String} position - insert before or after reference element + */ +var insertNode = function (referenceNode, newElement, position) { + if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) { + throw new Error('target and element must be a node'); + } + referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling)); +}; +/** + * Insert before target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ +var insertBefore = function (target, element) { return insertNode(target, element, 'before'); }; +/** + * Insert after target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ +var insertAfter = function (target, element) { return insertNode(target, element, 'after'); }; + +/* eslint-env browser */ +/** + * Filter only wanted nodes + * @param {HTMLElement} sortableContainer + * @param {Function} customSerializer + * @returns {Array} + */ +var _serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) { + if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; } + if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; } + // check for valid sortableContainer + if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) { + throw new Error('You need to provide a sortableContainer to be serialized.'); + } + // check for valid serializers + if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') { + throw new Error('You need to provide a valid serializer for items and the container.'); + } + // get options + var options = addData(sortableContainer, 'opts'); + var item = options.items; + // serialize container + var items = _filter(sortableContainer.children, item); + var serializedItems = items.map(function (item) { + return { + parent: sortableContainer, + node: item, + html: item.outerHTML, + index: _index(item, items) + }; + }); + // serialize container + var container = { + node: sortableContainer, + itemCount: serializedItems.length + }; + return { + container: customContainerSerializer(container), + items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); }) + }; +}); + +/* eslint-env browser */ +/** + * create a placeholder element + * @param {HTMLElement} sortableElement a single sortable + * @param {string|undefined} placeholder a string representing an html element + * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder + */ +var _makePlaceholder = (function (sortableElement, placeholder, placeholderClass) { + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + var _a; + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('You must provide a valid element as a sortable.'); + } + // if placeholder is not an element + if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) { + throw new Error('You must provide a valid element as a placeholder or set ot to undefined.'); + } + // if no placeholder element is given + if (placeholder === undefined) { + if (['UL', 'OL'].includes(sortableElement.tagName)) { + placeholder = document.createElement('li'); + } + else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) { + placeholder = document.createElement('tr'); + // set colspan to always all rows, otherwise the item can only be dropped in first column + placeholder.innerHTML = ''; + } + else { + placeholder = document.createElement('div'); + } + } + // add classes to placeholder + if (typeof placeholderClass === 'string') { + (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' ')); + } + return placeholder; +}); + +/* eslint-env browser */ +/** + * Get height of an element including padding + * @param {HTMLElement} element an dom element + */ +var _getElementHeight = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('You must provide a valid dom element'); + } + // get calculated style of element + var style = window.getComputedStyle(element); + // pick applicable properties, convert to int and reduce by adding + return ['height', 'padding-top', 'padding-bottom'] + .map(function (key) { + var int = parseInt(style.getPropertyValue(key), 10); + return isNaN(int) ? 0 : int; + }) + .reduce(function (sum, value) { return sum + value; }); +}); + +/* eslint-env browser */ +/** + * get handle or return item + * @param {Array} items + * @param {string} selector + */ +var _getHandles = (function (items, selector) { + if (!(items instanceof Array)) { + throw new Error('You must provide a Array of HTMLElements to be filtered.'); + } + if (typeof selector !== 'string') { + return items; + } + return items + // remove items without handle from array + .filter(function (item) { + return item.querySelector(selector) instanceof HTMLElement || + (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement); + }) + // replace item with handle in array + .map(function (item) { + return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector)); + }); +}); + +/** + * @param {Event} event + * @returns {HTMLElement} + */ +var getEventTarget = (function (event) { + return (event.composedPath && event.composedPath()[0]) || event.target; +}); + +/* eslint-env browser */ +/** + * defaultDragImage returns the current item as dragged image + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {object} elementOffset - an object with the offsets top, left, right & bottom + * @param {Event} event - the original drag event object + * @return {object} with element, posX and posY properties + */ +var defaultDragImage = function (draggedElement, elementOffset, event) { + return { + element: draggedElement, + posX: event.pageX - elementOffset.left, + posY: event.pageY - elementOffset.top + }; +}; +/** + * attaches an element as the drag image to an event + * @param {Event} event - the original drag event object + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {Function} customDragImage - function to create a custom dragImage + * @return void + */ +var setDragImage = (function (event, draggedElement, customDragImage) { + // check if event is provided + if (!(event instanceof Event)) { + throw new Error('setDragImage requires a DragEvent as the first argument.'); + } + // check if draggedElement is provided + if (!(draggedElement instanceof HTMLElement)) { + throw new Error('setDragImage requires the dragged element as the second argument.'); + } + // set default function of none provided + if (!customDragImage) { + customDragImage = defaultDragImage; + } + // check if setDragImage method is available + if (event.dataTransfer && event.dataTransfer.setDragImage) { + // get the elements offset + var elementOffset = _offset(draggedElement); + // get the dragImage + var dragImage = customDragImage(draggedElement, elementOffset, event); + // check if custom function returns correct values + if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') { + throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].'); + } + // needs to be set for HTML5 drag & drop to work + event.dataTransfer.effectAllowed = 'copyMove'; + // Firefox requires it to use the event target's id for the data + event.dataTransfer.setData('text/plain', getEventTarget(event).id); + // set the drag image on the event + event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY); + } +}); + +/** + * Check if curList accepts items from destList + * @param {sortable} destination the container an item is move to + * @param {sortable} origin the container an item comes from + */ +var _listsConnected = (function (destination, origin) { + // check if valid sortable + if (destination.isSortable === true) { + var acceptFrom = store(destination).getConfig('acceptFrom'); + // check if acceptFrom is valid + if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') { + throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.'); + } + if (acceptFrom !== null) { + return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) { + return sel.length > 0 && origin.matches(sel); + }).length > 0; + } + // drop in same list + if (destination === origin) { + return true; + } + // check if lists are connected with connectWith + if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) { + return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith'); + } + } + return false; +}); + +/** + * default configurations + */ +var defaultConfiguration = { + items: null, + // deprecated + connectWith: null, + // deprecated + disableIEFix: null, + acceptFrom: null, + copy: false, + placeholder: null, + placeholderClass: 'sortable-placeholder', + draggingClass: 'sortable-dragging', + hoverClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null +}; + +/** + * make sure a function is only called once within the given amount of time + * @param {Function} fn the function to throttle + * @param {number} threshold time limit for throttling + */ +// must use function to keep this context +function _throttle (fn, threshold) { + var _this = this; + if (threshold === void 0) { threshold = 250; } + // check function + if (typeof fn !== 'function') { + throw new Error('You must provide a function as the first argument for throttle.'); + } + // check threshold + if (typeof threshold !== 'number') { + throw new Error('You must provide a number as the second argument for throttle.'); + } + var lastEventTimestamp = null; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var now = Date.now(); + if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) { + lastEventTimestamp = now; + fn.apply(_this, args); + } + }; +} + +/* eslint-env browser */ +/** + * enable or disable hoverClass on mouseenter/leave if container Items + * @param {sortable} sortableContainer a valid sortableContainer + * @param {boolean} enable enable or disable event + */ +// export default (sortableContainer: sortable, enable: boolean) => { +var enableHoverClass = (function (sortableContainer, enable) { + if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') { + var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' '); + // add class on hover + if (enable === true) { + addEventListener(sortableContainer, 'mousemove', _throttle(function (event) { + // check of no mouse button was pressed when mousemove started == no drag + if (event.buttons === 0) { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a, _b; + if (item !== event.target) { + (_a = item.classList).remove.apply(_a, hoverClasses_1); + } + else { + (_b = item.classList).add.apply(_b, hoverClasses_1); + } + }); + } + }, store(sortableContainer).getConfig('throttleTime'))); + // remove class on leave + addEventListener(sortableContainer, 'mouseleave', function () { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a; + (_a = item.classList).remove.apply(_a, hoverClasses_1); + }); + }); + // remove events + } + else { + removeEventListener(sortableContainer, 'mousemove'); + removeEventListener(sortableContainer, 'mouseleave'); + } + } +}); + +/* eslint-env browser */ +/* + * variables global to the plugin + */ +var dragging; +var draggingHeight; +/* + * Keeps track of the initialy selected list, where 'dragstart' event was triggered + * It allows us to move the data in between individual Sortable List instances + */ +// Origin List - data from before any item was changed +var originContainer; +var originIndex; +var originElementIndex; +var originItemsBeforeUpdate; +// Previous Sortable Container - we dispatch as sortenter event when a +// dragged item enters a sortableContainer for the first time +var previousContainer; +// Destination List - data from before any item was changed +var destinationItemsBeforeUpdate; +/** + * remove event handlers from items + * @param {Array|NodeList} items + */ +var _removeItemEvents = function (items) { + removeEventListener(items, 'dragstart'); + removeEventListener(items, 'dragend'); + removeEventListener(items, 'dragover'); + removeEventListener(items, 'dragenter'); + removeEventListener(items, 'drop'); + removeEventListener(items, 'mouseenter'); + removeEventListener(items, 'mouseleave'); +}; +/** + * _getDragging returns the current element to drag or + * a copy of the element. + * Is Copy Active for sortable + * @param {HTMLElement} draggedItem - the item that the user drags + * @param {HTMLElement} sortable a single sortable + */ +var _getDragging = function (draggedItem, sortable) { + var ditem = draggedItem; + if (store(sortable).getConfig('copy') === true) { + ditem = draggedItem.cloneNode(true); + addAttribute(ditem, 'aria-copied', 'true'); + draggedItem.parentElement.appendChild(ditem); + ditem.style.display = 'none'; + ditem.oldDisplay = draggedItem.style.display; + } + return ditem; +}; +/** + * Remove data from sortable + * @param {HTMLElement} sortable a single sortable + */ +var _removeSortableData = function (sortable) { + removeData(sortable); + removeAttribute(sortable, 'aria-dropeffect'); +}; +/** + * Remove data from items + * @param {Array|HTMLElement} items + */ +var _removeItemData = function (items) { + removeAttribute(items, 'aria-grabbed'); + removeAttribute(items, 'aria-copied'); + removeAttribute(items, 'draggable'); + removeAttribute(items, 'role'); +}; +/** + * find sortable from element. travels up parent element until found or null. + * @param {HTMLElement} element a single sortable + * @param {Event} event - the current event. We need to pass it to be able to + * find Sortable whith shadowRoot (document fragment has no parent) + */ +function findSortable(element, event) { + if (event.composedPath) { + return event.composedPath().find(function (el) { return el.isSortable; }); + } + while (element.isSortable !== true) { + element = element.parentElement; + } + return element; +} +/** + * Dragging event is on the sortable element. finds the top child that + * contains the element. + * @param {HTMLElement} sortableElement a single sortable + * @param {HTMLElement} element is that being dragged + */ +function findDragElement(sortableElement, element) { + var options = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, options.items); + var itemlist = items.filter(function (ele) { + return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element)); + }); + return itemlist.length > 0 ? itemlist[0] : element; +} +/** + * Destroy the sortable + * @param {HTMLElement} sortableElement a single sortable + */ +var _destroySortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts') || {}; + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + _removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + _removeItemEvents(items); + _removeItemData(items); +}; +/** + * Enable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ +var _enableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'move'); + addData(sortableElement, '_disabled', 'false'); + addAttribute(handles, 'draggable', 'true'); + // @todo: remove this fix + // IE FIX for ghost + // can be disabled as it has the side effect that other events + // (e.g. click) will be ignored + if (opts.disableIEFix === false) { + var spanEl = (document || window.document).createElement('span'); + if (typeof spanEl.dragDrop === 'function') { + addEventListener(handles, 'mousedown', function () { + if (items.indexOf(this) !== -1) { + this.dragDrop(); + } + else { + var parent = this.parentElement; + while (items.indexOf(parent) === -1) { + parent = parent.parentElement; + } + parent.dragDrop(); + } + }); + } + } +}; +/** + * Disable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ +var _disableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'none'); + addData(sortableElement, '_disabled', 'true'); + addAttribute(handles, 'draggable', 'false'); + removeEventListener(handles, 'mousedown'); +}; +/** + * Reload the sortable + * @param {HTMLElement} sortableElement a single sortable + * @description events need to be removed to not be double bound + */ +var _reloadSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addData(sortableElement, '_disabled', 'false'); + // remove event handlers from items + _removeItemEvents(items); + removeEventListener(handles, 'mousedown'); + // remove event handlers from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); +}; +/** + * Public sortable object + * @param {Array|NodeList} sortableElements + * @param {object|string} options|method + */ +function sortable(sortableElements, options) { + // get method string to see if a method is called + var method = String(options); + options = options || {}; + // check if the user provided a selector instead of an element + if (typeof sortableElements === 'string') { + sortableElements = document.querySelectorAll(sortableElements); + } + // if the user provided an element, return it in an array to keep the return value consistant + if (sortableElements instanceof HTMLElement) { + sortableElements = [sortableElements]; + } + sortableElements = Array.prototype.slice.call(sortableElements); + if (/serialize/.test(method)) { + return sortableElements.map(function (sortableContainer) { + var opts = addData(sortableContainer, 'opts'); + return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer); + }); + } + sortableElements.forEach(function (sortableElement) { + if (/enable|disable|destroy/.test(method)) { + return sortable[method](sortableElement); + } + // log deprecation + ['connectWith', 'disableIEFix'].forEach(function (configKey) { + if (options.hasOwnProperty(configKey) && options[configKey] !== null) { + console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating."); + } + }); + // merge options with default options + options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options); + // init data store for sortable + store(sortableElement).config = options; + // set options on sortable + addData(sortableElement, 'opts', options); + // property to define as sortable + sortableElement.isSortable = true; + // reset sortable + _reloadSortable(sortableElement); + // initialize + var listItems = _filter(sortableElement.children, options.items); + // create element if user defined a placeholder element as a string + var customPlaceholder; + if (options.placeholder !== null && options.placeholder !== undefined) { + var tempContainer = document.createElement(sortableElement.tagName); + if (options.placeholder instanceof HTMLElement) { + tempContainer.appendChild(options.placeholder); + } + else { + tempContainer.innerHTML = options.placeholder; + } + customPlaceholder = tempContainer.children[0]; + } + // add placeholder + store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass); + addData(sortableElement, 'items', options.items); + if (options.acceptFrom) { + addData(sortableElement, 'acceptFrom', options.acceptFrom); + } + else if (options.connectWith) { + addData(sortableElement, 'connectWith', options.connectWith); + } + _enableSortable(sortableElement); + addAttribute(listItems, 'role', 'option'); + addAttribute(listItems, 'aria-grabbed', 'false'); + // enable hover class + enableHoverClass(sortableElement, true); + /* + Handle drag events on draggable items + Handle is set at the sortableElement level as it will bubble up + from the item + */ + addEventListener(sortableElement, 'dragstart', function (e) { + // ignore dragstart events + var target = getEventTarget(e); + if (target.isSortable === true) { + return; + } + e.stopImmediatePropagation(); + if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') { + return; + } + var sortableContainer = findSortable(target, e); + var dragItem = findDragElement(sortableContainer, target); + // grab values + originItemsBeforeUpdate = _filter(sortableContainer.children, options.items); + originIndex = originItemsBeforeUpdate.indexOf(dragItem); + originElementIndex = _index(dragItem, sortableContainer.children); + originContainer = sortableContainer; + // add transparent clone or other ghost to cursor + setDragImage(e, dragItem, options.customDragImage); + // cache selsection & add attr for dragging + draggingHeight = _getElementHeight(dragItem); + dragItem.classList.add(options.draggingClass); + dragging = _getDragging(dragItem, sortableContainer); + addAttribute(dragging, 'aria-grabbed', 'true'); + // dispatch sortstart event on each element in group + sortableContainer.dispatchEvent(new CustomEvent('sortstart', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging, + originalTarget: target + } + })); + }); + /* + We are capturing targetSortable before modifications with 'dragenter' event + */ + addEventListener(sortableElement, 'dragenter', function (e) { + var target = getEventTarget(e); + var sortableContainer = findSortable(target, e); + if (sortableContainer && sortableContainer !== previousContainer) { + destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items')) + .filter(function (item) { return item !== store(sortableElement).placeholder; }); + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + } + previousContainer = sortableContainer; + }); + /* + * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend + * Fires each time dragEvent end, or ESC pressed + * We are using it to clean up any draggable elements and placeholders + */ + addEventListener(sortableElement, 'dragend', function (e) { + if (!dragging) { + return; + } + dragging.classList.remove(options.draggingClass); + addAttribute(dragging, 'aria-grabbed', 'false'); + if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') { + dragging.remove(); + } + dragging.style.display = dragging.oldDisplay; + delete dragging.oldDisplay; + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; }) + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + .filter(isInDom)[0]; + if (visiblePlaceholder) { + visiblePlaceholder.remove(); + } + // dispatch sortstart event on each element in group + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + previousContainer = null; + dragging = null; + draggingHeight = null; + }); + /* + * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop + * Fires when valid drop target area is hit + */ + addEventListener(sortableElement, 'drop', function (e) { + if (!_listsConnected(sortableElement, dragging.parentElement)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + addData(dragging, 'dropped', 'true'); + // get the one placeholder that is currently visible + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { + return data.placeholder; + }) + // filter only HTMLElements + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + // filter only elements in DOM + .filter(isInDom)[0]; + // attach element after placeholder + insertAfter(visiblePlaceholder, dragging); + // remove placeholder from dom + visiblePlaceholder.remove(); + /* + * Fires Custom Event - 'sortstop' + */ + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + var placeholder = store(sortableElement).placeholder; + var originItems = _filter(originContainer.children, options.items) + .filter(function (item) { return item !== placeholder; }); + var destinationContainer = this.isSortable === true ? this : this.parentElement; + var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items')) + .filter(function (item) { return item !== placeholder; }); + var destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = _index(dragging, destinationItems); + /* + * When a list item changed container lists or index within a list + * Fires Custom Event - 'sortupdate' + */ + if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { + sortableElement.dispatchEvent(new CustomEvent('sortupdate', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer, + itemsBeforeUpdate: originItemsBeforeUpdate, + items: originItems + }, + destination: { + index: destinationIndex, + elementIndex: destinationElementIndex, + container: destinationContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate, + items: destinationItems + }, + item: dragging + } + })); + } + }); + var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + } + // if element the draggedItem is dragged onto is within the array of all elements in list + // (not only items, but also disabled, etc.) + if (Array.from(sortableElement.children).indexOf(element) > -1) { + var thisHeight = _getElementHeight(element); + var placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = _index(element, element.parentElement.children); + // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering + if (thisHeight > draggingHeight) { + // Dead zone? + var deadZone = thisHeight - draggingHeight; + var offsetTop = _offset(element).top; + if (placeholderIndex < thisIndex && pageY < offsetTop) { + return; + } + if (placeholderIndex > thisIndex && + pageY > offsetTop + thisHeight - deadZone) { + return; + } + } + if (dragging.oldDisplay === undefined) { + dragging.oldDisplay = dragging.style.display; + } + if (dragging.style.display !== 'none') { + dragging.style.display = 'none'; + } + // To avoid flicker, determine where to position the placeholder + // based on where the mouse pointer is relative to the elements + // vertical center. + var placeAfter = false; + try { + var elementMiddle = _offset(element).top + element.offsetHeight / 2; + placeAfter = pageY >= elementMiddle; + } + catch (e) { + placeAfter = placeholderIndex < thisIndex; + } + if (placeAfter) { + insertAfter(element, store(sortableElement).placeholder); + } + else { + insertBefore(element, store(sortableElement).placeholder); + } + // get placeholders from all stores & remove all but current one + Array.from(stores.values()) + // remove empty values + .filter(function (data) { return data.placeholder !== undefined; }) + // foreach placeholder in array if outside of current sorableContainer -> remove from DOM + .forEach(function (data) { + if (data.placeholder !== store(sortableElement).placeholder) { + data.placeholder.remove(); + } + }); + } + else { + // get all placeholders from store + var placeholders = Array.from(stores.values()) + .filter(function (data) { return data.placeholder !== undefined; }) + .map(function (data) { + return data.placeholder; + }); + // check if element is not in placeholders + if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) { + placeholders.forEach(function (element) { return element.remove(); }); + element.appendChild(store(sortableElement).placeholder); + } + } + }, options.debounce); + // Handle dragover and dragenter events on draggable items + var onDragOverEnter = function (e) { + var element = e.target; + var sortableElement = element.isSortable === true ? element : findSortable(element, e); + element = findDragElement(sortableElement, element); + if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') { + return; + } + var options = addData(sortableElement, 'opts'); + if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) { + return; + } + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move'; + debouncedDragOverEnter(sortableElement, element, e.pageY); + }; + addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter); + addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter); + }); + return sortableElements; +} +sortable.destroy = function (sortableElement) { + _destroySortable(sortableElement); +}; +sortable.enable = function (sortableElement) { + _enableSortable(sortableElement); +}; +sortable.disable = function (sortableElement) { + _disableSortable(sortableElement); +}; +/* START.TESTS_ONLY */ +sortable.__testing = { + // add internal methods here for testing purposes + _data: addData, + _removeItemEvents: _removeItemEvents, + _removeItemData: _removeItemData, + _removeSortableData: _removeSortableData +}; + +module.exports = sortable; diff --git a/dist/html5sortable.es.js b/dist/html5sortable.es.js new file mode 100644 index 00000000..efccb584 --- /dev/null +++ b/dist/html5sortable.es.js @@ -0,0 +1,1209 @@ +/* + * HTML5Sortable package + * https://github.com/lukasoppermann/html5sortable + * + * Maintained by Lukas Oppermann + * + * Released under the MIT license. + */ +/** + * Get or set data on element + * @param {HTMLElement} element + * @param {string} key + * @param {any} value + * @return {*} + */ +function addData(element, key, value) { + if (value === undefined) { + return element && element.h5s && element.h5s.data && element.h5s.data[key]; + } + else { + element.h5s = element.h5s || {}; + element.h5s.data = element.h5s.data || {}; + element.h5s.data[key] = value; + } +} +/** + * Remove data from element + * @param {HTMLElement} element + */ +function removeData(element) { + if (element.h5s) { + delete element.h5s.data; + } +} + +/* eslint-env browser */ +/** + * Filter only wanted nodes + * @param {NodeList|HTMLCollection|Array} nodes + * @param {String} selector + * @returns {Array} + */ +var _filter = (function (nodes, selector) { + if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) { + throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.'); + } + if (typeof selector !== 'string') { + return Array.from(nodes); + } + return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); }); +}); + +/* eslint-env browser */ +var stores = new Map(); +/** + * Stores data & configurations per Sortable + * @param {Object} config + */ +var Store = /** @class */ (function () { + function Store() { + this._config = new Map(); // eslint-disable-line no-undef + this._placeholder = undefined; // eslint-disable-line no-undef + this._data = new Map(); // eslint-disable-line no-undef + } + Object.defineProperty(Store.prototype, "config", { + /** + * get the configuration map of a class instance + * @method config + * @return {object} + */ + get: function () { + // transform Map to object + var config = {}; + this._config.forEach(function (value, key) { + config[key] = value; + }); + // return object + return config; + }, + /** + * set the configuration of a class instance + * @method config + * @param {object} config object of configurations + */ + set: function (config) { + if (typeof config !== 'object') { + throw new Error('You must provide a valid configuration object to the config setter.'); + } + // combine config with default + var mergedConfig = Object.assign({}, config); + // add config to map + this._config = new Map(Object.entries(mergedConfig)); + }, + enumerable: true, + configurable: true + }); + /** + * set individual configuration of a class instance + * @method setConfig + * @param key valid configuration key + * @param value any value + * @return void + */ + Store.prototype.setConfig = function (key, value) { + if (!this._config.has(key)) { + throw new Error("Trying to set invalid configuration item: " + key); + } + // set config + this._config.set(key, value); + }; + /** + * get an individual configuration of a class instance + * @method getConfig + * @param key valid configuration key + * @return any configuration value + */ + Store.prototype.getConfig = function (key) { + if (!this._config.has(key)) { + throw new Error("Invalid configuration item requested: " + key); + } + return this._config.get(key); + }; + Object.defineProperty(Store.prototype, "placeholder", { + /** + * get the placeholder for a class instance + * @method placeholder + * @return {HTMLElement|null} + */ + get: function () { + return this._placeholder; + }, + /** + * set the placeholder for a class instance + * @method placeholder + * @param {HTMLElement} placeholder + * @return {void} + */ + set: function (placeholder) { + if (!(placeholder instanceof HTMLElement) && placeholder !== null) { + throw new Error('A placeholder must be an html element or null.'); + } + this._placeholder = placeholder; + }, + enumerable: true, + configurable: true + }); + /** + * set an data entry + * @method setData + * @param {string} key + * @param {any} value + * @return {void} + */ + Store.prototype.setData = function (key, value) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + this._data.set(key, value); + }; + /** + * get an data entry + * @method getData + * @param {string} key an existing key + * @return {any} + */ + Store.prototype.getData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.get(key); + }; + /** + * delete an data entry + * @method deleteData + * @param {string} key an existing key + * @return {boolean} + */ + Store.prototype.deleteData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.delete(key); + }; + return Store; +}()); +/** + * @param {HTMLElement} sortableElement + * @returns {Class: Store} + */ +var store = (function (sortableElement) { + // if sortableElement is wrong type + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('Please provide a sortable to the store function.'); + } + // create new instance if not avilable + if (!stores.has(sortableElement)) { + stores.set(sortableElement, new Store()); + } + // return instance + return stores.get(sortableElement); +}); + +/** + * @param {Array|HTMLElement} element + * @param {Function} callback + * @param {string} event + */ +function addEventListener(element, eventName, callback) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addEventListener(element[i], eventName, callback); + } + return; + } + element.addEventListener(eventName, callback); + store(element).setData("event" + eventName, callback); +} +/** + * @param {Array|HTMLElement} element + * @param {string} eventName + */ +function removeEventListener(element, eventName) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeEventListener(element[i], eventName); + } + return; + } + element.removeEventListener(eventName, store(element).getData("event" + eventName)); + store(element).deleteData("event" + eventName); +} + +/** + * @param {Array|HTMLElement} element + * @param {string} attribute + * @param {string} value + */ +function addAttribute(element, attribute, value) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addAttribute(element[i], attribute, value); + } + return; + } + element.setAttribute(attribute, value); +} +/** + * @param {Array|HTMLElement} element + * @param {string} attribute + */ +function removeAttribute(element, attribute) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeAttribute(element[i], attribute); + } + return; + } + element.removeAttribute(attribute); +} + +/** + * @param {HTMLElement} element + * @returns {Object} + */ +var _offset = (function (element) { + if (!element.parentElement || element.getClientRects().length === 0) { + throw new Error('target element must be part of the dom'); + } + var rect = element.getClientRects()[0]; + return { + left: rect.left + window.pageXOffset, + right: rect.right + window.pageXOffset, + top: rect.top + window.pageYOffset, + bottom: rect.bottom + window.pageYOffset + }; +}); + +/** + * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed + * @param {Function} func to debounce + * @param {number} time to wait before calling function with latest arguments, 0 - no debounce + * @returns {function} - debounced function + */ +var _debounce = (function (func, wait) { + if (wait === void 0) { wait = 0; } + var timeout; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + clearTimeout(timeout); + timeout = setTimeout(function () { + func.apply(void 0, args); + }, wait); + }; +}); + +/* eslint-env browser */ +/** + * Get position of the element relatively to its sibling elements + * @param {HTMLElement} element + * @returns {number} + */ +var _index = (function (element, elementList) { + if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) { + throw new Error('You must provide an element and a list of elements.'); + } + return Array.from(elementList).indexOf(element); +}); + +/* eslint-env browser */ +/** + * Test whether element is in DOM + * @param {HTMLElement} element + * @returns {boolean} + */ +var isInDom = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('Element is not a node element.'); + } + return element.parentNode !== null; +}); + +/* eslint-env browser */ +/** + * Insert node before or after target + * @param {HTMLElement} referenceNode - reference element + * @param {HTMLElement} newElement - element to be inserted + * @param {String} position - insert before or after reference element + */ +var insertNode = function (referenceNode, newElement, position) { + if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) { + throw new Error('target and element must be a node'); + } + referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling)); +}; +/** + * Insert before target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ +var insertBefore = function (target, element) { return insertNode(target, element, 'before'); }; +/** + * Insert after target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ +var insertAfter = function (target, element) { return insertNode(target, element, 'after'); }; + +/* eslint-env browser */ +/** + * Filter only wanted nodes + * @param {HTMLElement} sortableContainer + * @param {Function} customSerializer + * @returns {Array} + */ +var _serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) { + if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; } + if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; } + // check for valid sortableContainer + if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) { + throw new Error('You need to provide a sortableContainer to be serialized.'); + } + // check for valid serializers + if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') { + throw new Error('You need to provide a valid serializer for items and the container.'); + } + // get options + var options = addData(sortableContainer, 'opts'); + var item = options.items; + // serialize container + var items = _filter(sortableContainer.children, item); + var serializedItems = items.map(function (item) { + return { + parent: sortableContainer, + node: item, + html: item.outerHTML, + index: _index(item, items) + }; + }); + // serialize container + var container = { + node: sortableContainer, + itemCount: serializedItems.length + }; + return { + container: customContainerSerializer(container), + items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); }) + }; +}); + +/* eslint-env browser */ +/** + * create a placeholder element + * @param {HTMLElement} sortableElement a single sortable + * @param {string|undefined} placeholder a string representing an html element + * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder + */ +var _makePlaceholder = (function (sortableElement, placeholder, placeholderClass) { + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + var _a; + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('You must provide a valid element as a sortable.'); + } + // if placeholder is not an element + if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) { + throw new Error('You must provide a valid element as a placeholder or set ot to undefined.'); + } + // if no placeholder element is given + if (placeholder === undefined) { + if (['UL', 'OL'].includes(sortableElement.tagName)) { + placeholder = document.createElement('li'); + } + else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) { + placeholder = document.createElement('tr'); + // set colspan to always all rows, otherwise the item can only be dropped in first column + placeholder.innerHTML = ''; + } + else { + placeholder = document.createElement('div'); + } + } + // add classes to placeholder + if (typeof placeholderClass === 'string') { + (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' ')); + } + return placeholder; +}); + +/* eslint-env browser */ +/** + * Get height of an element including padding + * @param {HTMLElement} element an dom element + */ +var _getElementHeight = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('You must provide a valid dom element'); + } + // get calculated style of element + var style = window.getComputedStyle(element); + // pick applicable properties, convert to int and reduce by adding + return ['height', 'padding-top', 'padding-bottom'] + .map(function (key) { + var int = parseInt(style.getPropertyValue(key), 10); + return isNaN(int) ? 0 : int; + }) + .reduce(function (sum, value) { return sum + value; }); +}); + +/* eslint-env browser */ +/** + * get handle or return item + * @param {Array} items + * @param {string} selector + */ +var _getHandles = (function (items, selector) { + if (!(items instanceof Array)) { + throw new Error('You must provide a Array of HTMLElements to be filtered.'); + } + if (typeof selector !== 'string') { + return items; + } + return items + // remove items without handle from array + .filter(function (item) { + return item.querySelector(selector) instanceof HTMLElement || + (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement); + }) + // replace item with handle in array + .map(function (item) { + return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector)); + }); +}); + +/** + * @param {Event} event + * @returns {HTMLElement} + */ +var getEventTarget = (function (event) { + return (event.composedPath && event.composedPath()[0]) || event.target; +}); + +/* eslint-env browser */ +/** + * defaultDragImage returns the current item as dragged image + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {object} elementOffset - an object with the offsets top, left, right & bottom + * @param {Event} event - the original drag event object + * @return {object} with element, posX and posY properties + */ +var defaultDragImage = function (draggedElement, elementOffset, event) { + return { + element: draggedElement, + posX: event.pageX - elementOffset.left, + posY: event.pageY - elementOffset.top + }; +}; +/** + * attaches an element as the drag image to an event + * @param {Event} event - the original drag event object + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {Function} customDragImage - function to create a custom dragImage + * @return void + */ +var setDragImage = (function (event, draggedElement, customDragImage) { + // check if event is provided + if (!(event instanceof Event)) { + throw new Error('setDragImage requires a DragEvent as the first argument.'); + } + // check if draggedElement is provided + if (!(draggedElement instanceof HTMLElement)) { + throw new Error('setDragImage requires the dragged element as the second argument.'); + } + // set default function of none provided + if (!customDragImage) { + customDragImage = defaultDragImage; + } + // check if setDragImage method is available + if (event.dataTransfer && event.dataTransfer.setDragImage) { + // get the elements offset + var elementOffset = _offset(draggedElement); + // get the dragImage + var dragImage = customDragImage(draggedElement, elementOffset, event); + // check if custom function returns correct values + if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') { + throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].'); + } + // needs to be set for HTML5 drag & drop to work + event.dataTransfer.effectAllowed = 'copyMove'; + // Firefox requires it to use the event target's id for the data + event.dataTransfer.setData('text/plain', getEventTarget(event).id); + // set the drag image on the event + event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY); + } +}); + +/** + * Check if curList accepts items from destList + * @param {sortable} destination the container an item is move to + * @param {sortable} origin the container an item comes from + */ +var _listsConnected = (function (destination, origin) { + // check if valid sortable + if (destination.isSortable === true) { + var acceptFrom = store(destination).getConfig('acceptFrom'); + // check if acceptFrom is valid + if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') { + throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.'); + } + if (acceptFrom !== null) { + return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) { + return sel.length > 0 && origin.matches(sel); + }).length > 0; + } + // drop in same list + if (destination === origin) { + return true; + } + // check if lists are connected with connectWith + if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) { + return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith'); + } + } + return false; +}); + +/** + * default configurations + */ +var defaultConfiguration = { + items: null, + // deprecated + connectWith: null, + // deprecated + disableIEFix: null, + acceptFrom: null, + copy: false, + placeholder: null, + placeholderClass: 'sortable-placeholder', + draggingClass: 'sortable-dragging', + hoverClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null +}; + +/** + * make sure a function is only called once within the given amount of time + * @param {Function} fn the function to throttle + * @param {number} threshold time limit for throttling + */ +// must use function to keep this context +function _throttle (fn, threshold) { + var _this = this; + if (threshold === void 0) { threshold = 250; } + // check function + if (typeof fn !== 'function') { + throw new Error('You must provide a function as the first argument for throttle.'); + } + // check threshold + if (typeof threshold !== 'number') { + throw new Error('You must provide a number as the second argument for throttle.'); + } + var lastEventTimestamp = null; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var now = Date.now(); + if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) { + lastEventTimestamp = now; + fn.apply(_this, args); + } + }; +} + +/* eslint-env browser */ +/** + * enable or disable hoverClass on mouseenter/leave if container Items + * @param {sortable} sortableContainer a valid sortableContainer + * @param {boolean} enable enable or disable event + */ +// export default (sortableContainer: sortable, enable: boolean) => { +var enableHoverClass = (function (sortableContainer, enable) { + if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') { + var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' '); + // add class on hover + if (enable === true) { + addEventListener(sortableContainer, 'mousemove', _throttle(function (event) { + // check of no mouse button was pressed when mousemove started == no drag + if (event.buttons === 0) { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a, _b; + if (item !== event.target) { + (_a = item.classList).remove.apply(_a, hoverClasses_1); + } + else { + (_b = item.classList).add.apply(_b, hoverClasses_1); + } + }); + } + }, store(sortableContainer).getConfig('throttleTime'))); + // remove class on leave + addEventListener(sortableContainer, 'mouseleave', function () { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a; + (_a = item.classList).remove.apply(_a, hoverClasses_1); + }); + }); + // remove events + } + else { + removeEventListener(sortableContainer, 'mousemove'); + removeEventListener(sortableContainer, 'mouseleave'); + } + } +}); + +/* eslint-env browser */ +/* + * variables global to the plugin + */ +var dragging; +var draggingHeight; +/* + * Keeps track of the initialy selected list, where 'dragstart' event was triggered + * It allows us to move the data in between individual Sortable List instances + */ +// Origin List - data from before any item was changed +var originContainer; +var originIndex; +var originElementIndex; +var originItemsBeforeUpdate; +// Previous Sortable Container - we dispatch as sortenter event when a +// dragged item enters a sortableContainer for the first time +var previousContainer; +// Destination List - data from before any item was changed +var destinationItemsBeforeUpdate; +/** + * remove event handlers from items + * @param {Array|NodeList} items + */ +var _removeItemEvents = function (items) { + removeEventListener(items, 'dragstart'); + removeEventListener(items, 'dragend'); + removeEventListener(items, 'dragover'); + removeEventListener(items, 'dragenter'); + removeEventListener(items, 'drop'); + removeEventListener(items, 'mouseenter'); + removeEventListener(items, 'mouseleave'); +}; +/** + * _getDragging returns the current element to drag or + * a copy of the element. + * Is Copy Active for sortable + * @param {HTMLElement} draggedItem - the item that the user drags + * @param {HTMLElement} sortable a single sortable + */ +var _getDragging = function (draggedItem, sortable) { + var ditem = draggedItem; + if (store(sortable).getConfig('copy') === true) { + ditem = draggedItem.cloneNode(true); + addAttribute(ditem, 'aria-copied', 'true'); + draggedItem.parentElement.appendChild(ditem); + ditem.style.display = 'none'; + ditem.oldDisplay = draggedItem.style.display; + } + return ditem; +}; +/** + * Remove data from sortable + * @param {HTMLElement} sortable a single sortable + */ +var _removeSortableData = function (sortable) { + removeData(sortable); + removeAttribute(sortable, 'aria-dropeffect'); +}; +/** + * Remove data from items + * @param {Array|HTMLElement} items + */ +var _removeItemData = function (items) { + removeAttribute(items, 'aria-grabbed'); + removeAttribute(items, 'aria-copied'); + removeAttribute(items, 'draggable'); + removeAttribute(items, 'role'); +}; +/** + * find sortable from element. travels up parent element until found or null. + * @param {HTMLElement} element a single sortable + * @param {Event} event - the current event. We need to pass it to be able to + * find Sortable whith shadowRoot (document fragment has no parent) + */ +function findSortable(element, event) { + if (event.composedPath) { + return event.composedPath().find(function (el) { return el.isSortable; }); + } + while (element.isSortable !== true) { + element = element.parentElement; + } + return element; +} +/** + * Dragging event is on the sortable element. finds the top child that + * contains the element. + * @param {HTMLElement} sortableElement a single sortable + * @param {HTMLElement} element is that being dragged + */ +function findDragElement(sortableElement, element) { + var options = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, options.items); + var itemlist = items.filter(function (ele) { + return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element)); + }); + return itemlist.length > 0 ? itemlist[0] : element; +} +/** + * Destroy the sortable + * @param {HTMLElement} sortableElement a single sortable + */ +var _destroySortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts') || {}; + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + _removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + _removeItemEvents(items); + _removeItemData(items); +}; +/** + * Enable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ +var _enableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'move'); + addData(sortableElement, '_disabled', 'false'); + addAttribute(handles, 'draggable', 'true'); + // @todo: remove this fix + // IE FIX for ghost + // can be disabled as it has the side effect that other events + // (e.g. click) will be ignored + if (opts.disableIEFix === false) { + var spanEl = (document || window.document).createElement('span'); + if (typeof spanEl.dragDrop === 'function') { + addEventListener(handles, 'mousedown', function () { + if (items.indexOf(this) !== -1) { + this.dragDrop(); + } + else { + var parent = this.parentElement; + while (items.indexOf(parent) === -1) { + parent = parent.parentElement; + } + parent.dragDrop(); + } + }); + } + } +}; +/** + * Disable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ +var _disableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'none'); + addData(sortableElement, '_disabled', 'true'); + addAttribute(handles, 'draggable', 'false'); + removeEventListener(handles, 'mousedown'); +}; +/** + * Reload the sortable + * @param {HTMLElement} sortableElement a single sortable + * @description events need to be removed to not be double bound + */ +var _reloadSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addData(sortableElement, '_disabled', 'false'); + // remove event handlers from items + _removeItemEvents(items); + removeEventListener(handles, 'mousedown'); + // remove event handlers from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); +}; +/** + * Public sortable object + * @param {Array|NodeList} sortableElements + * @param {object|string} options|method + */ +function sortable(sortableElements, options) { + // get method string to see if a method is called + var method = String(options); + options = options || {}; + // check if the user provided a selector instead of an element + if (typeof sortableElements === 'string') { + sortableElements = document.querySelectorAll(sortableElements); + } + // if the user provided an element, return it in an array to keep the return value consistant + if (sortableElements instanceof HTMLElement) { + sortableElements = [sortableElements]; + } + sortableElements = Array.prototype.slice.call(sortableElements); + if (/serialize/.test(method)) { + return sortableElements.map(function (sortableContainer) { + var opts = addData(sortableContainer, 'opts'); + return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer); + }); + } + sortableElements.forEach(function (sortableElement) { + if (/enable|disable|destroy/.test(method)) { + return sortable[method](sortableElement); + } + // log deprecation + ['connectWith', 'disableIEFix'].forEach(function (configKey) { + if (options.hasOwnProperty(configKey) && options[configKey] !== null) { + console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating."); + } + }); + // merge options with default options + options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options); + // init data store for sortable + store(sortableElement).config = options; + // set options on sortable + addData(sortableElement, 'opts', options); + // property to define as sortable + sortableElement.isSortable = true; + // reset sortable + _reloadSortable(sortableElement); + // initialize + var listItems = _filter(sortableElement.children, options.items); + // create element if user defined a placeholder element as a string + var customPlaceholder; + if (options.placeholder !== null && options.placeholder !== undefined) { + var tempContainer = document.createElement(sortableElement.tagName); + if (options.placeholder instanceof HTMLElement) { + tempContainer.appendChild(options.placeholder); + } + else { + tempContainer.innerHTML = options.placeholder; + } + customPlaceholder = tempContainer.children[0]; + } + // add placeholder + store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass); + addData(sortableElement, 'items', options.items); + if (options.acceptFrom) { + addData(sortableElement, 'acceptFrom', options.acceptFrom); + } + else if (options.connectWith) { + addData(sortableElement, 'connectWith', options.connectWith); + } + _enableSortable(sortableElement); + addAttribute(listItems, 'role', 'option'); + addAttribute(listItems, 'aria-grabbed', 'false'); + // enable hover class + enableHoverClass(sortableElement, true); + /* + Handle drag events on draggable items + Handle is set at the sortableElement level as it will bubble up + from the item + */ + addEventListener(sortableElement, 'dragstart', function (e) { + // ignore dragstart events + var target = getEventTarget(e); + if (target.isSortable === true) { + return; + } + e.stopImmediatePropagation(); + if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') { + return; + } + var sortableContainer = findSortable(target, e); + var dragItem = findDragElement(sortableContainer, target); + // grab values + originItemsBeforeUpdate = _filter(sortableContainer.children, options.items); + originIndex = originItemsBeforeUpdate.indexOf(dragItem); + originElementIndex = _index(dragItem, sortableContainer.children); + originContainer = sortableContainer; + // add transparent clone or other ghost to cursor + setDragImage(e, dragItem, options.customDragImage); + // cache selsection & add attr for dragging + draggingHeight = _getElementHeight(dragItem); + dragItem.classList.add(options.draggingClass); + dragging = _getDragging(dragItem, sortableContainer); + addAttribute(dragging, 'aria-grabbed', 'true'); + // dispatch sortstart event on each element in group + sortableContainer.dispatchEvent(new CustomEvent('sortstart', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging, + originalTarget: target + } + })); + }); + /* + We are capturing targetSortable before modifications with 'dragenter' event + */ + addEventListener(sortableElement, 'dragenter', function (e) { + var target = getEventTarget(e); + var sortableContainer = findSortable(target, e); + if (sortableContainer && sortableContainer !== previousContainer) { + destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items')) + .filter(function (item) { return item !== store(sortableElement).placeholder; }); + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + } + previousContainer = sortableContainer; + }); + /* + * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend + * Fires each time dragEvent end, or ESC pressed + * We are using it to clean up any draggable elements and placeholders + */ + addEventListener(sortableElement, 'dragend', function (e) { + if (!dragging) { + return; + } + dragging.classList.remove(options.draggingClass); + addAttribute(dragging, 'aria-grabbed', 'false'); + if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') { + dragging.remove(); + } + dragging.style.display = dragging.oldDisplay; + delete dragging.oldDisplay; + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; }) + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + .filter(isInDom)[0]; + if (visiblePlaceholder) { + visiblePlaceholder.remove(); + } + // dispatch sortstart event on each element in group + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + previousContainer = null; + dragging = null; + draggingHeight = null; + }); + /* + * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop + * Fires when valid drop target area is hit + */ + addEventListener(sortableElement, 'drop', function (e) { + if (!_listsConnected(sortableElement, dragging.parentElement)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + addData(dragging, 'dropped', 'true'); + // get the one placeholder that is currently visible + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { + return data.placeholder; + }) + // filter only HTMLElements + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + // filter only elements in DOM + .filter(isInDom)[0]; + // attach element after placeholder + insertAfter(visiblePlaceholder, dragging); + // remove placeholder from dom + visiblePlaceholder.remove(); + /* + * Fires Custom Event - 'sortstop' + */ + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + var placeholder = store(sortableElement).placeholder; + var originItems = _filter(originContainer.children, options.items) + .filter(function (item) { return item !== placeholder; }); + var destinationContainer = this.isSortable === true ? this : this.parentElement; + var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items')) + .filter(function (item) { return item !== placeholder; }); + var destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = _index(dragging, destinationItems); + /* + * When a list item changed container lists or index within a list + * Fires Custom Event - 'sortupdate' + */ + if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { + sortableElement.dispatchEvent(new CustomEvent('sortupdate', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer, + itemsBeforeUpdate: originItemsBeforeUpdate, + items: originItems + }, + destination: { + index: destinationIndex, + elementIndex: destinationElementIndex, + container: destinationContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate, + items: destinationItems + }, + item: dragging + } + })); + } + }); + var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + } + // if element the draggedItem is dragged onto is within the array of all elements in list + // (not only items, but also disabled, etc.) + if (Array.from(sortableElement.children).indexOf(element) > -1) { + var thisHeight = _getElementHeight(element); + var placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = _index(element, element.parentElement.children); + // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering + if (thisHeight > draggingHeight) { + // Dead zone? + var deadZone = thisHeight - draggingHeight; + var offsetTop = _offset(element).top; + if (placeholderIndex < thisIndex && pageY < offsetTop) { + return; + } + if (placeholderIndex > thisIndex && + pageY > offsetTop + thisHeight - deadZone) { + return; + } + } + if (dragging.oldDisplay === undefined) { + dragging.oldDisplay = dragging.style.display; + } + if (dragging.style.display !== 'none') { + dragging.style.display = 'none'; + } + // To avoid flicker, determine where to position the placeholder + // based on where the mouse pointer is relative to the elements + // vertical center. + var placeAfter = false; + try { + var elementMiddle = _offset(element).top + element.offsetHeight / 2; + placeAfter = pageY >= elementMiddle; + } + catch (e) { + placeAfter = placeholderIndex < thisIndex; + } + if (placeAfter) { + insertAfter(element, store(sortableElement).placeholder); + } + else { + insertBefore(element, store(sortableElement).placeholder); + } + // get placeholders from all stores & remove all but current one + Array.from(stores.values()) + // remove empty values + .filter(function (data) { return data.placeholder !== undefined; }) + // foreach placeholder in array if outside of current sorableContainer -> remove from DOM + .forEach(function (data) { + if (data.placeholder !== store(sortableElement).placeholder) { + data.placeholder.remove(); + } + }); + } + else { + // get all placeholders from store + var placeholders = Array.from(stores.values()) + .filter(function (data) { return data.placeholder !== undefined; }) + .map(function (data) { + return data.placeholder; + }); + // check if element is not in placeholders + if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) { + placeholders.forEach(function (element) { return element.remove(); }); + element.appendChild(store(sortableElement).placeholder); + } + } + }, options.debounce); + // Handle dragover and dragenter events on draggable items + var onDragOverEnter = function (e) { + var element = e.target; + var sortableElement = element.isSortable === true ? element : findSortable(element, e); + element = findDragElement(sortableElement, element); + if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') { + return; + } + var options = addData(sortableElement, 'opts'); + if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) { + return; + } + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move'; + debouncedDragOverEnter(sortableElement, element, e.pageY); + }; + addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter); + addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter); + }); + return sortableElements; +} +sortable.destroy = function (sortableElement) { + _destroySortable(sortableElement); +}; +sortable.enable = function (sortableElement) { + _enableSortable(sortableElement); +}; +sortable.disable = function (sortableElement) { + _disableSortable(sortableElement); +}; +/* START.TESTS_ONLY */ +sortable.__testing = { + // add internal methods here for testing purposes + _data: addData, + _removeItemEvents: _removeItemEvents, + _removeItemData: _removeItemData, + _removeSortableData: _removeSortableData +}; + +export default sortable; diff --git a/dist/html5sortable.js b/dist/html5sortable.js new file mode 100644 index 00000000..e002920b --- /dev/null +++ b/dist/html5sortable.js @@ -0,0 +1,1214 @@ +/* + * HTML5Sortable package + * https://github.com/lukasoppermann/html5sortable + * + * Maintained by Lukas Oppermann + * + * Released under the MIT license. + */ +var sortable = (function () { + 'use strict'; + + /** + * Get or set data on element + * @param {HTMLElement} element + * @param {string} key + * @param {any} value + * @return {*} + */ + function addData(element, key, value) { + if (value === undefined) { + return element && element.h5s && element.h5s.data && element.h5s.data[key]; + } + else { + element.h5s = element.h5s || {}; + element.h5s.data = element.h5s.data || {}; + element.h5s.data[key] = value; + } + } + /** + * Remove data from element + * @param {HTMLElement} element + */ + function removeData(element) { + if (element.h5s) { + delete element.h5s.data; + } + } + + /* eslint-env browser */ + /** + * Filter only wanted nodes + * @param {NodeList|HTMLCollection|Array} nodes + * @param {String} selector + * @returns {Array} + */ + var _filter = (function (nodes, selector) { + if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) { + throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.'); + } + if (typeof selector !== 'string') { + return Array.from(nodes); + } + return Array.from(nodes).filter(function (item) { return item.nodeType === 1 && item.matches(selector); }); + }); + + /* eslint-env browser */ + var stores = new Map(); + /** + * Stores data & configurations per Sortable + * @param {Object} config + */ + var Store = /** @class */ (function () { + function Store() { + this._config = new Map(); // eslint-disable-line no-undef + this._placeholder = undefined; // eslint-disable-line no-undef + this._data = new Map(); // eslint-disable-line no-undef + } + Object.defineProperty(Store.prototype, "config", { + /** + * get the configuration map of a class instance + * @method config + * @return {object} + */ + get: function () { + // transform Map to object + var config = {}; + this._config.forEach(function (value, key) { + config[key] = value; + }); + // return object + return config; + }, + /** + * set the configuration of a class instance + * @method config + * @param {object} config object of configurations + */ + set: function (config) { + if (typeof config !== 'object') { + throw new Error('You must provide a valid configuration object to the config setter.'); + } + // combine config with default + var mergedConfig = Object.assign({}, config); + // add config to map + this._config = new Map(Object.entries(mergedConfig)); + }, + enumerable: true, + configurable: true + }); + /** + * set individual configuration of a class instance + * @method setConfig + * @param key valid configuration key + * @param value any value + * @return void + */ + Store.prototype.setConfig = function (key, value) { + if (!this._config.has(key)) { + throw new Error("Trying to set invalid configuration item: " + key); + } + // set config + this._config.set(key, value); + }; + /** + * get an individual configuration of a class instance + * @method getConfig + * @param key valid configuration key + * @return any configuration value + */ + Store.prototype.getConfig = function (key) { + if (!this._config.has(key)) { + throw new Error("Invalid configuration item requested: " + key); + } + return this._config.get(key); + }; + Object.defineProperty(Store.prototype, "placeholder", { + /** + * get the placeholder for a class instance + * @method placeholder + * @return {HTMLElement|null} + */ + get: function () { + return this._placeholder; + }, + /** + * set the placeholder for a class instance + * @method placeholder + * @param {HTMLElement} placeholder + * @return {void} + */ + set: function (placeholder) { + if (!(placeholder instanceof HTMLElement) && placeholder !== null) { + throw new Error('A placeholder must be an html element or null.'); + } + this._placeholder = placeholder; + }, + enumerable: true, + configurable: true + }); + /** + * set an data entry + * @method setData + * @param {string} key + * @param {any} value + * @return {void} + */ + Store.prototype.setData = function (key, value) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + this._data.set(key, value); + }; + /** + * get an data entry + * @method getData + * @param {string} key an existing key + * @return {any} + */ + Store.prototype.getData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.get(key); + }; + /** + * delete an data entry + * @method deleteData + * @param {string} key an existing key + * @return {boolean} + */ + Store.prototype.deleteData = function (key) { + if (typeof key !== 'string') { + throw new Error("The key must be a string."); + } + return this._data.delete(key); + }; + return Store; + }()); + /** + * @param {HTMLElement} sortableElement + * @returns {Class: Store} + */ + var store = (function (sortableElement) { + // if sortableElement is wrong type + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('Please provide a sortable to the store function.'); + } + // create new instance if not avilable + if (!stores.has(sortableElement)) { + stores.set(sortableElement, new Store()); + } + // return instance + return stores.get(sortableElement); + }); + + /** + * @param {Array|HTMLElement} element + * @param {Function} callback + * @param {string} event + */ + function addEventListener(element, eventName, callback) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addEventListener(element[i], eventName, callback); + } + return; + } + element.addEventListener(eventName, callback); + store(element).setData("event" + eventName, callback); + } + /** + * @param {Array|HTMLElement} element + * @param {string} eventName + */ + function removeEventListener(element, eventName) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeEventListener(element[i], eventName); + } + return; + } + element.removeEventListener(eventName, store(element).getData("event" + eventName)); + store(element).deleteData("event" + eventName); + } + + /** + * @param {Array|HTMLElement} element + * @param {string} attribute + * @param {string} value + */ + function addAttribute(element, attribute, value) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + addAttribute(element[i], attribute, value); + } + return; + } + element.setAttribute(attribute, value); + } + /** + * @param {Array|HTMLElement} element + * @param {string} attribute + */ + function removeAttribute(element, attribute) { + if (element instanceof Array) { + for (var i = 0; i < element.length; ++i) { + removeAttribute(element[i], attribute); + } + return; + } + element.removeAttribute(attribute); + } + + /** + * @param {HTMLElement} element + * @returns {Object} + */ + var _offset = (function (element) { + if (!element.parentElement || element.getClientRects().length === 0) { + throw new Error('target element must be part of the dom'); + } + var rect = element.getClientRects()[0]; + return { + left: rect.left + window.pageXOffset, + right: rect.right + window.pageXOffset, + top: rect.top + window.pageYOffset, + bottom: rect.bottom + window.pageYOffset + }; + }); + + /** + * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed + * @param {Function} func to debounce + * @param {number} time to wait before calling function with latest arguments, 0 - no debounce + * @returns {function} - debounced function + */ + var _debounce = (function (func, wait) { + if (wait === void 0) { wait = 0; } + var timeout; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + clearTimeout(timeout); + timeout = setTimeout(function () { + func.apply(void 0, args); + }, wait); + }; + }); + + /* eslint-env browser */ + /** + * Get position of the element relatively to its sibling elements + * @param {HTMLElement} element + * @returns {number} + */ + var _index = (function (element, elementList) { + if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) { + throw new Error('You must provide an element and a list of elements.'); + } + return Array.from(elementList).indexOf(element); + }); + + /* eslint-env browser */ + /** + * Test whether element is in DOM + * @param {HTMLElement} element + * @returns {boolean} + */ + var isInDom = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('Element is not a node element.'); + } + return element.parentNode !== null; + }); + + /* eslint-env browser */ + /** + * Insert node before or after target + * @param {HTMLElement} referenceNode - reference element + * @param {HTMLElement} newElement - element to be inserted + * @param {String} position - insert before or after reference element + */ + var insertNode = function (referenceNode, newElement, position) { + if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) { + throw new Error('target and element must be a node'); + } + referenceNode.parentElement.insertBefore(newElement, (position === 'before' ? referenceNode : referenceNode.nextElementSibling)); + }; + /** + * Insert before target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ + var insertBefore = function (target, element) { return insertNode(target, element, 'before'); }; + /** + * Insert after target + * @param {HTMLElement} target + * @param {HTMLElement} element + */ + var insertAfter = function (target, element) { return insertNode(target, element, 'after'); }; + + /* eslint-env browser */ + /** + * Filter only wanted nodes + * @param {HTMLElement} sortableContainer + * @param {Function} customSerializer + * @returns {Array} + */ + var _serialize = (function (sortableContainer, customItemSerializer, customContainerSerializer) { + if (customItemSerializer === void 0) { customItemSerializer = function (serializedItem, sortableContainer) { return serializedItem; }; } + if (customContainerSerializer === void 0) { customContainerSerializer = function (serializedContainer) { return serializedContainer; }; } + // check for valid sortableContainer + if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) { + throw new Error('You need to provide a sortableContainer to be serialized.'); + } + // check for valid serializers + if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') { + throw new Error('You need to provide a valid serializer for items and the container.'); + } + // get options + var options = addData(sortableContainer, 'opts'); + var item = options.items; + // serialize container + var items = _filter(sortableContainer.children, item); + var serializedItems = items.map(function (item) { + return { + parent: sortableContainer, + node: item, + html: item.outerHTML, + index: _index(item, items) + }; + }); + // serialize container + var container = { + node: sortableContainer, + itemCount: serializedItems.length + }; + return { + container: customContainerSerializer(container), + items: serializedItems.map(function (item) { return customItemSerializer(item, sortableContainer); }) + }; + }); + + /* eslint-env browser */ + /** + * create a placeholder element + * @param {HTMLElement} sortableElement a single sortable + * @param {string|undefined} placeholder a string representing an html element + * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder + */ + var _makePlaceholder = (function (sortableElement, placeholder, placeholderClass) { + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + var _a; + if (!(sortableElement instanceof HTMLElement)) { + throw new Error('You must provide a valid element as a sortable.'); + } + // if placeholder is not an element + if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) { + throw new Error('You must provide a valid element as a placeholder or set ot to undefined.'); + } + // if no placeholder element is given + if (placeholder === undefined) { + if (['UL', 'OL'].includes(sortableElement.tagName)) { + placeholder = document.createElement('li'); + } + else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) { + placeholder = document.createElement('tr'); + // set colspan to always all rows, otherwise the item can only be dropped in first column + placeholder.innerHTML = ''; + } + else { + placeholder = document.createElement('div'); + } + } + // add classes to placeholder + if (typeof placeholderClass === 'string') { + (_a = placeholder.classList).add.apply(_a, placeholderClass.split(' ')); + } + return placeholder; + }); + + /* eslint-env browser */ + /** + * Get height of an element including padding + * @param {HTMLElement} element an dom element + */ + var _getElementHeight = (function (element) { + if (!(element instanceof HTMLElement)) { + throw new Error('You must provide a valid dom element'); + } + // get calculated style of element + var style = window.getComputedStyle(element); + // pick applicable properties, convert to int and reduce by adding + return ['height', 'padding-top', 'padding-bottom'] + .map(function (key) { + var int = parseInt(style.getPropertyValue(key), 10); + return isNaN(int) ? 0 : int; + }) + .reduce(function (sum, value) { return sum + value; }); + }); + + /* eslint-env browser */ + /** + * get handle or return item + * @param {Array} items + * @param {string} selector + */ + var _getHandles = (function (items, selector) { + if (!(items instanceof Array)) { + throw new Error('You must provide a Array of HTMLElements to be filtered.'); + } + if (typeof selector !== 'string') { + return items; + } + return items + // remove items without handle from array + .filter(function (item) { + return item.querySelector(selector) instanceof HTMLElement || + (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement); + }) + // replace item with handle in array + .map(function (item) { + return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector)); + }); + }); + + /** + * @param {Event} event + * @returns {HTMLElement} + */ + var getEventTarget = (function (event) { + return (event.composedPath && event.composedPath()[0]) || event.target; + }); + + /* eslint-env browser */ + /** + * defaultDragImage returns the current item as dragged image + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {object} elementOffset - an object with the offsets top, left, right & bottom + * @param {Event} event - the original drag event object + * @return {object} with element, posX and posY properties + */ + var defaultDragImage = function (draggedElement, elementOffset, event) { + return { + element: draggedElement, + posX: event.pageX - elementOffset.left, + posY: event.pageY - elementOffset.top + }; + }; + /** + * attaches an element as the drag image to an event + * @param {Event} event - the original drag event object + * @param {HTMLElement} draggedElement - the item that the user drags + * @param {Function} customDragImage - function to create a custom dragImage + * @return void + */ + var setDragImage = (function (event, draggedElement, customDragImage) { + // check if event is provided + if (!(event instanceof Event)) { + throw new Error('setDragImage requires a DragEvent as the first argument.'); + } + // check if draggedElement is provided + if (!(draggedElement instanceof HTMLElement)) { + throw new Error('setDragImage requires the dragged element as the second argument.'); + } + // set default function of none provided + if (!customDragImage) { + customDragImage = defaultDragImage; + } + // check if setDragImage method is available + if (event.dataTransfer && event.dataTransfer.setDragImage) { + // get the elements offset + var elementOffset = _offset(draggedElement); + // get the dragImage + var dragImage = customDragImage(draggedElement, elementOffset, event); + // check if custom function returns correct values + if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') { + throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].'); + } + // needs to be set for HTML5 drag & drop to work + event.dataTransfer.effectAllowed = 'copyMove'; + // Firefox requires it to use the event target's id for the data + event.dataTransfer.setData('text/plain', getEventTarget(event).id); + // set the drag image on the event + event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY); + } + }); + + /** + * Check if curList accepts items from destList + * @param {sortable} destination the container an item is move to + * @param {sortable} origin the container an item comes from + */ + var _listsConnected = (function (destination, origin) { + // check if valid sortable + if (destination.isSortable === true) { + var acceptFrom = store(destination).getConfig('acceptFrom'); + // check if acceptFrom is valid + if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') { + throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.'); + } + if (acceptFrom !== null) { + return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) { + return sel.length > 0 && origin.matches(sel); + }).length > 0; + } + // drop in same list + if (destination === origin) { + return true; + } + // check if lists are connected with connectWith + if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) { + return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith'); + } + } + return false; + }); + + /** + * default configurations + */ + var defaultConfiguration = { + items: null, + // deprecated + connectWith: null, + // deprecated + disableIEFix: null, + acceptFrom: null, + copy: false, + placeholder: null, + placeholderClass: 'sortable-placeholder', + draggingClass: 'sortable-dragging', + hoverClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null + }; + + /** + * make sure a function is only called once within the given amount of time + * @param {Function} fn the function to throttle + * @param {number} threshold time limit for throttling + */ + // must use function to keep this context + function _throttle (fn, threshold) { + var _this = this; + if (threshold === void 0) { threshold = 250; } + // check function + if (typeof fn !== 'function') { + throw new Error('You must provide a function as the first argument for throttle.'); + } + // check threshold + if (typeof threshold !== 'number') { + throw new Error('You must provide a number as the second argument for throttle.'); + } + var lastEventTimestamp = null; + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var now = Date.now(); + if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) { + lastEventTimestamp = now; + fn.apply(_this, args); + } + }; + } + + /* eslint-env browser */ + /** + * enable or disable hoverClass on mouseenter/leave if container Items + * @param {sortable} sortableContainer a valid sortableContainer + * @param {boolean} enable enable or disable event + */ + // export default (sortableContainer: sortable, enable: boolean) => { + var enableHoverClass = (function (sortableContainer, enable) { + if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') { + var hoverClasses_1 = store(sortableContainer).getConfig('hoverClass').split(' '); + // add class on hover + if (enable === true) { + addEventListener(sortableContainer, 'mousemove', _throttle(function (event) { + // check of no mouse button was pressed when mousemove started == no drag + if (event.buttons === 0) { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a, _b; + if (item !== event.target) { + (_a = item.classList).remove.apply(_a, hoverClasses_1); + } + else { + (_b = item.classList).add.apply(_b, hoverClasses_1); + } + }); + } + }, store(sortableContainer).getConfig('throttleTime'))); + // remove class on leave + addEventListener(sortableContainer, 'mouseleave', function () { + _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(function (item) { + var _a; + (_a = item.classList).remove.apply(_a, hoverClasses_1); + }); + }); + // remove events + } + else { + removeEventListener(sortableContainer, 'mousemove'); + removeEventListener(sortableContainer, 'mouseleave'); + } + } + }); + + /* eslint-env browser */ + /* + * variables global to the plugin + */ + var dragging; + var draggingHeight; + /* + * Keeps track of the initialy selected list, where 'dragstart' event was triggered + * It allows us to move the data in between individual Sortable List instances + */ + // Origin List - data from before any item was changed + var originContainer; + var originIndex; + var originElementIndex; + var originItemsBeforeUpdate; + // Previous Sortable Container - we dispatch as sortenter event when a + // dragged item enters a sortableContainer for the first time + var previousContainer; + // Destination List - data from before any item was changed + var destinationItemsBeforeUpdate; + /** + * remove event handlers from items + * @param {Array|NodeList} items + */ + var _removeItemEvents = function (items) { + removeEventListener(items, 'dragstart'); + removeEventListener(items, 'dragend'); + removeEventListener(items, 'dragover'); + removeEventListener(items, 'dragenter'); + removeEventListener(items, 'drop'); + removeEventListener(items, 'mouseenter'); + removeEventListener(items, 'mouseleave'); + }; + /** + * _getDragging returns the current element to drag or + * a copy of the element. + * Is Copy Active for sortable + * @param {HTMLElement} draggedItem - the item that the user drags + * @param {HTMLElement} sortable a single sortable + */ + var _getDragging = function (draggedItem, sortable) { + var ditem = draggedItem; + if (store(sortable).getConfig('copy') === true) { + ditem = draggedItem.cloneNode(true); + addAttribute(ditem, 'aria-copied', 'true'); + draggedItem.parentElement.appendChild(ditem); + ditem.style.display = 'none'; + ditem.oldDisplay = draggedItem.style.display; + } + return ditem; + }; + /** + * Remove data from sortable + * @param {HTMLElement} sortable a single sortable + */ + var _removeSortableData = function (sortable) { + removeData(sortable); + removeAttribute(sortable, 'aria-dropeffect'); + }; + /** + * Remove data from items + * @param {Array|HTMLElement} items + */ + var _removeItemData = function (items) { + removeAttribute(items, 'aria-grabbed'); + removeAttribute(items, 'aria-copied'); + removeAttribute(items, 'draggable'); + removeAttribute(items, 'role'); + }; + /** + * find sortable from element. travels up parent element until found or null. + * @param {HTMLElement} element a single sortable + * @param {Event} event - the current event. We need to pass it to be able to + * find Sortable whith shadowRoot (document fragment has no parent) + */ + function findSortable(element, event) { + if (event.composedPath) { + return event.composedPath().find(function (el) { return el.isSortable; }); + } + while (element.isSortable !== true) { + element = element.parentElement; + } + return element; + } + /** + * Dragging event is on the sortable element. finds the top child that + * contains the element. + * @param {HTMLElement} sortableElement a single sortable + * @param {HTMLElement} element is that being dragged + */ + function findDragElement(sortableElement, element) { + var options = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, options.items); + var itemlist = items.filter(function (ele) { + return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element)); + }); + return itemlist.length > 0 ? itemlist[0] : element; + } + /** + * Destroy the sortable + * @param {HTMLElement} sortableElement a single sortable + */ + var _destroySortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts') || {}; + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + _removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + _removeItemEvents(items); + _removeItemData(items); + }; + /** + * Enable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ + var _enableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'move'); + addData(sortableElement, '_disabled', 'false'); + addAttribute(handles, 'draggable', 'true'); + // @todo: remove this fix + // IE FIX for ghost + // can be disabled as it has the side effect that other events + // (e.g. click) will be ignored + if (opts.disableIEFix === false) { + var spanEl = (document || window.document).createElement('span'); + if (typeof spanEl.dragDrop === 'function') { + addEventListener(handles, 'mousedown', function () { + if (items.indexOf(this) !== -1) { + this.dragDrop(); + } + else { + var parent = this.parentElement; + while (items.indexOf(parent) === -1) { + parent = parent.parentElement; + } + parent.dragDrop(); + } + }); + } + } + }; + /** + * Disable the sortable + * @param {HTMLElement} sortableElement a single sortable + */ + var _disableSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addAttribute(sortableElement, 'aria-dropeffect', 'none'); + addData(sortableElement, '_disabled', 'true'); + addAttribute(handles, 'draggable', 'false'); + removeEventListener(handles, 'mousedown'); + }; + /** + * Reload the sortable + * @param {HTMLElement} sortableElement a single sortable + * @description events need to be removed to not be double bound + */ + var _reloadSortable = function (sortableElement) { + var opts = addData(sortableElement, 'opts'); + var items = _filter(sortableElement.children, opts.items); + var handles = _getHandles(items, opts.handle); + addData(sortableElement, '_disabled', 'false'); + // remove event handlers from items + _removeItemEvents(items); + removeEventListener(handles, 'mousedown'); + // remove event handlers from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'drop'); + }; + /** + * Public sortable object + * @param {Array|NodeList} sortableElements + * @param {object|string} options|method + */ + function sortable(sortableElements, options) { + // get method string to see if a method is called + var method = String(options); + options = options || {}; + // check if the user provided a selector instead of an element + if (typeof sortableElements === 'string') { + sortableElements = document.querySelectorAll(sortableElements); + } + // if the user provided an element, return it in an array to keep the return value consistant + if (sortableElements instanceof HTMLElement) { + sortableElements = [sortableElements]; + } + sortableElements = Array.prototype.slice.call(sortableElements); + if (/serialize/.test(method)) { + return sortableElements.map(function (sortableContainer) { + var opts = addData(sortableContainer, 'opts'); + return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer); + }); + } + sortableElements.forEach(function (sortableElement) { + if (/enable|disable|destroy/.test(method)) { + return sortable[method](sortableElement); + } + // log deprecation + ['connectWith', 'disableIEFix'].forEach(function (configKey) { + if (options.hasOwnProperty(configKey) && options[configKey] !== null) { + console.warn("HTML5Sortable: You are using the deprecated configuration \"" + configKey + "\". This will be removed in an upcoming version, make sure to migrate to the new options when updating."); + } + }); + // merge options with default options + options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options); + // init data store for sortable + store(sortableElement).config = options; + // set options on sortable + addData(sortableElement, 'opts', options); + // property to define as sortable + sortableElement.isSortable = true; + // reset sortable + _reloadSortable(sortableElement); + // initialize + var listItems = _filter(sortableElement.children, options.items); + // create element if user defined a placeholder element as a string + var customPlaceholder; + if (options.placeholder !== null && options.placeholder !== undefined) { + var tempContainer = document.createElement(sortableElement.tagName); + if (options.placeholder instanceof HTMLElement) { + tempContainer.appendChild(options.placeholder); + } + else { + tempContainer.innerHTML = options.placeholder; + } + customPlaceholder = tempContainer.children[0]; + } + // add placeholder + store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass); + addData(sortableElement, 'items', options.items); + if (options.acceptFrom) { + addData(sortableElement, 'acceptFrom', options.acceptFrom); + } + else if (options.connectWith) { + addData(sortableElement, 'connectWith', options.connectWith); + } + _enableSortable(sortableElement); + addAttribute(listItems, 'role', 'option'); + addAttribute(listItems, 'aria-grabbed', 'false'); + // enable hover class + enableHoverClass(sortableElement, true); + /* + Handle drag events on draggable items + Handle is set at the sortableElement level as it will bubble up + from the item + */ + addEventListener(sortableElement, 'dragstart', function (e) { + // ignore dragstart events + var target = getEventTarget(e); + if (target.isSortable === true) { + return; + } + e.stopImmediatePropagation(); + if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') { + return; + } + var sortableContainer = findSortable(target, e); + var dragItem = findDragElement(sortableContainer, target); + // grab values + originItemsBeforeUpdate = _filter(sortableContainer.children, options.items); + originIndex = originItemsBeforeUpdate.indexOf(dragItem); + originElementIndex = _index(dragItem, sortableContainer.children); + originContainer = sortableContainer; + // add transparent clone or other ghost to cursor + setDragImage(e, dragItem, options.customDragImage); + // cache selsection & add attr for dragging + draggingHeight = _getElementHeight(dragItem); + dragItem.classList.add(options.draggingClass); + dragging = _getDragging(dragItem, sortableContainer); + addAttribute(dragging, 'aria-grabbed', 'true'); + // dispatch sortstart event on each element in group + sortableContainer.dispatchEvent(new CustomEvent('sortstart', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging, + originalTarget: target + } + })); + }); + /* + We are capturing targetSortable before modifications with 'dragenter' event + */ + addEventListener(sortableElement, 'dragenter', function (e) { + var target = getEventTarget(e); + var sortableContainer = findSortable(target, e); + if (sortableContainer && sortableContainer !== previousContainer) { + destinationItemsBeforeUpdate = _filter(sortableContainer.children, addData(sortableContainer, 'items')) + .filter(function (item) { return item !== store(sortableElement).placeholder; }); + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + } + previousContainer = sortableContainer; + }); + /* + * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend + * Fires each time dragEvent end, or ESC pressed + * We are using it to clean up any draggable elements and placeholders + */ + addEventListener(sortableElement, 'dragend', function (e) { + if (!dragging) { + return; + } + dragging.classList.remove(options.draggingClass); + addAttribute(dragging, 'aria-grabbed', 'false'); + if (dragging.getAttribute('aria-copied') === 'true' && addData(dragging, 'dropped') !== 'true') { + dragging.remove(); + } + dragging.style.display = dragging.oldDisplay; + delete dragging.oldDisplay; + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { return data.placeholder; }) + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + .filter(isInDom)[0]; + if (visiblePlaceholder) { + visiblePlaceholder.remove(); + } + // dispatch sortstart event on each element in group + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + previousContainer = null; + dragging = null; + draggingHeight = null; + }); + /* + * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop + * Fires when valid drop target area is hit + */ + addEventListener(sortableElement, 'drop', function (e) { + if (!_listsConnected(sortableElement, dragging.parentElement)) { + return; + } + e.preventDefault(); + e.stopPropagation(); + addData(dragging, 'dropped', 'true'); + // get the one placeholder that is currently visible + var visiblePlaceholder = Array.from(stores.values()).map(function (data) { + return data.placeholder; + }) + // filter only HTMLElements + .filter(function (placeholder) { return placeholder instanceof HTMLElement; }) + // filter only elements in DOM + .filter(isInDom)[0]; + // attach element after placeholder + insertAfter(visiblePlaceholder, dragging); + // remove placeholder from dom + visiblePlaceholder.remove(); + /* + * Fires Custom Event - 'sortstop' + */ + sortableElement.dispatchEvent(new CustomEvent('sortstop', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + item: dragging + } + })); + var placeholder = store(sortableElement).placeholder; + var originItems = _filter(originContainer.children, options.items) + .filter(function (item) { return item !== placeholder; }); + var destinationContainer = this.isSortable === true ? this : this.parentElement; + var destinationItems = _filter(destinationContainer.children, addData(destinationContainer, 'items')) + .filter(function (item) { return item !== placeholder; }); + var destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = _index(dragging, destinationItems); + /* + * When a list item changed container lists or index within a list + * Fires Custom Event - 'sortupdate' + */ + if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) { + sortableElement.dispatchEvent(new CustomEvent('sortupdate', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer, + itemsBeforeUpdate: originItemsBeforeUpdate, + items: originItems + }, + destination: { + index: destinationIndex, + elementIndex: destinationElementIndex, + container: destinationContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate, + items: destinationItems + }, + item: dragging + } + })); + } + }); + var debouncedDragOverEnter = _debounce(function (sortableElement, element, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + } + // if element the draggedItem is dragged onto is within the array of all elements in list + // (not only items, but also disabled, etc.) + if (Array.from(sortableElement.children).indexOf(element) > -1) { + var thisHeight = _getElementHeight(element); + var placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = _index(element, element.parentElement.children); + // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering + if (thisHeight > draggingHeight) { + // Dead zone? + var deadZone = thisHeight - draggingHeight; + var offsetTop = _offset(element).top; + if (placeholderIndex < thisIndex && pageY < offsetTop) { + return; + } + if (placeholderIndex > thisIndex && + pageY > offsetTop + thisHeight - deadZone) { + return; + } + } + if (dragging.oldDisplay === undefined) { + dragging.oldDisplay = dragging.style.display; + } + if (dragging.style.display !== 'none') { + dragging.style.display = 'none'; + } + // To avoid flicker, determine where to position the placeholder + // based on where the mouse pointer is relative to the elements + // vertical center. + var placeAfter = false; + try { + var elementMiddle = _offset(element).top + element.offsetHeight / 2; + placeAfter = pageY >= elementMiddle; + } + catch (e) { + placeAfter = placeholderIndex < thisIndex; + } + if (placeAfter) { + insertAfter(element, store(sortableElement).placeholder); + } + else { + insertBefore(element, store(sortableElement).placeholder); + } + // get placeholders from all stores & remove all but current one + Array.from(stores.values()) + // remove empty values + .filter(function (data) { return data.placeholder !== undefined; }) + // foreach placeholder in array if outside of current sorableContainer -> remove from DOM + .forEach(function (data) { + if (data.placeholder !== store(sortableElement).placeholder) { + data.placeholder.remove(); + } + }); + } + else { + // get all placeholders from store + var placeholders = Array.from(stores.values()) + .filter(function (data) { return data.placeholder !== undefined; }) + .map(function (data) { + return data.placeholder; + }); + // check if element is not in placeholders + if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) { + placeholders.forEach(function (element) { return element.remove(); }); + element.appendChild(store(sortableElement).placeholder); + } + } + }, options.debounce); + // Handle dragover and dragenter events on draggable items + var onDragOverEnter = function (e) { + var element = e.target; + var sortableElement = element.isSortable === true ? element : findSortable(element, e); + element = findDragElement(sortableElement, element); + if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || addData(sortableElement, '_disabled') === 'true') { + return; + } + var options = addData(sortableElement, 'opts'); + if (parseInt(options.maxItems) && _filter(sortableElement.children, addData(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) { + return; + } + e.preventDefault(); + e.stopPropagation(); + e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move'; + debouncedDragOverEnter(sortableElement, element, e.pageY); + }; + addEventListener(listItems.concat(sortableElement), 'dragover', onDragOverEnter); + addEventListener(listItems.concat(sortableElement), 'dragenter', onDragOverEnter); + }); + return sortableElements; + } + sortable.destroy = function (sortableElement) { + _destroySortable(sortableElement); + }; + sortable.enable = function (sortableElement) { + _enableSortable(sortableElement); + }; + sortable.disable = function (sortableElement) { + _disableSortable(sortableElement); + }; + /* START.TESTS_ONLY */ + sortable.__testing = { + // add internal methods here for testing purposes + _data: addData, + _removeItemEvents: _removeItemEvents, + _removeItemData: _removeItemData, + _removeSortableData: _removeSortableData + }; + + return sortable; + +}()); diff --git a/dist/html5sortable.min.js b/dist/html5sortable.min.js new file mode 100644 index 00000000..3a724128 --- /dev/null +++ b/dist/html5sortable.min.js @@ -0,0 +1,2 @@ +var sortable=function(){"use strict";function c(e,t,n){if(void 0===n)return e&&e.h5s&&e.h5s.data&&e.h5s.data[t];e.h5s=e.h5s||{},e.h5s.data=e.h5s.data||{},e.h5s.data[t]=n}var d=function(e,t){if(!(e instanceof NodeList||e instanceof HTMLCollection||e instanceof Array))throw new Error("You must provide a nodeList/HTMLCollection/Array of elements to be filtered.");return"string"!=typeof t?Array.from(e):Array.from(e).filter(function(e){return 1===e.nodeType&&e.matches(t)})},u=new Map,t=function(){function e(){this._config=new Map,this._placeholder=void 0,this._data=new Map}return Object.defineProperty(e.prototype,"config",{get:function(){var n={};return this._config.forEach(function(e,t){n[t]=e}),n},set:function(e){if("object"!=typeof e)throw new Error("You must provide a valid configuration object to the config setter.");var t=Object.assign({},e);this._config=new Map(Object.entries(t))},enumerable:!0,configurable:!0}),e.prototype.setConfig=function(e,t){if(!this._config.has(e))throw new Error("Trying to set invalid configuration item: "+e);this._config.set(e,t)},e.prototype.getConfig=function(e){if(!this._config.has(e))throw new Error("Invalid configuration item requested: "+e);return this._config.get(e)},Object.defineProperty(e.prototype,"placeholder",{get:function(){return this._placeholder},set:function(e){if(!(e instanceof HTMLElement)&&null!==e)throw new Error("A placeholder must be an html element or null.");this._placeholder=e},enumerable:!0,configurable:!0}),e.prototype.setData=function(e,t){if("string"!=typeof e)throw new Error("The key must be a string.");this._data.set(e,t)},e.prototype.getData=function(e){if("string"!=typeof e)throw new Error("The key must be a string.");return this._data.get(e)},e.prototype.deleteData=function(e){if("string"!=typeof e)throw new Error("The key must be a string.");return this._data.delete(e)},e}(),p=function(e){if(!(e instanceof HTMLElement))throw new Error("Please provide a sortable to the store function.");return u.has(e)||u.set(e,new t),u.get(e)};function a(e,t,n){if(e instanceof Array)for(var r=0;r':t=document.createElement("div")),"string"==typeof n&&(r=t.classList).add.apply(r,n.split(" ")),t},b=function(e){if(!(e instanceof HTMLElement))throw new Error("You must provide a valid dom element");var n=window.getComputedStyle(e);return["height","padding-top","padding-bottom"].map(function(e){var t=parseInt(n.getPropertyValue(e),10);return isNaN(t)?0:t}).reduce(function(e,t){return e+t})},s=function(e,t){if(!(e instanceof Array))throw new Error("You must provide a Array of HTMLElements to be filtered.");return"string"!=typeof t?e:e.filter(function(e){return e.querySelector(t)instanceof HTMLElement||e.shadowRoot&&e.shadowRoot.querySelector(t)instanceof HTMLElement}).map(function(e){return e.querySelector(t)||e.shadowRoot&&e.shadowRoot.querySelector(t)})},T=function(e){return e.composedPath&&e.composedPath()[0]||e.target},f=function(e,t,n){return{element:e,posX:n.pageX-t.left,posY:n.pageY-t.top}},L=function(e,t,n){if(!(e instanceof Event))throw new Error("setDragImage requires a DragEvent as the first argument.");if(!(t instanceof HTMLElement))throw new Error("setDragImage requires the dragged element as the second argument.");if(n||(n=f),e.dataTransfer&&e.dataTransfer.setDragImage){var r=n(t,m(t),e);if(!(r.element instanceof HTMLElement)||"number"!=typeof r.posX||"number"!=typeof r.posY)throw new Error("The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].");e.dataTransfer.effectAllowed="copyMove",e.dataTransfer.setData("text/plain",T(e).id),e.dataTransfer.setDragImage(r.element,r.posX,r.posY)}},C=function(e,t){if(!0===e.isSortable){var n=p(e).getConfig("acceptFrom");if(null!==n&&!1!==n&&"string"!=typeof n)throw new Error('HTML5Sortable: Wrong argument, "acceptFrom" must be "null", "false", or a valid selector string.');if(null!==n)return!1!==n&&0=parseInt(r.maxItems)&&D.parentElement!==n||(e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect=!0===p(n).getConfig("copy")?"copy":"move",o(n,t,e.pageY))}};a(t.concat(s),"dragover",r),a(t.concat(s),"dragenter",r)}),e)}return X.destroy=function(e){var t,n,r,o;n=c(t=e,"opts")||{},r=d(t.children,n.items),o=s(r,n.handle),i(t,"dragover"),i(t,"dragenter"),i(t,"drop"),F(t),i(o,"mousedown"),P(r),N(r)},X.enable=function(e){z(e)},X.disable=function(e){var t,n,r,o;n=c(t=e,"opts"),r=d(t.children,n.items),o=s(r,n.handle),l(t,"aria-dropeffect","none"),c(t,"_disabled","true"),l(o,"draggable","false"),i(o,"mousedown")},X.__testing={_data:c,_removeItemEvents:P,_removeItemData:N,_removeSortableData:F},X}(); +//# sourceMappingURL=html5sortable.min.js.map diff --git a/dist/html5sortable.min.js.map b/dist/html5sortable.min.js.map new file mode 100644 index 00000000..d9a5f2f3 --- /dev/null +++ b/dist/html5sortable.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"html5sortable.min.js","sources":["../src/data.ts","../src/filter.ts","../src/store.ts","../src/eventListener.ts","../src/attribute.ts","../src/offset.ts","../src/debounce.ts","../src/index.ts","../src/isInDom.ts","../src/insertHtmlElements.ts","../src/serialize.ts","../src/makePlaceholder.ts","../src/elementHeight.ts","../src/getHandles.ts","../src/getEventTarget.ts","../src/setDragImage.ts","../src/isConnected.ts","../src/defaultConfiguration.ts","../src/html5sortable.ts","../src/hoverClass.ts","../src/throttle.ts"],"sourcesContent":["/**\n * Get or set data on element\n * @param {HTMLElement} element\n * @param {string} key\n * @param {any} value\n * @return {*}\n */\n\nfunction addData (element: HTMLElement, key: string, value?: any): HTMLElement|configuration|string|void {\n if (value === undefined) {\n return element && element.h5s && element.h5s.data && element.h5s.data[key]\n } else {\n element.h5s = element.h5s || {}\n element.h5s.data = element.h5s.data || {}\n element.h5s.data[key] = value\n }\n}\n/**\n * Remove data from element\n * @param {HTMLElement} element\n */\nfunction removeData (element: HTMLElement) {\n if (element.h5s) {\n delete element.h5s.data\n }\n}\n\nexport { addData, removeData }\n","/* eslint-env browser */\n/**\n * Filter only wanted nodes\n * @param {NodeList|HTMLCollection|Array} nodes\n * @param {String} selector\n * @returns {Array}\n */\nexport default (nodes: NodeList|HTMLCollection|Array, selector: string): Array => {\n if (!(nodes instanceof NodeList || nodes instanceof HTMLCollection || nodes instanceof Array)) {\n throw new Error('You must provide a nodeList/HTMLCollection/Array of elements to be filtered.')\n }\n if (typeof selector !== 'string') {\n return Array.from(nodes)\n }\n\n return Array.from(nodes).filter((item) => item.nodeType === 1 && item.matches(selector))\n}\n","/* eslint-env browser */\nexport let stores: Map = new Map()\n/**\n * Stores data & configurations per Sortable\n * @param {Object} config\n */\nexport class Store implements Store {\n private _config: Map = new Map() // eslint-disable-line no-undef\n private _placeholder?: HTMLElement = undefined // eslint-disable-line no-undef\n private _data: Map = new Map() // eslint-disable-line no-undef\n /**\n * set the configuration of a class instance\n * @method config\n * @param {object} config object of configurations\n */\n set config (config: configuration) {\n if (typeof config !== 'object') {\n throw new Error('You must provide a valid configuration object to the config setter.')\n }\n // combine config with default\n let mergedConfig = Object.assign({}, config)\n // add config to map\n this._config = new Map(Object.entries(mergedConfig))\n }\n /**\n * get the configuration map of a class instance\n * @method config\n * @return {object}\n */\n get config (): configuration {\n // transform Map to object\n let config = {}\n this._config.forEach((value, key) => {\n config[key] = value\n })\n // return object\n return config\n }\n /**\n * set individual configuration of a class instance\n * @method setConfig\n * @param key valid configuration key\n * @param value any value\n * @return void\n */\n setConfig (key: string, value: any): void {\n if (!this._config.has(key)) {\n throw new Error(`Trying to set invalid configuration item: ${key}`)\n }\n // set config\n this._config.set(key, value)\n }\n /**\n * get an individual configuration of a class instance\n * @method getConfig\n * @param key valid configuration key\n * @return any configuration value\n */\n getConfig (key: string): any {\n if (!this._config.has(key)) {\n throw new Error(`Invalid configuration item requested: ${key}`)\n }\n return this._config.get(key)\n }\n /**\n * get the placeholder for a class instance\n * @method placeholder\n * @return {HTMLElement|null}\n */\n get placeholder (): HTMLElement {\n return this._placeholder\n }\n /**\n * set the placeholder for a class instance\n * @method placeholder\n * @param {HTMLElement} placeholder\n * @return {void}\n */\n set placeholder (placeholder: HTMLElement): void {\n if (!(placeholder instanceof HTMLElement) && placeholder !== null) {\n throw new Error('A placeholder must be an html element or null.')\n }\n this._placeholder = placeholder\n }\n /**\n * set an data entry\n * @method setData\n * @param {string} key\n * @param {any} value\n * @return {void}\n */\n setData (key: string, value: Function): void {\n if (typeof key !== 'string') {\n throw new Error(`The key must be a string.`)\n }\n this._data.set(key, value)\n }\n /**\n * get an data entry\n * @method getData\n * @param {string} key an existing key\n * @return {any}\n */\n getData (key: string): any {\n if (typeof key !== 'string') {\n throw new Error(`The key must be a string.`)\n }\n return this._data.get(key)\n }\n /**\n * delete an data entry\n * @method deleteData\n * @param {string} key an existing key\n * @return {boolean}\n */\n deleteData (key: string): boolean {\n if (typeof key !== 'string') {\n throw new Error(`The key must be a string.`)\n }\n return this._data.delete(key)\n }\n}\n/**\n * @param {HTMLElement} sortableElement\n * @returns {Class: Store}\n */\nexport default (sortableElement: HTMLElement): Store => {\n // if sortableElement is wrong type\n if (!(sortableElement instanceof HTMLElement)) {\n throw new Error('Please provide a sortable to the store function.')\n }\n // create new instance if not avilable\n if (!stores.has(sortableElement)) {\n stores.set(sortableElement, new Store())\n }\n // return instance\n return stores.get(sortableElement)\n}\n","import store from './store'\n/**\n * @param {Array|HTMLElement} element\n * @param {Function} callback\n * @param {string} event\n */\nfunction addEventListener (element: Array|HTMLElement, eventName:string, callback: () => void) {\n if (element instanceof Array) {\n for (var i = 0; i < element.length; ++i) {\n addEventListener(element[i], eventName, callback)\n }\n return\n }\n element.addEventListener(eventName, callback)\n store(element).setData(`event${eventName}`, callback)\n}\n/**\n * @param {Array|HTMLElement} element\n * @param {string} eventName\n */\nfunction removeEventListener (element: Array|HTMLElement, eventName: string) {\n if (element instanceof Array) {\n for (var i = 0; i < element.length; ++i) {\n removeEventListener(element[i], eventName)\n }\n return\n }\n element.removeEventListener(eventName, store(element).getData(`event${eventName}`))\n store(element).deleteData(`event${eventName}`)\n}\n\nexport { addEventListener, removeEventListener }\n","/**\n * @param {Array|HTMLElement} element\n * @param {string} attribute\n * @param {string} value\n */\nfunction addAttribute (element: Array|HTMLElement, attribute:string, value:string) {\n if (element instanceof Array) {\n for (var i = 0; i < element.length; ++i) {\n addAttribute(element[i], attribute, value)\n }\n return\n }\n element.setAttribute(attribute, value)\n}\n/**\n * @param {Array|HTMLElement} element\n * @param {string} attribute\n */\nfunction removeAttribute (element: Array|HTMLElement, attribute:string) {\n if (element instanceof Array) {\n for (var i = 0; i < element.length; ++i) {\n removeAttribute(element[i], attribute)\n }\n return\n }\n element.removeAttribute(attribute)\n}\n\nexport { addAttribute, removeAttribute }\n","/**\n * @param {HTMLElement} element\n * @returns {Object}\n */\nexport default (element: HTMLElement): offsetObject => {\n if (!element.parentElement || element.getClientRects().length === 0) {\n throw new Error('target element must be part of the dom')\n }\n\n let rect = element.getClientRects()[0]\n return {\n left: rect.left + window.pageXOffset,\n right: rect.right + window.pageXOffset,\n top: rect.top + window.pageYOffset,\n bottom: rect.bottom + window.pageYOffset\n }\n}\n","/**\n * Creates and returns a new debounced version of the passed function which will postpone its execution until after wait milliseconds have elapsed\n * @param {Function} func to debounce\n * @param {number} time to wait before calling function with latest arguments, 0 - no debounce\n * @returns {function} - debounced function\n */\nexport default (func: Function, wait: number = 0): Function => {\n let timeout\n return (...args) => {\n clearTimeout(timeout)\n timeout = setTimeout(() => {\n func(...args)\n }, wait)\n }\n}\n","/* eslint-env browser */\n/**\n * Get position of the element relatively to its sibling elements\n * @param {HTMLElement} element\n * @returns {number}\n */\nexport default (element: HTMLElement, elementList: HTMLCollection | NodeList | Array): number => {\n if (!(element instanceof HTMLElement) || !(elementList instanceof NodeList || elementList instanceof HTMLCollection || elementList instanceof Array)) {\n throw new Error('You must provide an element and a list of elements.')\n }\n\n return Array.from(elementList).indexOf(element)\n}\n","/* eslint-env browser */\n/**\n * Test whether element is in DOM\n * @param {HTMLElement} element\n * @returns {boolean}\n */\nexport default (element: HTMLElement): boolean => {\n if (!(element instanceof HTMLElement)) {\n throw new Error('Element is not a node element.')\n }\n\n return element.parentNode !== null\n}\n","/* eslint-env browser */\n/**\n * Insert node before or after target\n * @param {HTMLElement} referenceNode - reference element\n * @param {HTMLElement} newElement - element to be inserted\n * @param {String} position - insert before or after reference element\n */\nlet insertNode = (referenceNode: HTMLElement, newElement: HTMLElement, position: String) => {\n if (!(referenceNode instanceof HTMLElement) || !(referenceNode.parentElement instanceof HTMLElement)) {\n throw new Error('target and element must be a node')\n }\n referenceNode.parentElement.insertBefore(\n newElement,\n (position === 'before' ? referenceNode : referenceNode.nextElementSibling)\n )\n}\n/**\n * Insert before target\n * @param {HTMLElement} target\n * @param {HTMLElement} element\n */\nlet insertBefore = (target: HTMLElement, element: HTMLElement) => insertNode(target, element, 'before')\n/**\n * Insert after target\n * @param {HTMLElement} target\n * @param {HTMLElement} element\n */\nlet insertAfter = (target: HTMLElement, element: HTMLElement) => insertNode(target, element, 'after')\n\nexport { insertBefore, insertAfter }\n","/* eslint-env browser */\nimport { addData as _data } from './data' // yuk, data really needs to be refactored\nimport filter from './filter'\nimport index from './index'\n/**\n * Filter only wanted nodes\n * @param {HTMLElement} sortableContainer\n * @param {Function} customSerializer\n * @returns {Array}\n */\nexport default (sortableContainer: HTMLElement, customItemSerializer: Function = (serializedItem: serializedItem, sortableContainer: HTMLElement) => serializedItem, customContainerSerializer: Function = (serializedContainer: object) => serializedContainer): object => {\n // check for valid sortableContainer\n if (!(sortableContainer instanceof HTMLElement) || !sortableContainer.isSortable === true) {\n throw new Error('You need to provide a sortableContainer to be serialized.')\n }\n // check for valid serializers\n if (typeof customItemSerializer !== 'function' || typeof customContainerSerializer !== 'function') {\n throw new Error('You need to provide a valid serializer for items and the container.')\n }\n // get options\n let options = _data(sortableContainer, 'opts')\n\n let item: string|undefined = options.items\n\n // serialize container\n let items = filter(sortableContainer.children, item)\n let serializedItems: serializedItem[] = items.map((item) => {\n return {\n parent: sortableContainer,\n node: item,\n html: item.outerHTML,\n index: index(item, items)\n }\n })\n // serialize container\n let container = {\n node: sortableContainer,\n itemCount: serializedItems.length\n }\n\n return {\n container: customContainerSerializer(container),\n items: serializedItems.map((item: object) => customItemSerializer(item, sortableContainer))\n }\n}\n","/* eslint-env browser */\n/**\n * create a placeholder element\n * @param {HTMLElement} sortableElement a single sortable\n * @param {string|undefined} placeholder a string representing an html element\n * @param {string} placeholderClasses a string representing the classes that should be added to the placeholder\n */\nexport default (sortableElement: HTMLElement, placeholder?: HTMLElement, placeholderClass: string = 'sortable-placeholder') => {\n if (!(sortableElement instanceof HTMLElement)) {\n throw new Error('You must provide a valid element as a sortable.')\n }\n // if placeholder is not an element\n if (!(placeholder instanceof HTMLElement) && placeholder !== undefined) {\n throw new Error('You must provide a valid element as a placeholder or set ot to undefined.')\n }\n // if no placeholder element is given\n if (placeholder === undefined) {\n if (['UL', 'OL'].includes(sortableElement.tagName)) {\n placeholder = document.createElement('li')\n } else if (['TABLE', 'TBODY'].includes(sortableElement.tagName)) {\n placeholder = document.createElement('tr')\n // set colspan to always all rows, otherwise the item can only be dropped in first column\n placeholder.innerHTML = ''\n } else {\n placeholder = document.createElement('div')\n }\n }\n // add classes to placeholder\n if (typeof placeholderClass === 'string') {\n placeholder.classList.add(...placeholderClass.split(' '))\n }\n\n return placeholder\n}\n","/* eslint-env browser */\n/**\n * Get height of an element including padding\n * @param {HTMLElement} element an dom element\n */\nexport default (element: HTMLElement) => {\n if (!(element instanceof HTMLElement)) {\n throw new Error('You must provide a valid dom element')\n }\n // get calculated style of element\n let style = window.getComputedStyle(element)\n // pick applicable properties, convert to int and reduce by adding\n return ['height', 'padding-top', 'padding-bottom']\n .map((key) => {\n let int = parseInt(style.getPropertyValue(key), 10)\n return isNaN(int) ? 0 : int\n })\n .reduce((sum, value) => sum + value)\n}\n","/* eslint-env browser */\n/**\n * get handle or return item\n * @param {Array} items\n * @param {string} selector\n */\n\nexport default (items: Array, selector: string): Array => {\n if (!(items instanceof Array)) {\n throw new Error('You must provide a Array of HTMLElements to be filtered.')\n }\n\n if (typeof selector !== 'string') {\n return items\n }\n\n return items\n // remove items without handle from array\n .filter((item: HTMLElement) => {\n return item.querySelector(selector) instanceof HTMLElement ||\n (item.shadowRoot && item.shadowRoot.querySelector(selector) instanceof HTMLElement)\n })\n // replace item with handle in array\n .map((item: HTMLElement) => {\n return item.querySelector(selector) || (item.shadowRoot && item.shadowRoot.querySelector(selector))\n })\n}\n","/**\n * @param {Event} event\n * @returns {HTMLElement}\n */\nexport default (event: Event): HTMLElement => {\n return (event.composedPath && event.composedPath()[0]) || event.target\n}\n","/* eslint-env browser */\nimport offset from './offset'\nimport getEventTarget from './getEventTarget'\n/**\n * defaultDragImage returns the current item as dragged image\n * @param {HTMLElement} draggedElement - the item that the user drags\n * @param {object} elementOffset - an object with the offsets top, left, right & bottom\n * @param {Event} event - the original drag event object\n * @return {object} with element, posX and posY properties\n */\nlet defaultDragImage = (draggedElement: HTMLElement, elementOffset: offsetObject, event: DragEvent): object => {\n return {\n element: draggedElement,\n posX: event.pageX - elementOffset.left,\n posY: event.pageY - elementOffset.top\n }\n}\n/**\n * attaches an element as the drag image to an event\n * @param {Event} event - the original drag event object\n * @param {HTMLElement} draggedElement - the item that the user drags\n * @param {Function} customDragImage - function to create a custom dragImage\n * @return void\n */\nexport default (event: DragEvent, draggedElement: HTMLElement, customDragImage: Function): void => {\n // check if event is provided\n if (!(event instanceof Event)) {\n throw new Error('setDragImage requires a DragEvent as the first argument.')\n }\n // check if draggedElement is provided\n if (!(draggedElement instanceof HTMLElement)) {\n throw new Error('setDragImage requires the dragged element as the second argument.')\n }\n // set default function of none provided\n if (!customDragImage) {\n customDragImage = defaultDragImage\n }\n // check if setDragImage method is available\n if (event.dataTransfer && event.dataTransfer.setDragImage) {\n // get the elements offset\n let elementOffset = offset(draggedElement)\n // get the dragImage\n let dragImage = customDragImage(draggedElement, elementOffset, event)\n // check if custom function returns correct values\n if (!(dragImage.element instanceof HTMLElement) || typeof dragImage.posX !== 'number' || typeof dragImage.posY !== 'number') {\n throw new Error('The customDragImage function you provided must return and object with the properties element[string], posX[integer], posY[integer].')\n }\n // needs to be set for HTML5 drag & drop to work\n event.dataTransfer.effectAllowed = 'copyMove'\n // Firefox requires it to use the event target's id for the data\n event.dataTransfer.setData('text/plain', getEventTarget(event).id)\n // set the drag image on the event\n event.dataTransfer.setDragImage(dragImage.element, dragImage.posX, dragImage.posY)\n }\n}\n","import store from './store'\n/**\n * Check if curList accepts items from destList\n * @param {sortable} destination the container an item is move to\n * @param {sortable} origin the container an item comes from\n */\nexport default (destination: sortable, origin: sortable) => {\n // check if valid sortable\n if (destination.isSortable === true) {\n const acceptFrom = store(destination).getConfig('acceptFrom')\n // check if acceptFrom is valid\n if (acceptFrom !== null && acceptFrom !== false && typeof acceptFrom !== 'string') {\n throw new Error('HTML5Sortable: Wrong argument, \"acceptFrom\" must be \"null\", \"false\", or a valid selector string.')\n }\n\n if (acceptFrom !== null) {\n return acceptFrom !== false && acceptFrom.split(',').filter(function (sel) {\n return sel.length > 0 && origin.matches(sel)\n }).length > 0\n }\n // drop in same list\n if (destination === origin) {\n return true\n }\n // check if lists are connected with connectWith\n if (store(destination).getConfig('connectWith') !== undefined && store(destination).getConfig('connectWith') !== null) {\n return store(destination).getConfig('connectWith') === store(origin).getConfig('connectWith')\n }\n }\n return false\n}\n","/**\n * default configurations\n */\nexport default {\n items: null,\n // deprecated\n connectWith: null,\n // deprecated\n disableIEFix: null,\n acceptFrom: null,\n copy: false,\n placeholder: null,\n placeholderClass: 'sortable-placeholder',\n draggingClass: 'sortable-dragging',\n hoverClass: false,\n debounce: 0,\n throttleTime: 100,\n maxItems: 0,\n itemSerializer: undefined,\n containerSerializer: undefined,\n customDragImage: null\n}\n","/* eslint-env browser */\n'use strict'\n\nimport { addData as _data, removeData as _removeData } from './data'\nimport _filter from './filter'\nimport { addEventListener as _on, removeEventListener as _off } from './eventListener'\nimport { addAttribute as _attr, removeAttribute as _removeAttr } from './attribute'\nimport _offset from './offset'\nimport _debounce from './debounce'\nimport _index from './index'\nimport isInDom from './isInDom'\nimport { insertBefore as _before, insertAfter as _after } from './insertHtmlElements'\nimport _serialize from './serialize'\nimport _makePlaceholder from './makePlaceholder'\nimport _getElementHeight from './elementHeight'\nimport _getHandles from './getHandles'\nimport getEventTarget from './getEventTarget'\nimport setDragImage from './setDragImage'\nimport { default as store, stores } from './store' /* eslint-disable-line */\nimport _listsConnected from './isConnected'\nimport defaultConfiguration from './defaultConfiguration'\nimport enableHoverClass from './hoverClass'\n/*\n * variables global to the plugin\n */\nlet dragging\nlet draggingHeight\n\n/*\n * Keeps track of the initialy selected list, where 'dragstart' event was triggered\n * It allows us to move the data in between individual Sortable List instances\n */\n\n// Origin List - data from before any item was changed\nlet originContainer\nlet originIndex\nlet originElementIndex\nlet originItemsBeforeUpdate\n\n// Previous Sortable Container - we dispatch as sortenter event when a\n// dragged item enters a sortableContainer for the first time\nlet previousContainer\n\n// Destination List - data from before any item was changed\nlet destinationItemsBeforeUpdate\n\n/**\n * remove event handlers from items\n * @param {Array|NodeList} items\n */\nconst _removeItemEvents = function (items) {\n _off(items, 'dragstart')\n _off(items, 'dragend')\n _off(items, 'dragover')\n _off(items, 'dragenter')\n _off(items, 'drop')\n _off(items, 'mouseenter')\n _off(items, 'mouseleave')\n}\n/**\n * _getDragging returns the current element to drag or\n * a copy of the element.\n * Is Copy Active for sortable\n * @param {HTMLElement} draggedItem - the item that the user drags\n * @param {HTMLElement} sortable a single sortable\n */\nconst _getDragging = function (draggedItem, sortable) {\n let ditem = draggedItem\n if (store(sortable).getConfig('copy') === true) {\n ditem = draggedItem.cloneNode(true)\n _attr(ditem, 'aria-copied', 'true')\n draggedItem.parentElement.appendChild(ditem)\n ditem.style.display = 'none'\n ditem.oldDisplay = draggedItem.style.display\n }\n return ditem\n}\n/**\n * Remove data from sortable\n * @param {HTMLElement} sortable a single sortable\n */\nconst _removeSortableData = function (sortable) {\n _removeData(sortable)\n _removeAttr(sortable, 'aria-dropeffect')\n}\n/**\n * Remove data from items\n * @param {Array|HTMLElement} items\n */\nconst _removeItemData = function (items) {\n _removeAttr(items, 'aria-grabbed')\n _removeAttr(items, 'aria-copied')\n _removeAttr(items, 'draggable')\n _removeAttr(items, 'role')\n}\n/**\n * find sortable from element. travels up parent element until found or null.\n * @param {HTMLElement} element a single sortable\n * @param {Event} event - the current event. We need to pass it to be able to\n * find Sortable whith shadowRoot (document fragment has no parent)\n */\nfunction findSortable (element, event) {\n if (event.composedPath) {\n return event.composedPath().find(el => el.isSortable)\n }\n while (element.isSortable !== true) {\n element = element.parentElement\n }\n return element\n}\n/**\n * Dragging event is on the sortable element. finds the top child that\n * contains the element.\n * @param {HTMLElement} sortableElement a single sortable\n * @param {HTMLElement} element is that being dragged\n */\nfunction findDragElement (sortableElement, element) {\n const options = _data(sortableElement, 'opts')\n const items = _filter(sortableElement.children, options.items)\n const itemlist = items.filter(function (ele) {\n return ele.contains(element) || (ele.shadowRoot && ele.shadowRoot.contains(element))\n })\n\n return itemlist.length > 0 ? itemlist[0] : element\n}\n/**\n * Destroy the sortable\n * @param {HTMLElement} sortableElement a single sortable\n */\nconst _destroySortable = function (sortableElement) {\n const opts = _data(sortableElement, 'opts') || {}\n const items = _filter(sortableElement.children, opts.items)\n const handles = _getHandles(items, opts.handle)\n // remove event handlers & data from sortable\n _off(sortableElement, 'dragover')\n _off(sortableElement, 'dragenter')\n _off(sortableElement, 'drop')\n // remove event data from sortable\n _removeSortableData(sortableElement)\n // remove event handlers & data from items\n _off(handles, 'mousedown')\n _removeItemEvents(items)\n _removeItemData(items)\n}\n/**\n * Enable the sortable\n * @param {HTMLElement} sortableElement a single sortable\n */\nconst _enableSortable = function (sortableElement) {\n const opts = _data(sortableElement, 'opts')\n const items = _filter(sortableElement.children, opts.items)\n const handles = _getHandles(items, opts.handle)\n _attr(sortableElement, 'aria-dropeffect', 'move')\n _data(sortableElement, '_disabled', 'false')\n _attr(handles, 'draggable', 'true')\n // @todo: remove this fix\n // IE FIX for ghost\n // can be disabled as it has the side effect that other events\n // (e.g. click) will be ignored\n if (opts.disableIEFix === false) {\n const spanEl = (document || window.document).createElement('span')\n if (typeof spanEl.dragDrop === 'function') {\n _on(handles, 'mousedown', function () {\n if (items.indexOf(this) !== -1) {\n this.dragDrop()\n } else {\n let parent = this.parentElement\n while (items.indexOf(parent) === -1) {\n parent = parent.parentElement\n }\n parent.dragDrop()\n }\n })\n }\n }\n}\n/**\n * Disable the sortable\n * @param {HTMLElement} sortableElement a single sortable\n */\nconst _disableSortable = function (sortableElement) {\n const opts = _data(sortableElement, 'opts')\n const items = _filter(sortableElement.children, opts.items)\n const handles = _getHandles(items, opts.handle)\n _attr(sortableElement, 'aria-dropeffect', 'none')\n _data(sortableElement, '_disabled', 'true')\n _attr(handles, 'draggable', 'false')\n _off(handles, 'mousedown')\n}\n/**\n * Reload the sortable\n * @param {HTMLElement} sortableElement a single sortable\n * @description events need to be removed to not be double bound\n */\nconst _reloadSortable = function (sortableElement) {\n const opts = _data(sortableElement, 'opts')\n const items = _filter(sortableElement.children, opts.items)\n const handles = _getHandles(items, opts.handle)\n _data(sortableElement, '_disabled', 'false')\n // remove event handlers from items\n _removeItemEvents(items)\n _off(handles, 'mousedown')\n // remove event handlers from sortable\n _off(sortableElement, 'dragover')\n _off(sortableElement, 'dragenter')\n _off(sortableElement, 'drop')\n}\n\n/**\n * Public sortable object\n * @param {Array|NodeList} sortableElements\n * @param {object|string} options|method\n */\nexport default function sortable (sortableElements, options: object|string|undefined): sortable {\n // get method string to see if a method is called\n const method = String(options)\n options = options || {}\n // check if the user provided a selector instead of an element\n if (typeof sortableElements === 'string') {\n sortableElements = document.querySelectorAll(sortableElements)\n }\n // if the user provided an element, return it in an array to keep the return value consistant\n if (sortableElements instanceof HTMLElement) {\n sortableElements = [sortableElements]\n }\n\n sortableElements = Array.prototype.slice.call(sortableElements)\n\n if (/serialize/.test(method)) {\n return sortableElements.map((sortableContainer) => {\n let opts = _data(sortableContainer, 'opts')\n return _serialize(sortableContainer, opts.itemSerializer, opts.containerSerializer)\n })\n }\n\n sortableElements.forEach(function (sortableElement) {\n if (/enable|disable|destroy/.test(method)) {\n return sortable[method](sortableElement)\n }\n // log deprecation\n ['connectWith', 'disableIEFix'].forEach((configKey) => {\n if (options.hasOwnProperty(configKey) && options[configKey] !== null) {\n console.warn(`HTML5Sortable: You are using the deprecated configuration \"${configKey}\". This will be removed in an upcoming version, make sure to migrate to the new options when updating.`)\n }\n })\n // merge options with default options\n options = Object.assign({}, defaultConfiguration, store(sortableElement).config, options)\n // init data store for sortable\n store(sortableElement).config = options\n // set options on sortable\n _data(sortableElement, 'opts', options)\n // property to define as sortable\n sortableElement.isSortable = true\n // reset sortable\n _reloadSortable(sortableElement)\n // initialize\n const listItems = _filter(sortableElement.children, options.items)\n // create element if user defined a placeholder element as a string\n let customPlaceholder\n if (options.placeholder !== null && options.placeholder !== undefined) {\n let tempContainer = document.createElement(sortableElement.tagName)\n if (options.placeholder instanceof HTMLElement) {\n tempContainer.appendChild(options.placeholder)\n } else {\n tempContainer.innerHTML = options.placeholder\n }\n customPlaceholder = tempContainer.children[0]\n }\n // add placeholder\n store(sortableElement).placeholder = _makePlaceholder(sortableElement, customPlaceholder, options.placeholderClass)\n\n _data(sortableElement, 'items', options.items)\n\n if (options.acceptFrom) {\n _data(sortableElement, 'acceptFrom', options.acceptFrom)\n } else if (options.connectWith) {\n _data(sortableElement, 'connectWith', options.connectWith)\n }\n\n _enableSortable(sortableElement)\n _attr(listItems, 'role', 'option')\n _attr(listItems, 'aria-grabbed', 'false')\n // enable hover class\n enableHoverClass(sortableElement, true)\n /*\n Handle drag events on draggable items\n Handle is set at the sortableElement level as it will bubble up\n from the item\n */\n _on(sortableElement, 'dragstart', function (e) {\n // ignore dragstart events\n const target = getEventTarget(e)\n if (target.isSortable === true) {\n return\n }\n e.stopImmediatePropagation()\n\n if ((options.handle && !target.matches(options.handle)) || target.getAttribute('draggable') === 'false') {\n return\n }\n\n const sortableContainer = findSortable(target, e)\n const dragItem = findDragElement(sortableContainer, target)\n\n // grab values\n originItemsBeforeUpdate = _filter(sortableContainer.children, options.items)\n originIndex = originItemsBeforeUpdate.indexOf(dragItem)\n originElementIndex = _index(dragItem, sortableContainer.children)\n originContainer = sortableContainer\n\n // add transparent clone or other ghost to cursor\n setDragImage(e, dragItem, options.customDragImage)\n // cache selsection & add attr for dragging\n draggingHeight = _getElementHeight(dragItem)\n dragItem.classList.add(options.draggingClass)\n dragging = _getDragging(dragItem, sortableContainer)\n _attr(dragging, 'aria-grabbed', 'true')\n\n // dispatch sortstart event on each element in group\n sortableContainer.dispatchEvent(new CustomEvent('sortstart', {\n detail: {\n origin: {\n elementIndex: originElementIndex,\n index: originIndex,\n container: originContainer\n },\n item: dragging,\n originalTarget: target\n }\n }))\n })\n\n /*\n We are capturing targetSortable before modifications with 'dragenter' event\n */\n _on(sortableElement, 'dragenter', (e) => {\n const target = getEventTarget(e)\n const sortableContainer = findSortable(target, e)\n\n if (sortableContainer && sortableContainer !== previousContainer) {\n destinationItemsBeforeUpdate = _filter(sortableContainer.children, _data(sortableContainer, 'items'))\n .filter(item => item !== store(sortableElement).placeholder)\n\n sortableContainer.dispatchEvent(new CustomEvent('sortenter', {\n detail: {\n origin: {\n elementIndex: originElementIndex,\n index: originIndex,\n container: originContainer\n },\n destination: {\n container: sortableContainer,\n itemsBeforeUpdate: destinationItemsBeforeUpdate\n },\n item: dragging,\n originalTarget: target\n }\n }))\n }\n\n previousContainer = sortableContainer\n })\n\n /*\n * Dragend Event - https://developer.mozilla.org/en-US/docs/Web/Events/dragend\n * Fires each time dragEvent end, or ESC pressed\n * We are using it to clean up any draggable elements and placeholders\n */\n _on(sortableElement, 'dragend', function (e) {\n if (!dragging) {\n return\n }\n\n dragging.classList.remove(options.draggingClass)\n _attr(dragging, 'aria-grabbed', 'false')\n\n if (dragging.getAttribute('aria-copied') === 'true' && _data(dragging, 'dropped') !== 'true') {\n dragging.remove()\n }\n\n dragging.style.display = dragging.oldDisplay\n delete dragging.oldDisplay\n\n const visiblePlaceholder = Array.from(stores.values()).map(data => data.placeholder)\n .filter(placeholder => placeholder instanceof HTMLElement)\n .filter(isInDom)[0]\n\n if (visiblePlaceholder) {\n visiblePlaceholder.remove()\n }\n\n // dispatch sortstart event on each element in group\n sortableElement.dispatchEvent(new CustomEvent('sortstop', {\n detail: {\n origin: {\n elementIndex: originElementIndex,\n index: originIndex,\n container: originContainer\n },\n item: dragging\n }\n }))\n\n previousContainer = null\n dragging = null\n draggingHeight = null\n })\n\n /*\n * Drop Event - https://developer.mozilla.org/en-US/docs/Web/Events/drop\n * Fires when valid drop target area is hit\n */\n _on(sortableElement, 'drop', function (e) {\n if (!_listsConnected(sortableElement, dragging.parentElement)) {\n return\n }\n e.preventDefault()\n e.stopPropagation()\n\n _data(dragging, 'dropped', 'true')\n // get the one placeholder that is currently visible\n const visiblePlaceholder = Array.from(stores.values()).map((data) => {\n return data.placeholder\n })\n // filter only HTMLElements\n .filter(placeholder => placeholder instanceof HTMLElement)\n // filter only elements in DOM\n .filter(isInDom)[0]\n // attach element after placeholder\n _after(visiblePlaceholder, dragging)\n // remove placeholder from dom\n visiblePlaceholder.remove()\n\n /*\n * Fires Custom Event - 'sortstop'\n */\n sortableElement.dispatchEvent(new CustomEvent('sortstop', {\n detail: {\n origin: {\n elementIndex: originElementIndex,\n index: originIndex,\n container: originContainer\n },\n item: dragging\n }\n }))\n\n const placeholder = store(sortableElement).placeholder\n const originItems = _filter(originContainer.children, options.items)\n .filter(item => item !== placeholder)\n const destinationContainer = this.isSortable === true ? this : this.parentElement\n const destinationItems = _filter(destinationContainer.children, _data(destinationContainer, 'items'))\n .filter(item => item !== placeholder)\n const destinationElementIndex = _index(dragging, Array.from(dragging.parentElement.children)\n .filter(item => item !== placeholder))\n const destinationIndex = _index(dragging, destinationItems)\n\n /*\n * When a list item changed container lists or index within a list\n * Fires Custom Event - 'sortupdate'\n */\n if (originElementIndex !== destinationElementIndex || originContainer !== destinationContainer) {\n sortableElement.dispatchEvent(new CustomEvent('sortupdate', {\n detail: {\n origin: {\n elementIndex: originElementIndex,\n index: originIndex,\n container: originContainer,\n itemsBeforeUpdate: originItemsBeforeUpdate,\n items: originItems\n },\n destination: {\n index: destinationIndex,\n elementIndex: destinationElementIndex,\n container: destinationContainer,\n itemsBeforeUpdate: destinationItemsBeforeUpdate,\n items: destinationItems\n },\n item: dragging\n }\n }))\n }\n })\n\n const debouncedDragOverEnter = _debounce((sortableElement, element, pageY) => {\n if (!dragging) {\n return\n }\n\n // set placeholder height if forcePlaceholderSize option is set\n if (options.forcePlaceholderSize) {\n store(sortableElement).placeholder.style.height = draggingHeight + 'px'\n }\n // if element the draggedItem is dragged onto is within the array of all elements in list\n // (not only items, but also disabled, etc.)\n if (Array.from(sortableElement.children).indexOf(element) > -1) {\n const thisHeight = _getElementHeight(element)\n const placeholderIndex = _index(store(sortableElement).placeholder, element.parentElement.children)\n const thisIndex = _index(element, element.parentElement.children)\n // Check if `element` is bigger than the draggable. If it is, we have to define a dead zone to prevent flickering\n if (thisHeight > draggingHeight) {\n // Dead zone?\n const deadZone = thisHeight - draggingHeight\n const offsetTop = _offset(element).top\n if (placeholderIndex < thisIndex && pageY < offsetTop) {\n return\n }\n if (placeholderIndex > thisIndex &&\n pageY > offsetTop + thisHeight - deadZone) {\n return\n }\n }\n\n if (dragging.oldDisplay === undefined) {\n dragging.oldDisplay = dragging.style.display\n }\n\n if (dragging.style.display !== 'none') {\n dragging.style.display = 'none'\n }\n // To avoid flicker, determine where to position the placeholder\n // based on where the mouse pointer is relative to the elements\n // vertical center.\n let placeAfter = false\n try {\n let elementMiddle = _offset(element).top + element.offsetHeight / 2\n placeAfter = pageY >= elementMiddle\n } catch (e) {\n placeAfter = placeholderIndex < thisIndex\n }\n\n if (placeAfter) {\n _after(element, store(sortableElement).placeholder)\n } else {\n _before(element, store(sortableElement).placeholder)\n }\n // get placeholders from all stores & remove all but current one\n Array.from(stores.values())\n // remove empty values\n .filter(data => data.placeholder !== undefined)\n // foreach placeholder in array if outside of current sorableContainer -> remove from DOM\n .forEach((data) => {\n if (data.placeholder !== store(sortableElement).placeholder) {\n data.placeholder.remove()\n }\n })\n } else {\n // get all placeholders from store\n let placeholders = Array.from(stores.values())\n .filter((data) => data.placeholder !== undefined)\n .map((data) => {\n return data.placeholder\n })\n // check if element is not in placeholders\n if (placeholders.indexOf(element) === -1 && sortableElement === element && !_filter(element.children, options.items).length) {\n placeholders.forEach((element) => element.remove())\n element.appendChild(store(sortableElement).placeholder)\n }\n }\n }, options.debounce)\n // Handle dragover and dragenter events on draggable items\n const onDragOverEnter = function (e) {\n let element = e.target\n const sortableElement = element.isSortable === true ? element : findSortable(element, e)\n element = findDragElement(sortableElement, element)\n if (!dragging || !_listsConnected(sortableElement, dragging.parentElement) || _data(sortableElement, '_disabled') === 'true') {\n return\n }\n const options = _data(sortableElement, 'opts')\n if (parseInt(options.maxItems) && _filter(sortableElement.children, _data(sortableElement, 'items')).length >= parseInt(options.maxItems) && dragging.parentElement !== sortableElement) {\n return\n }\n e.preventDefault()\n e.stopPropagation()\n e.dataTransfer.dropEffect = store(sortableElement).getConfig('copy') === true ? 'copy' : 'move'\n debouncedDragOverEnter(sortableElement, element, e.pageY)\n }\n\n _on(listItems.concat(sortableElement), 'dragover', onDragOverEnter)\n _on(listItems.concat(sortableElement), 'dragenter', onDragOverEnter)\n })\n\n return sortableElements\n}\n\nsortable.destroy = function (sortableElement) {\n _destroySortable(sortableElement)\n}\n\nsortable.enable = function (sortableElement) {\n _enableSortable(sortableElement)\n}\n\nsortable.disable = function (sortableElement) {\n _disableSortable(sortableElement)\n}\n\n/* START.TESTS_ONLY */\nsortable.__testing = {\n // add internal methods here for testing purposes\n _data: _data,\n _removeItemEvents: _removeItemEvents,\n _removeItemData: _removeItemData,\n _removeSortableData: _removeSortableData\n}\n/* END.TESTS_ONLY */\n","/* eslint-env browser */\nimport store from './store'\nimport _filter from './filter'\nimport _throttle from './throttle'\nimport { addEventListener as _on, removeEventListener as _off } from './eventListener'\n/**\n * enable or disable hoverClass on mouseenter/leave if container Items\n * @param {sortable} sortableContainer a valid sortableContainer\n * @param {boolean} enable enable or disable event\n */\n// export default (sortableContainer: sortable, enable: boolean) => {\nexport default (sortableContainer: sortable, enable: boolean) => {\n if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') {\n let hoverClasses = store(sortableContainer).getConfig('hoverClass').split(' ')\n // add class on hover\n if (enable === true) {\n _on(sortableContainer, 'mousemove', _throttle((event) => {\n // check of no mouse button was pressed when mousemove started == no drag\n if (event.buttons === 0) {\n _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(item => {\n if (item !== event.target) {\n item.classList.remove(...hoverClasses)\n } else {\n item.classList.add(...hoverClasses)\n }\n })\n }\n }, store(sortableContainer).getConfig('throttleTime')))\n // remove class on leave\n _on(sortableContainer, 'mouseleave', () => {\n _filter(sortableContainer.children, store(sortableContainer).getConfig('items')).forEach(item => {\n item.classList.remove(...hoverClasses)\n })\n })\n // remove events\n } else {\n _off(sortableContainer, 'mousemove')\n _off(sortableContainer, 'mouseleave')\n }\n }\n}\n","/**\n * make sure a function is only called once within the given amount of time\n * @param {Function} fn the function to throttle\n * @param {number} threshold time limit for throttling\n */\n// must use function to keep this context\nexport default function (fn: Function, threshold: number = 250) {\n // check function\n if (typeof fn !== 'function') {\n throw new Error('You must provide a function as the first argument for throttle.')\n }\n // check threshold\n if (typeof threshold !== 'number') {\n throw new Error('You must provide a number as the second argument for throttle.')\n }\n\n let lastEventTimestamp = null\n\n return (...args) => {\n let now = Date.now()\n if (lastEventTimestamp === null || now - lastEventTimestamp >= threshold) {\n lastEventTimestamp = now\n fn.apply(this, args)\n }\n }\n}\n"],"names":["addData","element","key","value","undefined","h5s","data","nodes","selector","NodeList","HTMLCollection","Array","Error","from","filter","item","nodeType","matches","stores","Map","this","Object","Store","config","_config","forEach","mergedConfig","assign","entries","has","set","get","_placeholder","placeholder","HTMLElement","_data","delete","sortableElement","addEventListener","eventName","callback","i","length","store","setData","removeEventListener","getData","deleteData","addAttribute","attribute","setAttribute","removeAttribute","parentElement","getClientRects","rect","left","window","pageXOffset","right","top","pageYOffset","bottom","func","wait","timeout","_i","args","clearTimeout","setTimeout","elementList","indexOf","parentNode","insertNode","referenceNode","newElement","position","insertBefore","nextElementSibling","target","insertAfter","sortableContainer","customItemSerializer","customContainerSerializer","serializedItem","serializedContainer","isSortable","items","children","serializedItems","map","parent","node","html","outerHTML","index","container","itemCount","placeholderClass","includes","tagName","document","createElement","innerHTML","_a","classList","add","split","style","getComputedStyle","int","parseInt","getPropertyValue","isNaN","reduce","sum","querySelector","shadowRoot","event","composedPath","defaultDragImage","draggedElement","elementOffset","posX","pageX","posY","pageY","customDragImage","Event","dataTransfer","setDragImage","dragImage","offset","effectAllowed","getEventTarget","id","destination","origin","acceptFrom","getConfig","sel","connectWith","disableIEFix","copy","draggingClass","hoverClass","debounce","throttleTime","maxItems","itemSerializer","containerSerializer","dragging","draggingHeight","originContainer","originIndex","originElementIndex","originItemsBeforeUpdate","previousContainer","destinationItemsBeforeUpdate","enable","hoverClasses_1","_on","fn","threshold","lastEventTimestamp","now","Date","apply","_this","_throttle","buttons","_filter","remove","_b","_off","_removeItemEvents","_getDragging","draggedItem","sortable","ditem","_attr","cloneNode","appendChild","display","oldDisplay","_removeSortableData","_removeAttr","_removeItemData","findSortable","find","el","findDragElement","options","itemlist","ele","contains","_enableSortable","opts","handles","_getHandles","handle","dragDrop","_reloadSortable","sortableElements","method","String","querySelectorAll","prototype","slice","call","test","_serialize","configKey","hasOwnProperty","console","warn","defaultConfiguration","customPlaceholder","listItems","tempContainer","_makePlaceholder","enableHoverClass","e","stopImmediatePropagation","getAttribute","dragItem","_index","_getElementHeight","dispatchEvent","CustomEvent","detail","elementIndex","originalTarget","itemsBeforeUpdate","visiblePlaceholder","values","isInDom","_listsConnected","preventDefault","stopPropagation","_after","originItems","destinationContainer","destinationItems","destinationElementIndex","destinationIndex","debouncedDragOverEnter","_debounce","forcePlaceholderSize","height","thisHeight","placeholderIndex","thisIndex","deadZone","offsetTop","_offset","placeAfter","offsetHeight","_before","placeholders","onDragOverEnter","dropEffect","concat","destroy","disable","__testing"],"mappings":"qCAQA,SAASA,EAASC,EAAsBC,EAAaC,GACnD,QAAcC,IAAVD,EACF,OAAOF,GAAWA,EAAQI,KAAOJ,EAAQI,IAAIC,MAAQL,EAAQI,IAAIC,KAAKJ,GAEtED,EAAQI,IAAMJ,EAAQI,KAAO,GAC7BJ,EAAQI,IAAIC,KAAOL,EAAQI,IAAIC,MAAQ,GACvCL,EAAQI,IAAIC,KAAKJ,GAAOC,iBCPZI,EAAmDC,GACjE,KAAMD,aAAiBE,UAAYF,aAAiBG,gBAAkBH,aAAiBI,OACrF,MAAM,IAAIC,MAAM,gFAElB,MAAwB,iBAAbJ,EACFG,MAAME,KAAKN,GAGbI,MAAME,KAAKN,GAAOO,OAAO,SAACC,GAAS,OAAkB,IAAlBA,EAAKC,UAAkBD,EAAKE,QAAQT,MCdrEU,EAAkC,IAAIC,iBAKjD,aACUC,aAA4B,IAAID,IAChCC,uBAA6BhB,EAC7BgB,WAA0B,IAAID,IAgHxC,OA1GEE,sBAAIC,0BAcJ,WAEE,IAAIC,EAAS,GAKb,OAJAH,KAAKI,QAAQC,QAAQ,SAACtB,EAAOD,GAC3BqB,EAAOrB,GAAOC,IAGToB,OArBT,SAAYA,GACV,GAAsB,iBAAXA,EACT,MAAM,IAAIX,MAAM,uEAGlB,IAAIc,EAAeL,OAAOM,OAAO,GAAIJ,GAErCH,KAAKI,QAAU,IAAIL,IAAIE,OAAOO,QAAQF,qCAuBxCJ,sBAAA,SAAWpB,EAAaC,GACtB,IAAKiB,KAAKI,QAAQK,IAAI3B,GACpB,MAAM,IAAIU,MAAM,6CAA6CV,GAG/DkB,KAAKI,QAAQM,IAAI5B,EAAKC,IAQxBmB,sBAAA,SAAWpB,GACT,IAAKkB,KAAKI,QAAQK,IAAI3B,GACpB,MAAM,IAAIU,MAAM,yCAAyCV,GAE3D,OAAOkB,KAAKI,QAAQO,IAAI7B,IAO1BmB,sBAAIC,+BAAJ,WACE,OAAOF,KAAKY,kBAQd,SAAiBC,GACf,KAAMA,aAAuBC,cAAgC,OAAhBD,EAC3C,MAAM,IAAIrB,MAAM,kDAElBQ,KAAKY,aAAeC,mCAStBX,oBAAA,SAASpB,EAAaC,GACpB,GAAmB,iBAARD,EACT,MAAM,IAAIU,MAAM,6BAElBQ,KAAKe,MAAML,IAAI5B,EAAKC,IAQtBmB,oBAAA,SAASpB,GACP,GAAmB,iBAARA,EACT,MAAM,IAAIU,MAAM,6BAElB,OAAOQ,KAAKe,MAAMJ,IAAI7B,IAQxBoB,uBAAA,SAAYpB,GACV,GAAmB,iBAARA,EACT,MAAM,IAAIU,MAAM,6BAElB,OAAOQ,KAAKe,MAAMC,OAAOlC,oBAObmC,GAEd,KAAMA,aAA2BH,aAC/B,MAAM,IAAItB,MAAM,oDAOlB,OAJKM,EAAOW,IAAIQ,IACdnB,EAAOY,IAAIO,EAAiB,IAAIf,GAG3BJ,EAAOa,IAAIM,IClIpB,SAASC,EAAkBrC,EAAyCsC,EAAkBC,GACpF,GAAIvC,aAAmBU,MACrB,IAAK,IAAI8B,EAAI,EAAGA,EAAIxC,EAAQyC,SAAUD,EACpCH,EAAiBrC,EAAQwC,GAAIF,EAAWC,QAI5CvC,EAAQqC,iBAAiBC,EAAWC,GACpCG,EAAM1C,GAAS2C,QAAQ,QAAQL,EAAaC,GAM9C,SAASK,EAAqB5C,EAAyCsC,GACrE,GAAItC,aAAmBU,MACrB,IAAK,IAAI8B,EAAI,EAAGA,EAAIxC,EAAQyC,SAAUD,EACpCI,EAAoB5C,EAAQwC,GAAIF,QAIpCtC,EAAQ4C,oBAAoBN,EAAWI,EAAM1C,GAAS6C,QAAQ,QAAQP,IACtEI,EAAM1C,GAAS8C,WAAW,QAAQR,GCvBpC,SAASS,EAAc/C,EAAyCgD,EAAkB9C,GAChF,GAAIF,aAAmBU,MACrB,IAAK,IAAI8B,EAAI,EAAGA,EAAIxC,EAAQyC,SAAUD,EACpCO,EAAa/C,EAAQwC,GAAIQ,EAAW9C,QAIxCF,EAAQiD,aAAaD,EAAW9C,GAMlC,SAASgD,EAAiBlD,EAAyCgD,GACjE,GAAIhD,aAAmBU,MACrB,IAAK,IAAI8B,EAAI,EAAGA,EAAIxC,EAAQyC,SAAUD,EACpCU,EAAgBlD,EAAQwC,GAAIQ,QAIhChD,EAAQkD,gBAAgBF,kBCrBVhD,GACd,IAAKA,EAAQmD,eAAqD,IAApCnD,EAAQoD,iBAAiBX,OACrD,MAAM,IAAI9B,MAAM,0CAGlB,IAAI0C,EAAOrD,EAAQoD,iBAAiB,GACpC,MAAO,CACLE,KAAMD,EAAKC,KAAOC,OAAOC,YACzBC,MAAOJ,EAAKI,MAAQF,OAAOC,YAC3BE,IAAKL,EAAKK,IAAMH,OAAOI,YACvBC,OAAQP,EAAKO,OAASL,OAAOI,yBCRjBE,EAAgBC,GAC9B,IAAIC,EACJ,oBAF8BD,KAEvB,eAAC,aAAAE,mBAAAA,IAAAC,kBACNC,aAAaH,GACbA,EAAUI,WAAW,WACnBN,eAAQI,IACPH,gBCNS9D,EAAsBoE,GACpC,KAAMpE,aAAmBiC,cAAkBmC,aAAuB5D,UAAY4D,aAAuB3D,gBAAkB2D,aAAuB1D,QAC5I,MAAM,IAAIC,MAAM,uDAGlB,OAAOD,MAAME,KAAKwD,GAAaC,QAAQrE,eCLzBA,GACd,KAAMA,aAAmBiC,aACvB,MAAM,IAAItB,MAAM,kCAGlB,OAA8B,OAAvBX,EAAQsE,YCJbC,EAAa,SAACC,EAA4BC,EAAyBC,GACrE,KAAMF,aAAyBvC,aAAkBuC,EAAcrB,yBAAyBlB,aACtF,MAAM,IAAItB,MAAM,qCAElB6D,EAAcrB,cAAcwB,aAC1BF,EACc,WAAbC,EAAwBF,EAAgBA,EAAcI,qBAQvDD,EAAe,SAACE,EAAqB7E,GAAyB,OAAAuE,EAAWM,EAAQ7E,EAAS,WAM1F8E,EAAc,SAACD,EAAqB7E,GAAyB,OAAAuE,EAAWM,EAAQ7E,EAAS,qBCjB7E+E,EAAgCC,EAAqHC,GAEnK,gBAF8CD,WAAkCE,EAAgCH,GAAmC,OAAAG,iBAAgBD,WAAuCE,GAAgC,OAAAA,MAEpOJ,aAA6B9C,eAAkD,IAAjC8C,EAAkBK,WACpE,MAAM,IAAIzE,MAAM,6DAGlB,GAAoC,mBAAzBqE,GAA4E,mBAA9BC,EACvD,MAAM,IAAItE,MAAM,uEAGlB,IAEIG,EAFUoB,EAAM6C,EAAmB,QAEFM,MAGjCA,EAAQxE,EAAOkE,EAAkBO,SAAUxE,GAC3CyE,EAAoCF,EAAMG,IAAI,SAAC1E,GACjD,MAAO,CACL2E,OAAQV,EACRW,KAAM5E,EACN6E,KAAM7E,EAAK8E,UACXC,MAAOA,EAAM/E,EAAMuE,MASvB,MAAO,CACLS,UAAWb,EANG,CACdS,KAAMX,EACNgB,UAAWR,EAAgB9C,SAK3B4C,MAAOE,EAAgBC,IAAI,SAAC1E,GAAiB,OAAAkE,EAAqBlE,EAAMiE,kBCnC5D3C,EAA8BJ,EAA2BgE,SACvE,gBADuEA,4BACjE5D,aAA2BH,aAC/B,MAAM,IAAItB,MAAM,mDAGlB,KAAMqB,aAAuBC,mBAAgC9B,IAAhB6B,EAC3C,MAAM,IAAIrB,MAAM,6EAmBlB,YAhBoBR,IAAhB6B,IACE,CAAC,KAAM,MAAMiE,SAAS7D,EAAgB8D,SACxClE,EAAcmE,SAASC,cAAc,MAC5B,CAAC,QAAS,SAASH,SAAS7D,EAAgB8D,UACrDlE,EAAcmE,SAASC,cAAc,OAEzBC,UAAY,0BAExBrE,EAAcmE,SAASC,cAAc,QAIT,iBAArBJ,IACTM,EAAAtE,EAAYuE,WAAUC,YAAOR,EAAiBS,MAAM,MAG/CzE,cC3BOhC,GACd,KAAMA,aAAmBiC,aACvB,MAAM,IAAItB,MAAM,wCAGlB,IAAI+F,EAAQnD,OAAOoD,iBAAiB3G,GAEpC,MAAO,CAAC,SAAU,cAAe,kBAC9BwF,IAAI,SAACvF,GACJ,IAAI2G,EAAMC,SAASH,EAAMI,iBAAiB7G,GAAM,IAChD,OAAO8G,MAAMH,GAAO,EAAIA,IAEzBI,OAAO,SAACC,EAAK/G,GAAU,OAAA+G,EAAM/G,gBCVlBmF,EAA2B9E,GACzC,KAAM8E,aAAiB3E,OACrB,MAAM,IAAIC,MAAM,4DAGlB,MAAwB,iBAAbJ,EACF8E,EAGFA,EAEJxE,OAAO,SAACC,GACP,OAAOA,EAAKoG,cAAc3G,aAAqB0B,aAC5CnB,EAAKqG,YAAcrG,EAAKqG,WAAWD,cAAc3G,aAAqB0B,cAG1EuD,IAAI,SAAC1E,GACJ,OAAOA,EAAKoG,cAAc3G,IAAcO,EAAKqG,YAAcrG,EAAKqG,WAAWD,cAAc3G,iBCpB/E6G,GACd,OAAQA,EAAMC,cAAgBD,EAAMC,eAAe,IAAOD,EAAMvC,QCK9DyC,EAAmB,SAACC,EAA6BC,EAA6BJ,GAChF,MAAO,CACLpH,QAASuH,EACTE,KAAML,EAAMM,MAAQF,EAAclE,KAClCqE,KAAMP,EAAMQ,MAAQJ,EAAc9D,iBAUtB0D,EAAkBG,EAA6BM,GAE7D,KAAMT,aAAiBU,OACrB,MAAM,IAAInH,MAAM,4DAGlB,KAAM4G,aAA0BtF,aAC9B,MAAM,IAAItB,MAAM,qEAOlB,GAJKkH,IACHA,EAAkBP,GAGhBF,EAAMW,cAAgBX,EAAMW,aAAaC,aAAc,CAEzD,IAEIC,EAAYJ,EAAgBN,EAFZW,EAAOX,GAEoCH,GAE/D,KAAMa,EAAUjI,mBAAmBiC,cAA0C,iBAAnBgG,EAAUR,MAA+C,iBAAnBQ,EAAUN,KACxG,MAAM,IAAIhH,MAAM,uIAGlByG,EAAMW,aAAaI,cAAgB,WAEnCf,EAAMW,aAAapF,QAAQ,aAAcyF,EAAehB,GAAOiB,IAE/DjB,EAAMW,aAAaC,aAAaC,EAAUjI,QAASiI,EAAUR,KAAMQ,EAAUN,mBC9CjEW,EAAuBC,GAErC,IAA+B,IAA3BD,EAAYlD,WAAqB,CACnC,IAAMoD,EAAa9F,EAAM4F,GAAaG,UAAU,cAEhD,GAAmB,OAAfD,IAAsC,IAAfA,GAA8C,iBAAfA,EACxD,MAAM,IAAI7H,MAAM,oGAGlB,GAAmB,OAAf6H,EACF,OAAsB,IAAfA,GAEK,EAFmBA,EAAW/B,MAAM,KAAK5F,OAAO,SAAU6H,GACpE,OAAoB,EAAbA,EAAIjG,QAAc8F,EAAOvH,QAAQ0H,KACvCjG,OAGL,GAAI6F,IAAgBC,EAClB,OAAO,EAGT,QAAoDpI,IAAhDuC,EAAM4F,GAAaG,UAAU,gBAAgF,OAAhD/F,EAAM4F,GAAaG,UAAU,eAC5F,OAAO/F,EAAM4F,GAAaG,UAAU,iBAAmB/F,EAAM6F,GAAQE,UAAU,eAGnF,OAAO,KC1BM,CACbpD,MAAO,KAEPsD,YAAa,KAEbC,aAAc,KACdJ,WAAY,KACZK,MAAM,EACN7G,YAAa,KACbgE,iBAAkB,uBAClB8C,cAAe,oBACfC,YAAY,EACZC,SAAU,EACVC,aAAc,IACdC,SAAU,EACVC,oBAAgBhJ,EAChBiJ,yBAAqBjJ,EACrB0H,gBAAiB,UCKfwB,EACAC,EAQAC,EACAC,EACAC,EACAC,EAIAC,EAGAC,aCjCY7E,EAA6B8E,GAC3C,GAAgE,iBAArDnH,EAAMqC,GAAmB0D,UAAU,cAA4B,CACxE,IAAIqB,EAAepH,EAAMqC,GAAmB0D,UAAU,cAAchC,MAAM,MAE3D,IAAXoD,GACFE,EAAIhF,EAAmB,qBCVJiF,EAAcC,GAAvC,WAEE,gBAFqCA,OAEnB,mBAAPD,EACT,MAAM,IAAIrJ,MAAM,mEAGlB,GAAyB,iBAAdsJ,EACT,MAAM,IAAItJ,MAAM,kEAGlB,IAAIuJ,EAAqB,KAEzB,OAAO,eAAC,aAAAlG,mBAAAA,IAAAC,kBACN,IAAIkG,EAAMC,KAAKD,OACY,OAAvBD,GAA2DD,GAA5BE,EAAMD,KACvCA,EAAqBC,EACrBH,EAAGK,MAAMC,EAAMrG,KDNqBsG,CAAU,SAACnD,GAEvB,IAAlBA,EAAMoD,SACRC,EAAQ1F,EAAkBO,SAAU5C,EAAMqC,GAAmB0D,UAAU,UAAUjH,QAAQ,SAAAV,WACnFA,IAASsG,EAAMvC,QACjByB,EAAAxF,EAAKyF,WAAUmE,eAAUZ,IAEzBa,EAAA7J,EAAKyF,WAAUC,YAAOsD,MAI3BpH,EAAMqC,GAAmB0D,UAAU,kBAEtCsB,EAAIhF,EAAmB,aAAc,WACnC0F,EAAQ1F,EAAkBO,SAAU5C,EAAMqC,GAAmB0D,UAAU,UAAUjH,QAAQ,SAAAV,UACvFwF,EAAAxF,EAAKyF,WAAUmE,eAAUZ,SAK7Bc,EAAK7F,EAAmB,aACxB6F,EAAK7F,EAAmB,iBDaxB8F,EAAoB,SAAUxF,GAClCuF,EAAKvF,EAAO,aACZuF,EAAKvF,EAAO,WACZuF,EAAKvF,EAAO,YACZuF,EAAKvF,EAAO,aACZuF,EAAKvF,EAAO,QACZuF,EAAKvF,EAAO,cACZuF,EAAKvF,EAAO,eASRyF,EAAe,SAAUC,EAAaC,GAC1C,IAAIC,EAAQF,EAQZ,OAP0C,IAAtCrI,EAAMsI,GAAUvC,UAAU,UAE5ByC,EADAD,EAAQF,EAAYI,WAAU,GACjB,cAAe,QAC5BJ,EAAY5H,cAAciI,YAAYH,GACtCA,EAAMvE,MAAM2E,QAAU,OACtBJ,EAAMK,WAAaP,EAAYrE,MAAM2E,SAEhCJ,GAMHM,EAAsB,SAAUP,GlB5DtC,IAAqBhL,GAAAA,EkB6DPgL,GlB5DA5K,YACHJ,EAAQI,IAAIC,KkB4DrBmL,EAAYR,EAAU,oBAMlBS,EAAkB,SAAUpG,GAChCmG,EAAYnG,EAAO,gBACnBmG,EAAYnG,EAAO,eACnBmG,EAAYnG,EAAO,aACnBmG,EAAYnG,EAAO,SAQrB,SAASqG,EAAc1L,EAASoH,GAC9B,GAAIA,EAAMC,aACR,OAAOD,EAAMC,eAAesE,KAAK,SAAAC,GAAM,OAAAA,EAAGxG,aAE5C,MAA8B,IAAvBpF,EAAQoF,YACbpF,EAAUA,EAAQmD,cAEpB,OAAOnD,EAQT,SAAS6L,EAAiBzJ,EAAiBpC,GACzC,IAAM8L,EAAU5J,EAAME,EAAiB,QAEjC2J,EADQtB,EAAQrI,EAAgBkD,SAAUwG,EAAQzG,OACjCxE,OAAO,SAAUmL,GACtC,OAAOA,EAAIC,SAASjM,IAAagM,EAAI7E,YAAc6E,EAAI7E,WAAW8E,SAASjM,KAG7E,OAAyB,EAAlB+L,EAAStJ,OAAasJ,EAAS,GAAK/L,EAM7C,IAmBMkM,EAAkB,SAAU9J,GAChC,IAAM+J,EAAOjK,EAAME,EAAiB,QAC9BiD,EAAQoF,EAAQrI,EAAgBkD,SAAU6G,EAAK9G,OAC/C+G,EAAUC,EAAYhH,EAAO8G,EAAKG,SACxCpB,EAAM9I,EAAiB,kBAAmB,QAC1CF,EAAME,EAAiB,YAAa,SACpC8I,EAAMkB,EAAS,YAAa,SAKF,IAAtBD,EAAKvD,gBAEwB,mBADfzC,UAAY5C,OAAO4C,UAAUC,cAAc,QACzCmG,UAChBxC,EAAIqC,EAAS,YAAa,WACxB,IAA6B,IAAzB/G,EAAMhB,QAAQlD,MAChBA,KAAKoL,eACA,CAEL,IADA,IAAI9G,EAAStE,KAAKgC,eACgB,IAA3BkC,EAAMhB,QAAQoB,IACnBA,EAASA,EAAOtC,cAElBsC,EAAO8G,gBAwBXC,EAAkB,SAAUpK,GAChC,IAAM+J,EAAOjK,EAAME,EAAiB,QAC9BiD,EAAQoF,EAAQrI,EAAgBkD,SAAU6G,EAAK9G,OAC/C+G,EAAUC,EAAYhH,EAAO8G,EAAKG,QACxCpK,EAAME,EAAiB,YAAa,SAEpCyI,EAAkBxF,GAClBuF,EAAKwB,EAAS,aAEdxB,EAAKxI,EAAiB,YACtBwI,EAAKxI,EAAiB,aACtBwI,EAAKxI,EAAiB,kBAQA4I,EAAUyB,EAAkBX,GAElD,IAAMY,EAASC,OAAOb,GAatB,OAZAA,EAAUA,GAAW,GAEW,iBAArBW,IACTA,EAAmBtG,SAASyG,iBAAiBH,IAG3CA,aAA4BxK,cAC9BwK,EAAmB,CAACA,IAGtBA,EAAmB/L,MAAMmM,UAAUC,MAAMC,KAAKN,GAE1C,YAAYO,KAAKN,GACZD,EAAiBjH,IAAI,SAACT,GAC3B,IAAIoH,EAAOjK,EAAM6C,EAAmB,QACpC,OAAOkI,EAAWlI,EAAmBoH,EAAKhD,eAAgBgD,EAAK/C,wBAInEqD,EAAiBjL,QAAQ,SAAUY,GACjC,GAAI,yBAAyB4K,KAAKN,GAChC,OAAO1B,EAAS0B,GAAQtK,GAG1B,CAAC,cAAe,gBAAgBZ,QAAQ,SAAC0L,GACnCpB,EAAQqB,eAAeD,IAAqC,OAAvBpB,EAAQoB,IAC/CE,QAAQC,KAAK,8DAA8DH,8GAI/EpB,EAAU1K,OAAOM,OAAO,GAAI4L,EAAsB5K,EAAMN,GAAiBd,OAAQwK,GAEjFpJ,EAAMN,GAAiBd,OAASwK,EAEhC5J,EAAME,EAAiB,OAAQ0J,GAE/B1J,EAAgBgD,YAAa,EAE7BoH,EAAgBpK,GAEhB,IAEImL,EAFEC,EAAY/C,EAAQrI,EAAgBkD,SAAUwG,EAAQzG,OAG5D,GAA4B,OAAxByG,EAAQ9J,kBAAgD7B,IAAxB2L,EAAQ9J,YAA2B,CACrE,IAAIyL,EAAgBtH,SAASC,cAAchE,EAAgB8D,SACvD4F,EAAQ9J,uBAAuBC,YACjCwL,EAAcrC,YAAYU,EAAQ9J,aAElCyL,EAAcpH,UAAYyF,EAAQ9J,YAEpCuL,EAAoBE,EAAcnI,SAAS,GAG7C5C,EAAMN,GAAiBJ,YAAc0L,EAAiBtL,EAAiBmL,EAAmBzB,EAAQ9F,kBAElG9D,EAAME,EAAiB,QAAS0J,EAAQzG,OAEpCyG,EAAQtD,WACVtG,EAAME,EAAiB,aAAc0J,EAAQtD,YACpCsD,EAAQnD,aACjBzG,EAAME,EAAiB,cAAe0J,EAAQnD,aAGhDuD,EAAgB9J,GAChB8I,EAAMsC,EAAW,OAAQ,UACzBtC,EAAMsC,EAAW,eAAgB,SAEjCG,EAAiBvL,GAAiB,GAMlC2H,EAAI3H,EAAiB,YAAa,SAAUwL,GAE1C,IAAM/I,EAASuD,EAAewF,GAC9B,IAA0B,IAAtB/I,EAAOO,aAGXwI,EAAEC,6BAEG/B,EAAQQ,QAAWzH,EAAO7D,QAAQ8K,EAAQQ,UAAiD,UAArCzH,EAAOiJ,aAAa,cAA/E,CAIA,IAAM/I,EAAoB2G,EAAa7G,EAAQ+I,GACzCG,EAAWlC,EAAgB9G,EAAmBF,GAGpD6E,EAA0Be,EAAQ1F,EAAkBO,SAAUwG,EAAQzG,OACtEmE,EAAcE,EAAwBrF,QAAQ0J,GAC9CtE,EAAqBuE,EAAOD,EAAUhJ,EAAkBO,UACxDiE,EAAkBxE,EAGlBiD,EAAa4F,EAAGG,EAAUjC,EAAQjE,iBAElCyB,EAAiB2E,EAAkBF,GACnCA,EAASxH,UAAUC,IAAIsF,EAAQhD,eAE/BoC,EADA7B,EAAWyB,EAAaiD,EAAUhJ,GAClB,eAAgB,QAGhCA,EAAkBmJ,cAAc,IAAIC,YAAY,YAAa,CAC3DC,OAAQ,CACN7F,OAAQ,CACN8F,aAAc5E,EACd5D,MAAO2D,EACP1D,UAAWyD,GAEbzI,KAAMuI,EACNiF,eAAgBzJ,SAQtBkF,EAAI3H,EAAiB,YAAa,SAACwL,GACjC,IAAM/I,EAASuD,EAAewF,GACxB7I,EAAoB2G,EAAa7G,EAAQ+I,GAE3C7I,GAAqBA,IAAsB4E,IAC7CC,EAA+Ba,EAAQ1F,EAAkBO,SAAUpD,EAAM6C,EAAmB,UACzFlE,OAAO,SAAAC,GAAQ,OAAAA,IAAS4B,EAAMN,GAAiBJ,cAElD+C,EAAkBmJ,cAAc,IAAIC,YAAY,YAAa,CAC3DC,OAAQ,CACN7F,OAAQ,CACN8F,aAAc5E,EACd5D,MAAO2D,EACP1D,UAAWyD,GAEbjB,YAAa,CACXxC,UAAWf,EACXwJ,kBAAmB3E,GAErB9I,KAAMuI,EACNiF,eAAgBzJ,OAKtB8E,EAAoB5E,IAQtBgF,EAAI3H,EAAiB,UAAW,SAAUwL,GACxC,GAAKvE,EAAL,CAIAA,EAAS9C,UAAUmE,OAAOoB,EAAQhD,eAClCoC,EAAM7B,EAAU,eAAgB,SAEa,SAAzCA,EAASyE,aAAa,gBAA4D,SAA/B5L,EAAMmH,EAAU,YACrEA,EAASqB,SAGXrB,EAAS3C,MAAM2E,QAAUhC,EAASiC,kBAC3BjC,EAASiC,WAEhB,IAAMkD,EAAqB9N,MAAME,KAAKK,EAAOwN,UAAUjJ,IAAI,SAAAnF,GAAQ,OAAAA,EAAK2B,cACrEnB,OAAO,SAAAmB,GAAe,OAAAA,aAAuBC,cAC7CpB,OAAO6N,GAAS,GAEfF,GACFA,EAAmB9D,SAIrBtI,EAAgB8L,cAAc,IAAIC,YAAY,WAAY,CACxDC,OAAQ,CACN7F,OAAQ,CACN8F,aAAc5E,EACd5D,MAAO2D,EACP1D,UAAWyD,GAEbzI,KAAMuI,MAMVC,EADAD,EADAM,EAAoB,QAStBI,EAAI3H,EAAiB,OAAQ,SAAUwL,GACrC,GAAKe,EAAgBvM,EAAiBiH,EAASlG,eAA/C,CAGAyK,EAAEgB,iBACFhB,EAAEiB,kBAEF3M,EAAMmH,EAAU,UAAW,QAE3B,IAAMmF,EAAqB9N,MAAME,KAAKK,EAAOwN,UAAUjJ,IAAI,SAACnF,GAC1D,OAAOA,EAAK2B,cAGXnB,OAAO,SAAAmB,GAAe,OAAAA,aAAuBC,cAE7CpB,OAAO6N,GAAS,GAEnBI,EAAON,EAAoBnF,GAE3BmF,EAAmB9D,SAKnBtI,EAAgB8L,cAAc,IAAIC,YAAY,WAAY,CACxDC,OAAQ,CACN7F,OAAQ,CACN8F,aAAc5E,EACd5D,MAAO2D,EACP1D,UAAWyD,GAEbzI,KAAMuI,MAIV,IAAMrH,EAAcU,EAAMN,GAAiBJ,YACrC+M,EAActE,EAAQlB,EAAgBjE,SAAUwG,EAAQzG,OAC3DxE,OAAO,SAAAC,GAAQ,OAAAA,IAASkB,IACrBgN,GAA2C,IAApB7N,KAAKiE,WAAsBjE,KAAOA,KAAKgC,cAC9D8L,EAAmBxE,EAAQuE,EAAqB1J,SAAUpD,EAAM8M,EAAsB,UACzFnO,OAAO,SAAAC,GAAQ,OAAAA,IAASkB,IACrBkN,EAA0BlB,EAAO3E,EAAU3I,MAAME,KAAKyI,EAASlG,cAAcmC,UAChFzE,OAAO,SAAAC,GAAQ,OAAAA,IAASkB,KACrBmN,EAAmBnB,EAAO3E,EAAU4F,GAMtCxF,IAAuByF,GAA2B3F,IAAoByF,GACxE5M,EAAgB8L,cAAc,IAAIC,YAAY,aAAc,CAC1DC,OAAQ,CACN7F,OAAQ,CACN8F,aAAc5E,EACd5D,MAAO2D,EACP1D,UAAWyD,EACXgF,kBAAmB7E,EACnBrE,MAAO0J,GAETzG,YAAa,CACXzC,MAAOsJ,EACPd,aAAca,EACdpJ,UAAWkJ,EACXT,kBAAmB3E,EACnBvE,MAAO4J,GAETnO,KAAMuI,SAMd,IAAM+F,EAAyBC,EAAU,SAACjN,EAAiBpC,EAAS4H,GAClE,GAAKyB,EAUL,GALIyC,EAAQwD,uBACV5M,EAAMN,GAAiBJ,YAAY0E,MAAM6I,OAASjG,EAAiB,OAIR,EAAzD5I,MAAME,KAAKwB,EAAgBkD,UAAUjB,QAAQrE,GAAe,CAC9D,IAAMwP,EAAavB,EAAkBjO,GAC/ByP,EAAmBzB,EAAOtL,EAAMN,GAAiBJ,YAAahC,EAAQmD,cAAcmC,UACpFoK,EAAY1B,EAAOhO,EAASA,EAAQmD,cAAcmC,UAExD,GAAiBgE,EAAbkG,EAA6B,CAE/B,IAAMG,EAAWH,EAAalG,EACxBsG,EAAYC,EAAQ7P,GAAS0D,IACnC,GAAI+L,EAAmBC,GAAa9H,EAAQgI,EAC1C,OAEF,GAAuBF,EAAnBD,GACQG,EAAYJ,EAAaG,EAAjC/H,EACF,YAIwBzH,IAAxBkJ,EAASiC,aACXjC,EAASiC,WAAajC,EAAS3C,MAAM2E,SAGR,SAA3BhC,EAAS3C,MAAM2E,UACjBhC,EAAS3C,MAAM2E,QAAU,QAK3B,IAAIyE,GAAa,EACjB,IAEEA,EADoBD,EAAQ7P,GAAS0D,IAAM1D,EAAQ+P,aAAe,GACrDnI,EACb,MAAOgG,GACPkC,EAAaL,EAAmBC,EAG9BI,EACFhB,EAAO9O,EAAS0C,EAAMN,GAAiBJ,aAEvCgO,EAAQhQ,EAAS0C,EAAMN,GAAiBJ,aAG1CtB,MAAME,KAAKK,EAAOwN,UAEf5N,OAAO,SAAAR,GAAQ,YAAqBF,IAArBE,EAAK2B,cAEpBR,QAAQ,SAACnB,GACJA,EAAK2B,cAAgBU,EAAMN,GAAiBJ,aAC9C3B,EAAK2B,YAAY0I,eAGlB,CAEL,IAAIuF,EAAevP,MAAME,KAAKK,EAAOwN,UAClC5N,OAAO,SAACR,GAAS,YAAqBF,IAArBE,EAAK2B,cACtBwD,IAAI,SAACnF,GACJ,OAAOA,EAAK2B,eAGuB,IAAnCiO,EAAa5L,QAAQrE,IAAmBoC,IAAoBpC,GAAYyK,EAAQzK,EAAQsF,SAAUwG,EAAQzG,OAAO5C,SACnHwN,EAAazO,QAAQ,SAACxB,GAAY,OAAAA,EAAQ0K,WAC1C1K,EAAQoL,YAAY1I,EAAMN,GAAiBJ,gBAG9C8J,EAAQ9C,UAELkH,EAAkB,SAAUtC,GAChC,IAAI5N,EAAU4N,EAAE/I,OACVzC,GAAyC,IAAvBpC,EAAQoF,WAAsBpF,EAAU0L,EAAa1L,EAAS4N,GAEtF,GADA5N,EAAU6L,EAAgBzJ,EAAiBpC,GACtCqJ,GAAasF,EAAgBvM,EAAiBiH,EAASlG,gBAA0D,SAAxCjB,EAAME,EAAiB,aAArG,CAGA,IAAM0J,EAAU5J,EAAME,EAAiB,QACnCyE,SAASiF,EAAQ5C,WAAauB,EAAQrI,EAAgBkD,SAAUpD,EAAME,EAAiB,UAAUK,QAAUoE,SAASiF,EAAQ5C,WAAaG,EAASlG,gBAAkBf,IAGxKwL,EAAEgB,iBACFhB,EAAEiB,kBACFjB,EAAE7F,aAAaoI,YAA0D,IAA7CzN,EAAMN,GAAiBqG,UAAU,QAAmB,OAAS,OACzF2G,EAAuBhN,EAAiBpC,EAAS4N,EAAEhG,UAGrDmC,EAAIyD,EAAU4C,OAAOhO,GAAkB,WAAY8N,GACnDnG,EAAIyD,EAAU4C,OAAOhO,GAAkB,YAAa8N,KAG/CzD,UAGTzB,EAASqF,QAAU,SAAUjO,GAxcJ,IAAUA,EAC3B+J,EACA9G,EACA+G,EAFAD,EAAOjK,EADoBE,EAychBA,EAxcmB,SAAW,GACzCiD,EAAQoF,EAAQrI,EAAgBkD,SAAU6G,EAAK9G,OAC/C+G,EAAUC,EAAYhH,EAAO8G,EAAKG,QAExC1B,EAAKxI,EAAiB,YACtBwI,EAAKxI,EAAiB,aACtBwI,EAAKxI,EAAiB,QAEtBmJ,EAAoBnJ,GAEpBwI,EAAKwB,EAAS,aACdvB,EAAkBxF,GAClBoG,EAAgBpG,IA+blB2F,EAASnB,OAAS,SAAUzH,GAC1B8J,EAAgB9J,IAGlB4I,EAASsF,QAAU,SAAUlO,GA7ZJ,IAAUA,EAC3B+J,EACA9G,EACA+G,EAFAD,EAAOjK,EADoBE,EA8ZhBA,EA7ZmB,QAC9BiD,EAAQoF,EAAQrI,EAAgBkD,SAAU6G,EAAK9G,OAC/C+G,EAAUC,EAAYhH,EAAO8G,EAAKG,QACxCpB,EAAM9I,EAAiB,kBAAmB,QAC1CF,EAAME,EAAiB,YAAa,QACpC8I,EAAMkB,EAAS,YAAa,SAC5BxB,EAAKwB,EAAS,cA2ZhBpB,EAASuF,UAAY,CAEnBrO,MAAOA,EACP2I,kBAAmBA,EACnBY,gBAAiBA,EACjBF,oBAAqBA"} \ No newline at end of file