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..cb706baf --- /dev/null +++ b/dist/html5sortable.amd.js @@ -0,0 +1,1298 @@ +/* + * 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 */ + /* eslint-disable no-use-before-define */ + var stores = new Map(); + /* eslint-enable no-use-before-define */ + /** + * 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: false, + 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: false, + 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 getIndex = (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: getIndex(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) { + var _a; + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + 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); + // get only height if element has box-sizing: border-box specified + if (style.getPropertyValue('box-sizing') === 'border-box') { + return parseInt(style.getPropertyValue('height'), 10); + } + // 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 width of an element including padding + * @param {HTMLElement} element an dom element + */ + var getElementWidth = (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 ['width', 'padding-left', 'padding-right'] + .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, + dropTargetContainerClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null, + orientation: 'vertical' + }; + + /** + * 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 + */ + 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; + var draggingWidth; + /* + * 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'); + }; + // Remove container events + var removeContainerEvents = function (originContainer, previousContainer) { + if (originContainer) { + removeEventListener(originContainer, 'dragleave'); + } + if (previousContainer && (previousContainer !== originContainer)) { + removeEventListener(previousContainer, 'dragleave'); + } + }; + /** + * 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); + // disable adding hover class + enableHoverClass(sortableElement, false); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'dragstart'); + removeEventListener(sortableElement, 'dragend'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + removeItemEvents(items); + removeItemData(items); + removeContainerEvents(originContainer, previousContainer); + // clear sortable flag + sortableElement.isSortable = false; + }; + /** + * 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'); + // enable hover class + enableHoverClass(sortableElement, 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'); + enableHoverClass(sortableElement, false); + }; + /** + * 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); + removeContainerEvents(originContainer, previousContainer); + 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 (Object.prototype.hasOwnProperty.call(options, 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'); + /* + 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 = getIndex(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); + draggingWidth = getElementWidth(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; }); + if (options.dropTargetContainerClass) { + sortableContainer.classList.add(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + addEventListener(sortableContainer, 'dragleave', function (e) { + // TODO: rename outTarget to be more self-explanatory + // e.fromElement for very old browsers, similar to relatedTarget + var outTarget = e.relatedTarget || e.fromElement; + if (!e.currentTarget.contains(outTarget)) { + if (options.dropTargetContainerClass) { + sortableContainer.classList.remove(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortleave', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: sortableContainer + }, + 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; + draggingWidth = 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; }) + // 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 = getIndex(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = getIndex(dragging, destinationItems); + if (options.dropTargetContainerClass) { + destinationContainer.classList.remove(options.dropTargetContainerClass); + } + /* + * 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, pageX, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + store(sortableElement).placeholder.style.width = draggingWidth + '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 thisWidth = getElementWidth(element); + var placeholderIndex = getIndex(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = getIndex(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 || thisWidth > draggingWidth) { + // Dead zone? + var deadZoneVertical = thisHeight - draggingHeight; + var deadZoneHorizontal = thisWidth - draggingWidth; + var offsetTop = offset(element).top; + var offsetLeft = offset(element).left; + if (placeholderIndex < thisIndex && + ((options.orientation === 'vertical' && pageY < offsetTop) || + (options.orientation === 'horizontal' && pageX < offsetLeft))) { + return; + } + if (placeholderIndex > thisIndex && + ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) || + (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) { + 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 elementMiddleVertical = offset(element).top + element.offsetHeight / 2; + var elementMiddleHorizontal = offset(element).left + element.offsetWidth / 2; + placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) || + (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal)); + } + 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.pageX, 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, + removeContainerEvents: removeContainerEvents + }; + + return sortable; + +}); diff --git a/dist/html5sortable.cjs.js b/dist/html5sortable.cjs.js new file mode 100644 index 00000000..95f68b3b --- /dev/null +++ b/dist/html5sortable.cjs.js @@ -0,0 +1,1296 @@ +/* + * 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 */ +/* eslint-disable no-use-before-define */ +var stores = new Map(); +/* eslint-enable no-use-before-define */ +/** + * 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: false, + 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: false, + 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 getIndex = (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: getIndex(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) { + var _a; + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + 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); + // get only height if element has box-sizing: border-box specified + if (style.getPropertyValue('box-sizing') === 'border-box') { + return parseInt(style.getPropertyValue('height'), 10); + } + // 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 width of an element including padding + * @param {HTMLElement} element an dom element + */ +var getElementWidth = (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 ['width', 'padding-left', 'padding-right'] + .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, + dropTargetContainerClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null, + orientation: 'vertical' +}; + +/** + * 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 + */ +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; +var draggingWidth; +/* + * 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'); +}; +// Remove container events +var removeContainerEvents = function (originContainer, previousContainer) { + if (originContainer) { + removeEventListener(originContainer, 'dragleave'); + } + if (previousContainer && (previousContainer !== originContainer)) { + removeEventListener(previousContainer, 'dragleave'); + } +}; +/** + * 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); + // disable adding hover class + enableHoverClass(sortableElement, false); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'dragstart'); + removeEventListener(sortableElement, 'dragend'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + removeItemEvents(items); + removeItemData(items); + removeContainerEvents(originContainer, previousContainer); + // clear sortable flag + sortableElement.isSortable = false; +}; +/** + * 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'); + // enable hover class + enableHoverClass(sortableElement, 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'); + enableHoverClass(sortableElement, false); +}; +/** + * 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); + removeContainerEvents(originContainer, previousContainer); + 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 (Object.prototype.hasOwnProperty.call(options, 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'); + /* + 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 = getIndex(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); + draggingWidth = getElementWidth(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; }); + if (options.dropTargetContainerClass) { + sortableContainer.classList.add(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + addEventListener(sortableContainer, 'dragleave', function (e) { + // TODO: rename outTarget to be more self-explanatory + // e.fromElement for very old browsers, similar to relatedTarget + var outTarget = e.relatedTarget || e.fromElement; + if (!e.currentTarget.contains(outTarget)) { + if (options.dropTargetContainerClass) { + sortableContainer.classList.remove(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortleave', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: sortableContainer + }, + 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; + draggingWidth = 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; }) + // 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 = getIndex(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = getIndex(dragging, destinationItems); + if (options.dropTargetContainerClass) { + destinationContainer.classList.remove(options.dropTargetContainerClass); + } + /* + * 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, pageX, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + store(sortableElement).placeholder.style.width = draggingWidth + '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 thisWidth = getElementWidth(element); + var placeholderIndex = getIndex(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = getIndex(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 || thisWidth > draggingWidth) { + // Dead zone? + var deadZoneVertical = thisHeight - draggingHeight; + var deadZoneHorizontal = thisWidth - draggingWidth; + var offsetTop = offset(element).top; + var offsetLeft = offset(element).left; + if (placeholderIndex < thisIndex && + ((options.orientation === 'vertical' && pageY < offsetTop) || + (options.orientation === 'horizontal' && pageX < offsetLeft))) { + return; + } + if (placeholderIndex > thisIndex && + ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) || + (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) { + 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 elementMiddleVertical = offset(element).top + element.offsetHeight / 2; + var elementMiddleHorizontal = offset(element).left + element.offsetWidth / 2; + placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) || + (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal)); + } + 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.pageX, 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, + removeContainerEvents: removeContainerEvents +}; + +module.exports = sortable; diff --git a/dist/html5sortable.es.js b/dist/html5sortable.es.js new file mode 100644 index 00000000..46f74796 --- /dev/null +++ b/dist/html5sortable.es.js @@ -0,0 +1,1294 @@ +/* + * 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 */ +/* eslint-disable no-use-before-define */ +var stores = new Map(); +/* eslint-enable no-use-before-define */ +/** + * 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: false, + 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: false, + 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 getIndex = (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: getIndex(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) { + var _a; + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + 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); + // get only height if element has box-sizing: border-box specified + if (style.getPropertyValue('box-sizing') === 'border-box') { + return parseInt(style.getPropertyValue('height'), 10); + } + // 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 width of an element including padding + * @param {HTMLElement} element an dom element + */ +var getElementWidth = (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 ['width', 'padding-left', 'padding-right'] + .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, + dropTargetContainerClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null, + orientation: 'vertical' +}; + +/** + * 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 + */ +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; +var draggingWidth; +/* + * 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'); +}; +// Remove container events +var removeContainerEvents = function (originContainer, previousContainer) { + if (originContainer) { + removeEventListener(originContainer, 'dragleave'); + } + if (previousContainer && (previousContainer !== originContainer)) { + removeEventListener(previousContainer, 'dragleave'); + } +}; +/** + * 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); + // disable adding hover class + enableHoverClass(sortableElement, false); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'dragstart'); + removeEventListener(sortableElement, 'dragend'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + removeItemEvents(items); + removeItemData(items); + removeContainerEvents(originContainer, previousContainer); + // clear sortable flag + sortableElement.isSortable = false; +}; +/** + * 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'); + // enable hover class + enableHoverClass(sortableElement, 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'); + enableHoverClass(sortableElement, false); +}; +/** + * 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); + removeContainerEvents(originContainer, previousContainer); + 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 (Object.prototype.hasOwnProperty.call(options, 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'); + /* + 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 = getIndex(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); + draggingWidth = getElementWidth(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; }); + if (options.dropTargetContainerClass) { + sortableContainer.classList.add(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + addEventListener(sortableContainer, 'dragleave', function (e) { + // TODO: rename outTarget to be more self-explanatory + // e.fromElement for very old browsers, similar to relatedTarget + var outTarget = e.relatedTarget || e.fromElement; + if (!e.currentTarget.contains(outTarget)) { + if (options.dropTargetContainerClass) { + sortableContainer.classList.remove(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortleave', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: sortableContainer + }, + 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; + draggingWidth = 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; }) + // 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 = getIndex(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = getIndex(dragging, destinationItems); + if (options.dropTargetContainerClass) { + destinationContainer.classList.remove(options.dropTargetContainerClass); + } + /* + * 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, pageX, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + store(sortableElement).placeholder.style.width = draggingWidth + '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 thisWidth = getElementWidth(element); + var placeholderIndex = getIndex(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = getIndex(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 || thisWidth > draggingWidth) { + // Dead zone? + var deadZoneVertical = thisHeight - draggingHeight; + var deadZoneHorizontal = thisWidth - draggingWidth; + var offsetTop = offset(element).top; + var offsetLeft = offset(element).left; + if (placeholderIndex < thisIndex && + ((options.orientation === 'vertical' && pageY < offsetTop) || + (options.orientation === 'horizontal' && pageX < offsetLeft))) { + return; + } + if (placeholderIndex > thisIndex && + ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) || + (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) { + 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 elementMiddleVertical = offset(element).top + element.offsetHeight / 2; + var elementMiddleHorizontal = offset(element).left + element.offsetWidth / 2; + placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) || + (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal)); + } + 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.pageX, 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, + removeContainerEvents: removeContainerEvents +}; + +export default sortable; diff --git a/dist/html5sortable.js b/dist/html5sortable.js new file mode 100644 index 00000000..267ba8c9 --- /dev/null +++ b/dist/html5sortable.js @@ -0,0 +1,1299 @@ +/* + * 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 */ + /* eslint-disable no-use-before-define */ + var stores = new Map(); + /* eslint-enable no-use-before-define */ + /** + * 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: false, + 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: false, + 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 getIndex = (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: getIndex(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) { + var _a; + if (placeholderClass === void 0) { placeholderClass = 'sortable-placeholder'; } + 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); + // get only height if element has box-sizing: border-box specified + if (style.getPropertyValue('box-sizing') === 'border-box') { + return parseInt(style.getPropertyValue('height'), 10); + } + // 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 width of an element including padding + * @param {HTMLElement} element an dom element + */ + var getElementWidth = (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 ['width', 'padding-left', 'padding-right'] + .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, + dropTargetContainerClass: false, + debounce: 0, + throttleTime: 100, + maxItems: 0, + itemSerializer: undefined, + containerSerializer: undefined, + customDragImage: null, + orientation: 'vertical' + }; + + /** + * 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 + */ + 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; + var draggingWidth; + /* + * 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'); + }; + // Remove container events + var removeContainerEvents = function (originContainer, previousContainer) { + if (originContainer) { + removeEventListener(originContainer, 'dragleave'); + } + if (previousContainer && (previousContainer !== originContainer)) { + removeEventListener(previousContainer, 'dragleave'); + } + }; + /** + * 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); + // disable adding hover class + enableHoverClass(sortableElement, false); + // remove event handlers & data from sortable + removeEventListener(sortableElement, 'dragover'); + removeEventListener(sortableElement, 'dragenter'); + removeEventListener(sortableElement, 'dragstart'); + removeEventListener(sortableElement, 'dragend'); + removeEventListener(sortableElement, 'drop'); + // remove event data from sortable + removeSortableData(sortableElement); + // remove event handlers & data from items + removeEventListener(handles, 'mousedown'); + removeItemEvents(items); + removeItemData(items); + removeContainerEvents(originContainer, previousContainer); + // clear sortable flag + sortableElement.isSortable = false; + }; + /** + * 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'); + // enable hover class + enableHoverClass(sortableElement, 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'); + enableHoverClass(sortableElement, false); + }; + /** + * 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); + removeContainerEvents(originContainer, previousContainer); + 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 (Object.prototype.hasOwnProperty.call(options, 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'); + /* + 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 = getIndex(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); + draggingWidth = getElementWidth(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; }); + if (options.dropTargetContainerClass) { + sortableContainer.classList.add(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortenter', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: originContainer + }, + destination: { + container: sortableContainer, + itemsBeforeUpdate: destinationItemsBeforeUpdate + }, + item: dragging, + originalTarget: target + } + })); + addEventListener(sortableContainer, 'dragleave', function (e) { + // TODO: rename outTarget to be more self-explanatory + // e.fromElement for very old browsers, similar to relatedTarget + var outTarget = e.relatedTarget || e.fromElement; + if (!e.currentTarget.contains(outTarget)) { + if (options.dropTargetContainerClass) { + sortableContainer.classList.remove(options.dropTargetContainerClass); + } + sortableContainer.dispatchEvent(new CustomEvent('sortleave', { + detail: { + origin: { + elementIndex: originElementIndex, + index: originIndex, + container: sortableContainer + }, + 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; + draggingWidth = 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; }) + // 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 = getIndex(dragging, Array.from(dragging.parentElement.children) + .filter(function (item) { return item !== placeholder; })); + var destinationIndex = getIndex(dragging, destinationItems); + if (options.dropTargetContainerClass) { + destinationContainer.classList.remove(options.dropTargetContainerClass); + } + /* + * 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, pageX, pageY) { + if (!dragging) { + return; + } + // set placeholder height if forcePlaceholderSize option is set + if (options.forcePlaceholderSize) { + store(sortableElement).placeholder.style.height = draggingHeight + 'px'; + store(sortableElement).placeholder.style.width = draggingWidth + '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 thisWidth = getElementWidth(element); + var placeholderIndex = getIndex(store(sortableElement).placeholder, element.parentElement.children); + var thisIndex = getIndex(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 || thisWidth > draggingWidth) { + // Dead zone? + var deadZoneVertical = thisHeight - draggingHeight; + var deadZoneHorizontal = thisWidth - draggingWidth; + var offsetTop = offset(element).top; + var offsetLeft = offset(element).left; + if (placeholderIndex < thisIndex && + ((options.orientation === 'vertical' && pageY < offsetTop) || + (options.orientation === 'horizontal' && pageX < offsetLeft))) { + return; + } + if (placeholderIndex > thisIndex && + ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) || + (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) { + 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 elementMiddleVertical = offset(element).top + element.offsetHeight / 2; + var elementMiddleHorizontal = offset(element).left + element.offsetWidth / 2; + placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) || + (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal)); + } + 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.pageX, 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, + removeContainerEvents: removeContainerEvents + }; + + return sortable; + +}()); diff --git a/dist/html5sortable.min.js b/dist/html5sortable.min.js new file mode 100644 index 00000000..f7094931 --- /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 v=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)})},y=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:!1,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:!1,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}(),E=function(e){if(!(e instanceof HTMLElement))throw new Error("Please provide a sortable to the store function.");return y.has(e)||y.set(e,new t),y.get(e)};function i(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},L=function(e){if(!(e instanceof HTMLElement))throw new Error("You must provide a valid dom element");var n=window.getComputedStyle(e);return"border-box"===n.getPropertyValue("box-sizing")?parseInt(n.getPropertyValue("height"),10):["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})},x=function(e){if(!(e instanceof HTMLElement))throw new Error("You must provide a valid dom element");var n=window.getComputedStyle(e);return["width","padding-left","padding-right"].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)})},p=function(e){return e.composedPath&&e.composedPath()[0]||e.target},m=function(e,t,n){return{element:e,posX:n.pageX-t.left,posY:n.pageY-t.top}},g=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=m),e.dataTransfer&&e.dataTransfer.setDragImage){var r=n(t,w(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",p(e).id),e.dataTransfer.setDragImage(r.element,r.posX,r.posY)}},M=function(e,t){if(!0===e.isSortable){var n=E(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)&&H.parentElement!==n||(e.preventDefault(),e.stopPropagation(),e.dataTransfer.dropEffect=!0===E(n).getConfig("copy")?"copy":"move",o(n,t,e.pageX,e.pageY))}};i(t.concat(s),"dragover",r),i(t.concat(s),"dragenter",r)}),e)}return U.destroy=function(e){var t,n,r,o;n=c(t=e,"opts")||{},r=v(t.children,n.items),o=s(r,n.handle),h(t,!1),a(t,"dragover"),a(t,"dragenter"),a(t,"dragstart"),a(t,"dragend"),a(t,"drop"),F(t),a(o,"mousedown"),N(r),q(r),W(S,P),t.isSortable=!1},U.enable=function(e){B(e)},U.disable=function(e){var t,n,r,o;n=c(t=e,"opts"),r=v(t.children,n.items),o=s(r,n.handle),l(t,"aria-dropeffect","none"),c(t,"_disabled","true"),l(o,"draggable","false"),a(o,"mousedown"),h(t,!1)},U.__testing={data:c,removeItemEvents:N,removeItemData:q,removeSortableData:F,removeContainerEvents:W},U}(); +//# 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..dd207af8 --- /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/getIndex.ts","../src/isInDom.ts","../src/insertHtmlElements.ts","../src/serialize.ts","../src/makePlaceholder.ts","../src/elementHeight.ts","../src/elementWidth.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 */\n/* eslint-disable no-use-before-define */\nexport const stores: Map = new Map()\n/* eslint-enable no-use-before-define */\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 const 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\n get config (): configuration {\n // transform Map to object\n const config = {}\n this._config.forEach((value, key) => {\n config[key] = value\n })\n // return object\n return config\n }\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 /**\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 /**\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 /**\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 /**\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 /**\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 /**\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 (let 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 (let 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 (let 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 (let 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 const 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 */\nconst 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 */\nconst insertBefore = (target: HTMLElement, element: HTMLElement) => insertNode(target, element, 'before')\n/**\n * Insert after target\n * @param {HTMLElement} target\n * @param {HTMLElement} element\n */\nconst insertAfter = (target: HTMLElement, element: HTMLElement) => insertNode(target, element, 'after')\n\nexport { insertBefore, insertAfter }\n","/* eslint-env browser */\nimport { addData } from './data' // yuk, data really needs to be refactored\nimport filter from './filter'\nimport getIndex from './getIndex'\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 const options = addData(sortableContainer, 'opts')\n\n const item: string|undefined = options.items\n\n // serialize container\n const items = filter(sortableContainer.children, item)\n const serializedItems: serializedItem[] = items.map((item) => {\n return {\n parent: sortableContainer,\n node: item,\n html: item.outerHTML,\n index: getIndex(item, items)\n }\n })\n // serialize container\n const 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 const style = window.getComputedStyle(element)\n // get only height if element has box-sizing: border-box specified\n if (style.getPropertyValue('box-sizing') === 'border-box') {\n return parseInt(style.getPropertyValue('height'), 10)\n }\n // pick applicable properties, convert to int and reduce by adding\n return ['height', 'padding-top', 'padding-bottom']\n .map((key) => {\n const 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 width 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 const style = window.getComputedStyle(element)\n // pick applicable properties, convert to int and reduce by adding\n return ['width', 'padding-left', 'padding-right']\n .map((key) => {\n const 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 */\nconst 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 const elementOffset = offset(draggedElement)\n // get the dragImage\n const 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 dropTargetContainerClass: false,\n debounce: 0,\n throttleTime: 100,\n maxItems: 0,\n itemSerializer: undefined,\n containerSerializer: undefined,\n customDragImage: null,\n orientation: 'vertical'\n}\n","/* eslint-env browser */\n'use strict'\n\nimport { addData as data, 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 getIndex from './getIndex'\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 getElementWidth from './elementWidth'\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/*\n * variables global to the plugin\n */\nlet dragging\nlet draggingHeight\nlet draggingWidth\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// Remove container events\nconst removeContainerEvents = function (originContainer, previousContainer) {\n if (originContainer) {\n off(originContainer, 'dragleave')\n }\n if (previousContainer && (previousContainer !== originContainer)) {\n off(previousContainer, 'dragleave')\n }\n}\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 // disable adding hover class\n enableHoverClass(sortableElement, false)\n // remove event handlers & data from sortable\n off(sortableElement, 'dragover')\n off(sortableElement, 'dragenter')\n off(sortableElement, 'dragstart')\n off(sortableElement, 'dragend')\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 removeContainerEvents(originContainer, previousContainer)\n // clear sortable flag\n sortableElement.isSortable = false\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 // enable hover class\n enableHoverClass(sortableElement, 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 enableHoverClass(sortableElement, false)\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 removeContainerEvents(originContainer, previousContainer)\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: configuration|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 const 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 (Object.prototype.hasOwnProperty.call(options, 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 const 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 /*\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 = getIndex(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 draggingWidth = getElementWidth(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 if (options.dropTargetContainerClass) {\n sortableContainer.classList.add(options.dropTargetContainerClass)\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 on(sortableContainer, 'dragleave', e => {\n // TODO: rename outTarget to be more self-explanatory\n // e.fromElement for very old browsers, similar to relatedTarget\n const outTarget = e.relatedTarget || e.fromElement\n if (!e.currentTarget.contains(outTarget)) {\n if (options.dropTargetContainerClass) {\n sortableContainer.classList.remove(options.dropTargetContainerClass)\n }\n sortableContainer.dispatchEvent(new CustomEvent('sortleave', {\n detail: {\n origin: {\n elementIndex: originElementIndex,\n index: originIndex,\n container: sortableContainer\n },\n item: dragging,\n originalTarget: target\n }\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 draggingWidth = 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 // 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 = getIndex(dragging, Array.from(dragging.parentElement.children)\n .filter(item => item !== placeholder))\n const destinationIndex = getIndex(dragging, destinationItems)\n\n if (options.dropTargetContainerClass) {\n destinationContainer.classList.remove(options.dropTargetContainerClass)\n }\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, pageX, 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 store(sortableElement).placeholder.style.width = draggingWidth + '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 thisWidth = getElementWidth(element)\n const placeholderIndex = getIndex(store(sortableElement).placeholder, element.parentElement.children)\n const thisIndex = getIndex(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 || thisWidth > draggingWidth) {\n // Dead zone?\n const deadZoneVertical = thisHeight - draggingHeight\n const deadZoneHorizontal = thisWidth - draggingWidth\n const offsetTop = offset(element).top\n const offsetLeft = offset(element).left\n if (placeholderIndex < thisIndex &&\n ((options.orientation === 'vertical' && pageY < offsetTop) ||\n (options.orientation === 'horizontal' && pageX < offsetLeft))) {\n return\n }\n if (placeholderIndex > thisIndex &&\n ((options.orientation === 'vertical' && pageY > offsetTop + thisHeight - deadZoneVertical) ||\n (options.orientation === 'horizontal' && pageX > offsetLeft + thisWidth - deadZoneHorizontal))) {\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 const elementMiddleVertical = offset(element).top + element.offsetHeight / 2\n const elementMiddleHorizontal = offset(element).left + element.offsetWidth / 2\n placeAfter = (options.orientation === 'vertical' && (pageY >= elementMiddleVertical)) ||\n (options.orientation === 'horizontal' && (pageX >= elementMiddleHorizontal))\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 const 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.pageX, 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 removeContainerEvents: removeContainerEvents\n}\n/* END.TESTS_ONLY */\n","/* eslint-env browser */\nimport store from './store'\nimport filter from './filter'\nimport throttle from './throttle'\nimport { addEventListener, removeEventListener } 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 */\nexport default (sortableContainer: sortable, enable: boolean) => {\n if (typeof store(sortableContainer).getConfig('hoverClass') === 'string') {\n const hoverClasses = store(sortableContainer).getConfig('hoverClass').split(' ')\n // add class on hover\n if (enable === true) {\n addEventListener(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 addEventListener(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 removeEventListener(sortableContainer, 'mousemove')\n removeEventListener(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 const 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","getIndex","container","itemCount","placeholderClass","includes","tagName","document","createElement","innerHTML","_a","classList","add","split","style","getComputedStyle","getPropertyValue","parseInt","int","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","dropTargetContainerClass","debounce","throttleTime","maxItems","itemSerializer","containerSerializer","orientation","dragging","draggingHeight","draggingWidth","originContainer","originIndex","originElementIndex","originItemsBeforeUpdate","previousContainer","destinationItemsBeforeUpdate","enable","hoverClasses_1","fn","threshold","lastEventTimestamp","now","Date","apply","_this","throttle","buttons","remove","_b","removeItemEvents","off","removeContainerEvents","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","enableHoverClass","dragDrop","on","reloadSortable","sortableElements","method","String","querySelectorAll","prototype","slice","call","test","serialize","configKey","hasOwnProperty","console","warn","defaultConfiguration","customPlaceholder","listItems","tempContainer","makePlaceholder","e","stopImmediatePropagation","getAttribute","dragItem","getElementHeight","getElementWidth","dispatchEvent","CustomEvent","detail","elementIndex","originalTarget","itemsBeforeUpdate","outTarget","relatedTarget","fromElement","currentTarget","visiblePlaceholder","values","isInDom","listsConnected","preventDefault","stopPropagation","after","originItems","destinationContainer","destinationItems","destinationElementIndex","destinationIndex","debouncedDragOverEnter","forcePlaceholderSize","height","width","thisHeight","thisWidth","placeholderIndex","thisIndex","deadZoneVertical","deadZoneHorizontal","offsetTop","offsetLeft","placeAfter","elementMiddleVertical","offsetHeight","elementMiddleHorizontal","offsetWidth","before","placeholders","onDragOverEnter","dropEffect","concat","destroy","disable","__testing"],"mappings":"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"} \ No newline at end of file