UNPKG

vue-slicksort

Version:

Set of mixins to turn any list into a sortable, touch-friendly, animated list

1,271 lines 50.6 kB
'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var vue = require('vue');

// Export Sortable Element Component Mixin
const ElementMixin = vue.defineComponent({
    inject: ['manager'],
    props: {
        index: {
            type: Number,
            required: true,
        },
        disabled: {
            type: Boolean,
            default: false,
        },
    },
    data() {
        return {};
    },
    watch: {
        index(newIndex) {
            if (this.$el && this.$el.sortableInfo) {
                this.$el.sortableInfo.index = newIndex;
            }
        },
        disabled(isDisabled) {
            if (isDisabled) {
                this.removeDraggable();
            }
            else {
                this.setDraggable(this.index);
            }
        },
    },
    mounted() {
        const { disabled, index } = this.$props;
        if (!disabled) {
            this.setDraggable(index);
        }
    },
    beforeUnmount() {
        if (!this.disabled)
            this.removeDraggable();
    },
    methods: {
        setDraggable(index) {
            const node = this.$el;
            node.sortableInfo = {
                index,
                manager: this.manager,
            };
            this.ref = { node };
            this.manager.add(this.ref);
        },
        removeDraggable() {
            this.manager.remove(this.ref);
        },
    },
});

class Manager {
    constructor() {
        this.refs = [];
        this.active = null;
    }
    add(ref) {
        if (!this.refs) {
            this.refs = [];
        }
        this.refs.push(ref);
    }
    remove(ref) {
        const index = this.getIndex(ref);
        if (index !== -1) {
            this.refs.splice(index, 1);
        }
    }
    isActive() {
        return !!this.active;
    }
    getActive() {
        return this.refs.find(({ node }) => { var _a, _b; return ((_a = node === null || node === void 0 ? void 0 : node.sortableInfo) === null || _a === void 0 ? void 0 : _a.index) == ((_b = this === null || this === void 0 ? void 0 : this.active) === null || _b === void 0 ? void 0 : _b.index); }) || null;
    }
    getIndex(ref) {
        return this.refs.indexOf(ref);
    }
    getRefs() {
        return this.refs;
    }
    getOrderedRefs() {
        return this.refs.sort((a, b) => {
            return a.node.sortableInfo.index - b.node.sortableInfo.index;
        });
    }
}

const isTouch = (e) => {
    return e.touches != null;
};
// eslint-disable-next-line @typescript-eslint/ban-types
function hasOwnProperty(obj, prop) {
    return !!obj && Object.prototype.hasOwnProperty.call(obj, prop);
}
function arrayMove(arr, previousIndex, newIndex) {
    const array = arr.slice(0);
    if (newIndex >= array.length) {
        let k = newIndex - array.length;
        while (k-- + 1) {
            array.push(undefined);
        }
    }
    array.splice(newIndex, 0, array.splice(previousIndex, 1)[0]);
    return array;
}
function arrayRemove(arr, previousIndex) {
    const array = arr.slice(0);
    if (previousIndex >= array.length)
        return array;
    array.splice(previousIndex, 1);
    return array;
}
function arrayInsert(arr, newIndex, value) {
    const array = arr.slice(0);
    if (newIndex === array.length) {
        array.push(value);
    }
    else {
        array.splice(newIndex, 0, value);
    }
    return array;
}
const events = {
    start: ['touchstart', 'mousedown'],
    move: ['touchmove', 'mousemove'],
    end: ['touchend', 'mouseup'],
    cancel: ['touchcancel', 'keyup'],
};
function closest(el, fn) {
    while (el) {
        if (fn(el))
            return el;
        el = el.parentNode;
    }
}
function limit(min, max, value) {
    if (value < min) {
        return min;
    }
    if (value > max) {
        return max;
    }
    return value;
}
function getCSSPixelValue(stringValue) {
    if (stringValue.substr(-2) === 'px') {
        return parseFloat(stringValue);
    }
    return 0;
}
function getElementMargin(element) {
    const style = window.getComputedStyle(element);
    return {
        top: getCSSPixelValue(style.marginTop),
        right: getCSSPixelValue(style.marginRight),
        bottom: getCSSPixelValue(style.marginBottom),
        left: getCSSPixelValue(style.marginLeft),
    };
}
function getPointerOffset(e, reference = 'page') {
    const x = `${reference}X`;
    const y = `${reference}Y`;
    return {
        x: isTouch(e) ? e.touches[0][x] : e[x],
        y: isTouch(e) ? e.touches[0][y] : e[y],
    };
}
function offsetParents(node) {
    const nodes = [node];
    for (; node; node = node.offsetParent) {
        nodes.unshift(node);
    }
    return nodes;
}
function commonOffsetParent(node1, node2) {
    const parents1 = offsetParents(node1);
    const parents2 = offsetParents(node2);
    if (parents1[0] != parents2[0])
        throw 'No common ancestor!';
    for (let i = 0; i < parents1.length; i++) {
        if (parents1[i] != parents2[i])
            return parents1[i - 1];
    }
}
function getEdgeOffset(node, container, offset = { top: 0, left: 0 }) {
    // Get the actual offsetTop / offsetLeft value, no matter how deep the node is nested
    if (node) {
        const nodeOffset = {
            top: offset.top + node.offsetTop,
            left: offset.left + node.offsetLeft,
        };
        if (node.offsetParent !== container.offsetParent) {
            return getEdgeOffset(node.offsetParent, container, nodeOffset);
        }
        else {
            return nodeOffset;
        }
    }
    return { top: 0, left: 0 };
}
function cloneNode(node) {
    const fields = node.querySelectorAll('input, textarea, select');
    const clonedNode = node.cloneNode(true);
    const clonedFields = [...clonedNode.querySelectorAll('input, textarea, select')]; // Convert NodeList to Array
    clonedFields.forEach((field, index) => {
        if (field.type !== 'file' && fields[index]) {
            field.value = fields[index].value;
        }
    });
    return clonedNode;
}
function getLockPixelOffsets(lockOffset, width, height) {
    if (typeof lockOffset == 'string') {
        lockOffset = +lockOffset;
    }
    if (!Array.isArray(lockOffset)) {
        lockOffset = [lockOffset, lockOffset];
    }
    if (lockOffset.length !== 2) {
        throw new Error(`lockOffset prop of SortableContainer should be a single value or an array of exactly two values. Given ${lockOffset}`);
    }
    const [minLockOffset, maxLockOffset] = lockOffset;
    return [getLockPixelOffset(minLockOffset, width, height), getLockPixelOffset(maxLockOffset, width, height)];
}
function getLockPixelOffset(lockOffset, width, height) {
    let offsetX = lockOffset;
    let offsetY = lockOffset;
    let unit = 'px';
    if (typeof lockOffset === 'string') {
        const match = /^[+-]?\d*(?:\.\d*)?(px|%)$/.exec(lockOffset);
        if (match === null) {
            throw new Error(`lockOffset value should be a number or a string of a number followed by "px" or "%". Given ${lockOffset}`);
        }
        offsetX = offsetY = parseFloat(lockOffset);
        unit = match[1];
    }
    if (!isFinite(offsetX) || !isFinite(offsetY)) {
        throw new Error(`lockOffset value should be a finite. Given ${lockOffset}`);
    }
    if (unit === '%') {
        offsetX = (offsetX * width) / 100;
        offsetY = (offsetY * height) / 100;
    }
    return {
        x: offsetX,
        y: offsetY,
    };
}
function getDistance(x1, y1, x2, y2) {
    const x = x1 - x2;
    const y = y1 - y2;
    return Math.sqrt(x * x + y * y);
}
function getRectCenter(clientRect) {
    return {
        x: clientRect.left + clientRect.width / 2,
        y: clientRect.top + clientRect.height / 2,
    };
}
function resetTransform(nodes = []) {
    for (let i = 0, len = nodes.length; i < len; i++) {
        const node = nodes[i];
        const el = node.node;
        if (!el)
            return;
        // Clear the cached offsetTop / offsetLeft value
        node.edgeOffset = null;
        // Remove the transforms / transitions
        setTransform(el);
    }
}
function setTransform(el, transform = '', duration = '') {
    if (!el)
        return;
    el.style['transform'] = transform;
    el.style['transitionDuration'] = duration;
}
function withinBounds(pos, top, bottom) {
    const upper = Math.max(top, bottom);
    const lower = Math.min(top, bottom);
    return lower <= pos && pos <= upper;
}
function isPointWithinRect({ x, y }, { top, left, width, height }) {
    const withinX = withinBounds(x, left, left + width);
    const withinY = withinBounds(y, top, top + height);
    return withinX && withinY;
}

/* eslint-disable @typescript-eslint/ban-ts-comment */
// eslint-disable-next-line @typescript-eslint/ban-types
const timeout = setTimeout;
// Export Sortable Container Component Mixin
const ContainerMixin = vue.defineComponent({
    inject: {
        SlicksortHub: {
            from: 'SlicksortHub',
            default: null,
        },
    },
    provide() {
        return {
            manager: this.manager,
        };
    },
    props: {
        list: { type: Array, required: true },
        axis: { type: String, default: 'y' },
        distance: { type: Number, default: 0 },
        pressDelay: { type: Number, default: 0 },
        pressThreshold: { type: Number, default: 5 },
        useDragHandle: { type: Boolean, default: false },
        useWindowAsScrollContainer: { type: Boolean, default: false },
        hideSortableGhost: { type: Boolean, default: true },
        lockToContainerEdges: { type: Boolean, default: false },
        lockOffset: { type: [String, Number, Array], default: '50%' },
        transitionDuration: { type: Number, default: 300 },
        appendTo: { type: String, default: 'body' },
        draggedSettlingDuration: { type: Number, default: null },
        group: { type: String, default: '' },
        accept: { type: [Boolean, Array, Function], default: null },
        cancelKey: { type: String, default: 'Escape' },
        block: { type: Array, default: () => [] },
        lockAxis: { type: String, default: '' },
        helperClass: { type: String, default: '' },
        contentWindow: { type: Object, default: null },
        shouldCancelStart: {
            type: Function,
            default: (e) => {
                // Cancel sorting if the event target is an `input`, `textarea`, `select` or `option`
                const disabledElements = ['input', 'textarea', 'select', 'option', 'button'];
                return disabledElements.indexOf(e.target.tagName.toLowerCase()) !== -1;
            },
        },
        getHelperDimensions: {
            type: Function,
            default: ({ node }) => ({
                width: node.offsetWidth,
                height: node.offsetHeight,
            }),
        },
    },
    emits: ['sort-start', 'sort-move', 'sort-end', 'sort-cancel', 'sort-insert', 'sort-remove', 'update:list'],
    data() {
        let useHub = false;
        if (this.group) {
            // If the group option is set, it is assumed the user intends
            // to drag between containers and the required plugin has been installed
            if (this.SlicksortHub) {
                useHub = true;
            }
            else if (process.env.NODE_ENV !== 'production') {
                throw new Error('Slicksort plugin required to use "group" prop');
            }
        }
        return {
            sorting: false,
            hub: useHub ? this.SlicksortHub : null,
            manager: new Manager(),
        };
    },
    mounted() {
        if (this.hub) {
            this.id = this.hub.getId();
        }
        this.container = this.$el;
        this.document = this.container.ownerDocument || document;
        this._window = this.contentWindow || window;
        this.scrollContainer = this.useWindowAsScrollContainer ? { scrollLeft: 0, scrollTop: 0 } : this.container;
        this.events = {
            start: this.handleStart,
            move: this.handleMove,
            end: this.handleEnd,
        };
        for (const key in this.events) {
            if (hasOwnProperty(this.events, key)) {
                // @ts-ignore
                events[key].forEach((eventName) => this.container.addEventListener(eventName, this.events[key]));
            }
        }
        if (this.hub) {
            this.hub.addContainer(this);
        }
    },
    beforeUnmount() {
        for (const key in this.events) {
            if (hasOwnProperty(this.events, key)) {
                // @ts-ignore
                events[key].forEach((eventName) => this.container.removeEventListener(eventName, this.events[key]));
            }
        }
        if (this.hub) {
            this.hub.removeContainer(this);
        }
        if (this.dragendTimer)
            clearTimeout(this.dragendTimer);
        if (this.cancelTimer)
            clearTimeout(this.cancelTimer);
        if (this.pressTimer)
            clearTimeout(this.pressTimer);
        if (this.autoscrollInterval)
            clearInterval(this.autoscrollInterval);
    },
    methods: {
        handleStart(e) {
            const { distance, shouldCancelStart } = this.$props;
            if ((!isTouch(e) && e.button === 2) || shouldCancelStart(e)) {
                return false;
            }
            this._touched = true;
            this._pos = getPointerOffset(e);
            const target = e.target;
            const node = closest(target, (el) => el.sortableInfo != null);
            if (node && node.sortableInfo && this.nodeIsChild(node) && !this.sorting) {
                const { useDragHandle } = this.$props;
                const { index } = node.sortableInfo;
                if (useDragHandle && !closest(target, (el) => el.sortableHandle != null))
                    return;
                this.manager.active = { index };
                /*
                 * Fixes a bug in Firefox where the :active state of anchor tags
                 * prevent subsequent 'mousemove' events from being fired
                 * (see https://github.com/clauderic/react-sortable-hoc/issues/118)
                 */
                if (target.tagName.toLowerCase() === 'a') {
                    e.preventDefault();
                }
                if (!distance) {
                    if (this.pressDelay === 0) {
                        this.handlePress(e);
                    }
                    else {
                        this.pressTimer = timeout(() => this.handlePress(e), this.pressDelay);
                    }
                }
            }
        },
        nodeIsChild(node) {
            return node.sortableInfo.manager === this.manager;
        },
        handleMove(e) {
            const { distance, pressThreshold } = this.$props;
            if (!this.sorting && this._touched) {
                const offset = getPointerOffset(e);
                this._delta = {
                    x: this._pos.x - offset.x,
                    y: this._pos.y - offset.y,
                };
                const delta = Math.abs(this._delta.x) + Math.abs(this._delta.y);
                if (!distance && (!pressThreshold || (pressThreshold && delta >= pressThreshold))) {
                    if (this.cancelTimer)
                        clearTimeout(this.cancelTimer);
                    this.cancelTimer = timeout(this.cancel, 0);
                }
                else if (distance && delta >= distance && this.manager.isActive()) {
                    this.handlePress(e);
                }
            }
        },
        handleEnd() {
            if (!this._touched)
                return;
            const { distance } = this.$props;
            this._touched = false;
            if (!distance) {
                this.cancel();
            }
        },
        cancel() {
            if (!this.sorting) {
                if (this.pressTimer)
                    clearTimeout(this.pressTimer);
                this.manager.active = null;
                if (this.hub)
                    this.hub.cancel();
            }
        },
        handleSortCancel(e) {
            if (isTouch(e) || e.key === this.cancelKey) {
                this.newIndex = this.index;
                this.canceling = true;
                this.translate = { x: 0, y: 0 };
                this.animateNodes();
                this.handleSortEnd(e);
            }
        },
        handlePress(e) {
            e.stopPropagation();
            const active = this.manager.getActive();
            if (active) {
                const { getHelperDimensions, helperClass, hideSortableGhost, appendTo } = this.$props;
                const { node } = active;
                const { index } = node.sortableInfo;
                const margin = getElementMargin(node);
                const containerBoundingRect = this.container.getBoundingClientRect();
                const dimensions = getHelperDimensions({ index, node });
                this.node = node;
                this.margin = margin;
                this.width = dimensions.width;
                this.height = dimensions.height;
                this.marginOffset = {
                    x: this.margin.left + this.margin.right,
                    y: Math.max(this.margin.top, this.margin.bottom),
                };
                this.boundingClientRect = node.getBoundingClientRect();
                this.containerBoundingRect = containerBoundingRect;
                this.index = index;
                this.newIndex = index;
                const clonedNode = cloneNode(node);
                this.helper = this.document.querySelector(appendTo).appendChild(clonedNode);
                this.helper.style.position = 'fixed';
                this.helper.style.top = `${this.boundingClientRect.top - margin.top}px`;
                this.helper.style.left = `${this.boundingClientRect.left - margin.left}px`;
                this.helper.style.width = `${this.width}px`;
                this.helper.style.height = `${this.height}px`;
                this.helper.style.boxSizing = 'border-box';
                this.helper.style.pointerEvents = 'none';
                if (hideSortableGhost) {
                    this.sortableGhost = node;
                    node.style.visibility = 'hidden';
                    node.style.opacity = '0';
                }
                if (this.hub) {
                    this.hub.sortStart(this);
                    this.hub.helper = this.helper;
                    this.hub.ghost = this.sortableGhost;
                }
                this.intializeOffsets(e, this.boundingClientRect);
                this.offsetEdge = getEdgeOffset(node, this.container);
                if (helperClass) {
                    this.helper.classList.add(...helperClass.split(' '));
                }
                this.listenerNode = isTouch(e) ? node : this._window;
                // @ts-ignore
                events.move.forEach((eventName) => this.listenerNode.addEventListener(eventName, this.handleSortMove));
                // @ts-ignore
                events.end.forEach((eventName) => this.listenerNode.addEventListener(eventName, this.handleSortEnd));
                // @ts-ignore
                events.cancel.forEach((eventName) => this.listenerNode.addEventListener(eventName, this.handleSortCancel));
                this.sorting = true;
                this.$emit('sort-start', { event: e, node, index });
            }
        },
        handleSortMove(e) {
            e.preventDefault(); // Prevent scrolling on mobile
            this.updatePosition(e);
            if (this.hub) {
                const payload = this.list[this.index];
                this.hub.handleSortMove(e, payload);
            }
            if (!this.hub || this.hub.isDest(this)) {
                this.animateNodes();
                this.autoscroll();
            }
            this.$emit('sort-move', { event: e });
        },
        handleDropOut() {
            const removed = this.list[this.index];
            const newValue = arrayRemove(this.list, this.index);
            this.$emit('sort-remove', {
                oldIndex: this.index,
            });
            this.$emit('update:list', newValue);
            return removed;
        },
        handleDropIn(payload) {
            const newValue = arrayInsert(this.list, this.newIndex, payload);
            this.$emit('sort-insert', {
                newIndex: this.newIndex,
                value: payload,
            });
            this.$emit('update:list', newValue);
            this.handleDragEnd();
        },
        handleDragOut() {
            if (this.autoscrollInterval) {
                clearInterval(this.autoscrollInterval);
                this.autoscrollInterval = null;
            }
            if (this.hub.isSource(this)) {
                // Trick to animate all nodes up
                this.translate = {
                    x: 10000,
                    y: 10000,
                };
                this.animateNodes();
            }
            else {
                this.manager.getRefs().forEach((ref) => {
                    ref.node.style['transform'] = '';
                });
                this.dragendTimer = timeout(this.handleDragEnd, this.transitionDuration || 0);
            }
        },
        handleDragEnd() {
            if (this.autoscrollInterval) {
                clearInterval(this.autoscrollInterval);
                this.autoscrollInterval = null;
            }
            resetTransform(this.manager.getRefs());
            if (this.sortableGhost) {
                this.sortableGhost.remove();
                this.sortableGhost = null;
            }
            if (this.dragendTimer) {
                clearTimeout(this.dragendTimer);
                this.dragendTimer = null;
            }
            this.manager.active = null;
            this._touched = false;
            this.sorting = false;
        },
        intializeOffsets(e, clientRect) {
            const { useWindowAsScrollContainer, containerBoundingRect, _window } = this;
            this.marginOffset = {
                x: this.margin.left + this.margin.right,
                y: Math.max(this.margin.top, this.margin.bottom),
            };
            this._axis = {
                x: this.axis.indexOf('x') >= 0,
                y: this.axis.indexOf('y') >= 0,
            };
            this.initialOffset = getPointerOffset(e);
            // initialScroll;
            this.initialScroll = {
                top: this.scrollContainer.scrollTop,
                left: this.scrollContainer.scrollLeft,
            };
            // initialWindowScroll;
            this.initialWindowScroll = {
                top: window.pageYOffset,
                left: window.pageXOffset,
            };
            this.translate = { x: 0, y: 0 };
            this.minTranslate = {};
            this.maxTranslate = {};
            if (this._axis.x) {
                this.minTranslate.x =
                    (useWindowAsScrollContainer ? 0 : containerBoundingRect.left) - clientRect.left - this.width / 2;
                this.maxTranslate.x =
                    (useWindowAsScrollContainer ? _window.innerWidth : containerBoundingRect.left + containerBoundingRect.width) -
                        clientRect.left -
                        this.width / 2;
            }
            if (this._axis.y) {
                this.minTranslate.y =
                    (useWindowAsScrollContainer ? 0 : containerBoundingRect.top) - clientRect.top - this.height / 2;
                this.maxTranslate.y =
                    (useWindowAsScrollContainer
                        ? _window.innerHeight
                        : containerBoundingRect.top + containerBoundingRect.height) -
                        clientRect.top -
                        this.height / 2;
            }
        },
        handleDragIn(e, sortableGhost, helper) {
            if (this.hub.isSource(this)) {
                return;
            }
            if (this.dragendTimer) {
                this.handleDragEnd();
                clearTimeout(this.dragendTimer);
                this.dragendTimer = null;
            }
            const nodes = this.manager.getRefs();
            this.index = nodes.length;
            this.manager.active = { index: this.index };
            const containerBoundingRect = this.container.getBoundingClientRect();
            const helperBoundingRect = helper.getBoundingClientRect();
            this.containerBoundingRect = containerBoundingRect;
            this.sortableGhost = cloneNode(sortableGhost);
            this.container.appendChild(this.sortableGhost);
            const ghostRect = this.sortableGhost.getBoundingClientRect();
            this.boundingClientRect = ghostRect;
            this.margin = getElementMargin(this.sortableGhost);
            this.width = ghostRect.width;
            this.height = ghostRect.height;
            // XY coords of the inserted node, relative to the top-left corner of the container
            this.offsetEdge = getEdgeOffset(this.sortableGhost, this.container);
            this.intializeOffsets(e, ghostRect);
            // Move the initialOffset back to the insertion point of the
            // sortableGhost (end of the list), as if we had started the drag there.
            this.initialOffset.x += ghostRect.x - helperBoundingRect.x;
            this.initialOffset.y += ghostRect.y - helperBoundingRect.y;
            // Turn on dragging
            this.sorting = true;
        },
        handleSortEnd(e) {
            // Remove the event listeners if the node is still in the DOM
            if (this.listenerNode) {
                events.move.forEach((eventName) => 
                // @ts-ignore
                this.listenerNode.removeEventListener(eventName, this.handleSortMove));
                events.end.forEach((eventName) => 
                // @ts-ignore
                this.listenerNode.removeEventListener(eventName, this.handleSortEnd));
                events.cancel.forEach((eventName) => 
                // @ts-ignore
                this.listenerNode.removeEventListener(eventName, this.handleSortCancel));
            }
            const nodes = this.manager.getRefs();
            // Remove the helper class(es) early to give it a chance to transition back
            if (this.helper && this.helperClass) {
                this.helper.classList.remove(...this.helperClass.split(' '));
            }
            // Stop autoscroll
            if (this.autoscrollInterval)
                clearInterval(this.autoscrollInterval);
            this.autoscrollInterval = null;
            const onEnd = () => {
                // Remove the helper from the DOM
                if (this.helper) {
                    this.helper.remove();
                    this.helper = null;
                }
                if (this.hideSortableGhost && this.sortableGhost) {
                    this.sortableGhost.style.visibility = '';
                    this.sortableGhost.style.opacity = '';
                }
                resetTransform(nodes);
                // Update state
                if (this.hub && !this.hub.isDest(this)) {
                    this.canceling ? this.hub.cancel() : this.hub.handleSortEnd();
                }
                else if (this.canceling) {
                    this.$emit('sort-cancel', { event: e });
                }
                else {
                    this.$emit('sort-end', {
                        event: e,
                        oldIndex: this.index,
                        newIndex: this.newIndex,
                    });
                    this.$emit('update:list', arrayMove(this.list, this.index, this.newIndex));
                }
                this.manager.active = null;
                this._touched = false;
                this.canceling = false;
                this.sorting = false;
            };
            if (this.transitionDuration || this.draggedSettlingDuration) {
                this.transitionHelperIntoPlace(nodes, onEnd);
            }
            else {
                onEnd();
            }
        },
        transitionHelperIntoPlace(nodes, cb) {
            if (this.draggedSettlingDuration === 0 || nodes.length === 0 || !this.helper) {
                return Promise.resolve();
            }
            const indexNode = nodes[this.index].node;
            let targetX = 0;
            let targetY = 0;
            const scrollDifference = {
                top: window.pageYOffset - this.initialWindowScroll.top,
                left: window.pageXOffset - this.initialWindowScroll.left,
            };
            if (this.hub && !this.hub.isDest(this) && !this.canceling) {
                const dest = this.hub.getDest();
                if (!dest)
                    return;
                const destIndex = dest.newIndex;
                const destRefs = dest.manager.getOrderedRefs();
                const destNode = destIndex < destRefs.length ? destRefs[destIndex].node : dest.sortableGhost;
                const ancestor = commonOffsetParent(indexNode, destNode);
                const sourceOffset = getEdgeOffset(indexNode, ancestor);
                const targetOffset = getEdgeOffset(destNode, ancestor);
                targetX = targetOffset.left - sourceOffset.left - scrollDifference.left;
                targetY = targetOffset.top - sourceOffset.top - scrollDifference.top;
            }
            else {
                const newIndexNode = nodes[this.newIndex].node;
                const deltaScroll = {
                    left: this.scrollContainer.scrollLeft - this.initialScroll.left + scrollDifference.left,
                    top: this.scrollContainer.scrollTop - this.initialScroll.top + scrollDifference.top,
                };
                targetX = -deltaScroll.left;
                if (this.translate && this.translate.x > 0) {
                    // Diff against right edge when moving to the right
                    targetX +=
                        newIndexNode.offsetLeft + newIndexNode.offsetWidth - (indexNode.offsetLeft + indexNode.offsetWidth);
                }
                else {
                    targetX += newIndexNode.offsetLeft - indexNode.offsetLeft;
                }
                targetY = -deltaScroll.top;
                if (this.translate && this.translate.y > 0) {
                    // Diff against the bottom edge when moving down
                    targetY +=
                        newIndexNode.offsetTop + newIndexNode.offsetHeight - (indexNode.offsetTop + indexNode.offsetHeight);
                }
                else {
                    targetY += newIndexNode.offsetTop - indexNode.offsetTop;
                }
            }
            const duration = this.draggedSettlingDuration !== null ? this.draggedSettlingDuration : this.transitionDuration;
            setTransform(this.helper, `translate3d(${targetX}px,${targetY}px, 0)`, `${duration}ms`);
            // Register an event handler to clean up styles when the transition
            // finishes.
            const cleanup = (event) => {
                if (!event || event.propertyName === 'transform') {
                    clearTimeout(cleanupTimer);
                    setTransform(this.helper);
                    cb();
                }
            };
            // Force cleanup in case 'transitionend' never fires
            const cleanupTimer = setTimeout(cleanup, duration + 10);
            this.helper.addEventListener('transitionend', cleanup);
        },
        updatePosition(e) {
            const { lockAxis, lockToContainerEdges } = this.$props;
            const offset = getPointerOffset(e);
            const translate = {
                x: offset.x - this.initialOffset.x,
                y: offset.y - this.initialOffset.y,
            };
            // Adjust for window scroll
            translate.y -= window.pageYOffset - this.initialWindowScroll.top;
            translate.x -= window.pageXOffset - this.initialWindowScroll.left;
            this.translate = translate;
            if (lockToContainerEdges) {
                const [minLockOffset, maxLockOffset] = getLockPixelOffsets(this.lockOffset, this.height, this.width);
                const minOffset = {
                    x: this.width / 2 - minLockOffset.x,
                    y: this.height / 2 - minLockOffset.y,
                };
                const maxOffset = {
                    x: this.width / 2 - maxLockOffset.x,
                    y: this.height / 2 - maxLockOffset.y,
                };
                if (this.minTranslate.x && this.maxTranslate.x)
                    translate.x = limit(this.minTranslate.x + minOffset.x, this.maxTranslate.x - maxOffset.x, translate.x);
                if (this.minTranslate.y && this.maxTranslate.y)
                    translate.y = limit(this.minTranslate.y + minOffset.y, this.maxTranslate.y - maxOffset.y, translate.y);
            }
            if (lockAxis === 'x') {
                translate.y = 0;
            }
            else if (lockAxis === 'y') {
                translate.x = 0;
            }
            if (this.helper) {
                this.helper.style['transform'] = `translate3d(${translate.x}px,${translate.y}px, 0)`;
            }
        },
        animateNodes() {
            const { transitionDuration, hideSortableGhost } = this.$props;
            const nodes = this.manager.getOrderedRefs();
            const deltaScroll = {
                left: this.scrollContainer.scrollLeft - this.initialScroll.left,
                top: this.scrollContainer.scrollTop - this.initialScroll.top,
            };
            const sortingOffset = {
                left: this.offsetEdge.left + this.translate.x + deltaScroll.left,
                top: this.offsetEdge.top + this.translate.y + deltaScroll.top,
            };
            const scrollDifference = {
                top: window.pageYOffset - this.initialWindowScroll.top,
                left: window.pageXOffset - this.initialWindowScroll.left,
            };
            this.newIndex = null;
            for (let i = 0, len = nodes.length; i < len; i++) {
                const { node } = nodes[i];
                const index = node.sortableInfo.index;
                const width = node.offsetWidth;
                const height = node.offsetHeight;
                const offset = {
                    width: this.width > width ? width / 2 : this.width / 2,
                    height: this.height > height ? height / 2 : this.height / 2,
                };
                const translate = {
                    x: 0,
                    y: 0,
                };
                let { edgeOffset } = nodes[i];
                // If we haven't cached the node's offsetTop / offsetLeft value
                if (!edgeOffset) {
                    nodes[i].edgeOffset = edgeOffset = getEdgeOffset(node, this.container);
                }
                // Get a reference to the next and previous node
                const nextNode = i < nodes.length - 1 && nodes[i + 1];
                const prevNode = i > 0 && nodes[i - 1];
                // Also cache the next node's edge offset if needed.
                // We need this for calculating the animation in a grid setup
                if (nextNode && !nextNode.edgeOffset) {
                    nextNode.edgeOffset = getEdgeOffset(nextNode.node, this.container);
                }
                // If the node is the one we're currently animating, skip it
                if (index === this.index) {
                    /*
                     * With windowing libraries such as `react-virtualized`, the sortableGhost
                     * node may change while scrolling down and then back up (or vice-versa),
                     * so we need to update the reference to the new node just to be safe.
                     */
                    if (hideSortableGhost) {
                        this.sortableGhost = node;
                        node.style.visibility = 'hidden';
                        node.style.opacity = '0';
                    }
                    continue;
                }
                if (transitionDuration) {
                    node.style['transitionDuration'] = `${transitionDuration}ms`;
                }
                if (this._axis.x) {
                    if (this._axis.y) {
                        // Calculations for a grid setup
                        if (index < this.index &&
                            ((sortingOffset.left + scrollDifference.left - offset.width <= edgeOffset.left &&
                                sortingOffset.top + scrollDifference.top <= edgeOffset.top + offset.height) ||
                                sortingOffset.top + scrollDifference.top + offset.height <= edgeOffset.top)) {
                            // If the current node is to the left on the same row, or above the node that's being dragged
                            // then move it to the right
                            translate.x = this.width + this.marginOffset.x;
                            if (edgeOffset.left + translate.x > this.containerBoundingRect.width - offset.width && nextNode) {
                                // If it moves passed the right bounds, then animate it to the first position of the next row.
                                // We just use the offset of the next node to calculate where to move, because that node's original position
                                // is exactly where we want to go
                                translate.x = nextNode.edgeOffset.left - edgeOffset.left;
                                translate.y = nextNode.edgeOffset.top - edgeOffset.top;
                            }
                            if (this.newIndex === null) {
                                this.newIndex = index;
                            }
                        }
                        else if (index > this.index &&
                            ((sortingOffset.left + scrollDifference.left + offset.width >= edgeOffset.left &&
                                sortingOffset.top + scrollDifference.top + offset.height >= edgeOffset.top) ||
                                sortingOffset.top + scrollDifference.top + offset.height >= edgeOffset.top + height)) {
                            // If the current node is to the right on the same row, or below the node that's being dragged
                            // then move it to the left
                            translate.x = -(this.width + this.marginOffset.x);
                            if (edgeOffset.left + translate.x < this.containerBoundingRect.left + offset.width && prevNode) {
                                // If it moves passed the left bounds, then animate it to the last position of the previous row.
                                // We just use the offset of the previous node to calculate where to move, because that node's original position
                                // is exactly where we want to go
                                translate.x = prevNode.edgeOffset.left - edgeOffset.left;
                                translate.y = prevNode.edgeOffset.top - edgeOffset.top;
                            }
                            this.newIndex = index;
                        }
                    }
                    else {
                        if (index > this.index && sortingOffset.left + scrollDifference.left + offset.width >= edgeOffset.left) {
                            translate.x = -(this.width + this.marginOffset.x);
                            this.newIndex = index;
                        }
                        else if (index < this.index &&
                            sortingOffset.left + scrollDifference.left <= edgeOffset.left + offset.width) {
                            translate.x = this.width + this.marginOffset.x;
                            if (this.newIndex == null) {
                                this.newIndex = index;
                            }
                        }
                    }
                }
                else if (this._axis.y) {
                    if (index > this.index && sortingOffset.top + scrollDifference.top + offset.height >= edgeOffset.top) {
                        translate.y = -(this.height + this.marginOffset.y);
                        this.newIndex = index;
                    }
                    else if (index < this.index &&
                        sortingOffset.top + scrollDifference.top <= edgeOffset.top + offset.height) {
                        translate.y = this.height + this.marginOffset.y;
                        if (this.newIndex == null) {
                            this.newIndex = index;
                        }
                    }
                }
                node.style['transform'] = `translate3d(${translate.x}px,${translate.y}px,0)`;
            }
            if (this.newIndex == null) {
                this.newIndex = this.index;
            }
        },
        autoscroll() {
            const translate = this.translate;
            const direction = {
                x: 0,
                y: 0,
            };
            const speed = {
                x: 1,
                y: 1,
            };
            const acceleration = {
                x: 10,
                y: 10,
            };
            if (translate.y >= this.maxTranslate.y - this.height / 2) {
                direction.y = 1; // Scroll Down
                speed.y = acceleration.y * Math.abs((this.maxTranslate.y - this.height / 2 - translate.y) / this.height);
            }
            else if (translate.x >= this.maxTranslate.x - this.width / 2) {
                direction.x = 1; // Scroll Right
                speed.x = acceleration.x * Math.abs((this.maxTranslate.x - this.width / 2 - translate.x) / this.width);
            }
            else if (translate.y <= this.minTranslate.y + this.height / 2) {
                direction.y = -1; // Scroll Up
                speed.y = acceleration.y * Math.abs((translate.y - this.height / 2 - this.minTranslate.y) / this.height);
            }
            else if (translate.x <= this.minTranslate.x + this.width / 2) {
                direction.x = -1; // Scroll Left
                speed.x = acceleration.x * Math.abs((translate.x - this.width / 2 - this.minTranslate.x) / this.width);
            }
            if (this.autoscrollInterval) {
                clearInterval(this.autoscrollInterval);
                this.autoscrollInterval = null;
            }
            if (direction.x !== 0 || direction.y !== 0) {
                this.autoscrollInterval = window.setInterval(() => {
                    const offset = {
                        left: 1 * speed.x * direction.x,
                        top: 1 * speed.y * direction.y,
                    };
                    if (this.useWindowAsScrollContainer) {
                        this._window.scrollBy(offset.left, offset.top);
                    }
                    else {
                        this.scrollContainer.scrollTop += offset.top;
                        this.scrollContainer.scrollLeft += offset.left;
                    }
                    this.translate.x += offset.left;
                    this.translate.y += offset.top;
                    this.animateNodes();
                }, 5);
            }
        },
    },
});

// Export Sortable Element Handle Directive
const HandleDirective = {
    beforeMount(el) {
        el.sortableHandle = true;
    },
};

const SlickItem = vue.defineComponent({
    name: 'SlickItem',
    mixins: [ElementMixin],
    props: {
        tag: {
            type: String,
            default: 'div',
        },
    },
    render() {
        var _a, _b;
        return vue.h(this.tag, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a));
    },
});

const SlickList = vue.defineComponent({
    name: 'SlickList',
    mixins: [ContainerMixin],
    props: {
        tag: {
            type: String,
            default: 'div',
        },
        itemKey: {
            type: [String, Function],
            default: 'id',
        },
    },
    render() {
        var _a, _b;
        if (this.$slots.item) {
            return vue.h(this.tag, this.list.map((item, index) => {
                let key;
                if (item == null) {
                    return;
                }
                else if (typeof this.itemKey === 'function') {
                    key = this.itemKey(item);
                }
                else if (typeof item === 'object' &&
                    hasOwnProperty(item, this.itemKey) &&
                    typeof item[this.itemKey] == 'string') {
                    key = item[this.itemKey];
                }
                else if (typeof item === 'string') {
                    key = item;
                }
                else {
                    throw new Error('Cannot find key for item, use the item-key prop and pass a function or string');
                }
                return vue.h(SlickItem, {
                    key,
                    index,
                }, {
                    default: () => { var _a, _b; return (_b = (_a = this.$slots).item) === null || _b === void 0 ? void 0 : _b.call(_a, { item, index }); },
                });
            }));
        }
        return vue.h(this.tag, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a));
    },
});

const DragHandle = vue.defineComponent({
    props: {
        tag: {
            type: String,
            default: 'span',
        },
    },
    mounted() {
        this.$el.sortableHandle = true;
    },
    render() {
        var _a, _b;
        return vue.h(this.tag, (_b = (_a = this.$slots).default) === null || _b === void 0 ? void 0 : _b.call(_a));
    },
});

let containerIDCounter = 1;
/**
 * Always allow when dest === source
 * Defer to 'dest.accept()' if it is a function
 * Allow any group in the accept lists
 * Deny any group in the block list
 * Allow the same group by default, this can be overridden with the block prop
 */
function canAcceptElement(dest, source, payload) {
    if (source.id === dest.id)
        return true;
    if (dest.block && dest.block.includes(source.group))
        return false;
    if (typeof dest.accept === 'function') {
        return dest.accept({ dest, source, payload });
    }
    if (typeof dest.accept === 'boolean') {
        return dest.accept;
    }
    if (dest.accept && dest.accept.includes(source.group))
        return true;
    if (dest.group === source.group)
        return true;
    return false;
}
function findClosestDest({ x, y }, refs, currentDest) {
    // Quickly check if we are within the bounds of the current destination
    if (isPointWithinRect({ x, y }, currentDest.container.getBoundingClientRect())) {
        return currentDest;
    }
    let closest = null;
    let minDistance = Infinity;
    for (let i = 0; i < refs.length; i++) {
        const ref = refs[i];
        const rect = ref.container.getBoundingClientRect();
        const isWithin = isPointWithinRect({ x, y }, rect);
        if (isWithin) {
            // If we are within another destination, stop here
            return ref;
        }
        const center = getRectCenter(rect);
        const distance = getDistance(x, y, center.x, center.y);
        if (distance < minDistance) {
            closest = ref;
            minDistance = distance;
        }
    }
    // Try to guess the closest destination
    return closest;
}
class SlicksortHub {
    constructor() {
        this.helper = null;
        this.ghost = null;
        this.refs = [];
        this.source = null;
        this.dest = null;
    }
    getId() {
        return '' + containerIDCounter++;
    }
    isSource({ id }) {
        var _a;
        return ((_a = this.source) === null || _a === void 0 ? void 0 : _a.id) === id;
    }
    getSource() {
        return this.source;
    }
    isDest({ id }) {
        var _a;
        return ((_a = this.dest) === null || _a === void 0 ? void 0 : _a.id) === id;
    }
    getDest() {
        return this.dest;
    }
    addContainer(ref) {
        this.refs.push(ref);
    }
    removeContainer(ref) {
        this.refs = this.refs.filter((c) => c.id !== ref.id);
    }
    sortStart(ref) {
        this.source = ref;
        this.dest = ref;
    }
    handleSortMove(e, payload) {
        var _a, _b, _c, _d;
        const dest = this.dest;
        const source = this.source;
        if (!dest || !source)
            return;
        const refs = this.refs;
        const pointer = getPointerOffset(e, 'client');
        const newDest = findClosestDest(pointer, refs, dest) || dest;
        if (dest.id !== newDest.id && canAcceptElement(newDest, source, payload)) {
            this.dest = newDest;
            dest.handleDragOut();
            newDest.handleDragIn(e, this.ghost, this.helper);
        }
        if (dest.id !== ((_a = this.source) === null || _a === void 0 ? void 0 : _a.id)) {
            (_b = this.dest) === null || _b === void 0 ? void 0 : _b.updatePosition(e);
            (_c = this.dest) === null || _c === void 0 ? void 0 : _c.animateNodes();
            (_d = this.dest) === null || _d === void 0 ? void 0 : _d.autoscroll();
        }
    }
    handleSortEnd() {
        var _a, _b, _c, _d;
        if (((_a = this.source) === null || _a === void 0 ? void 0 : _a.id) === ((_b = this.dest) === null || _b === void 0 ? void 0 : _b.id))
            return;
        const payload = (_c = this.source) === null || _c === void 0 ? void 0 : _c.handleDropOut();
        (_d = this.dest) === null || _d === void 0 ? void 0 : _d.handleDropIn(payload);
        this.reset();
    }
    reset() {
        this.source = null;
        this.dest = null;
        this.helper = null;
        this.ghost = null;
    }
    cancel() {
        var _a;
        (_a = this.dest) === null || _a === void 0 ? void 0 : _a.handleDragEnd();
        this.reset();
    }
}

const plugin = {
    install(app) {
        app.directive('drag-handle', HandleDirective);
        app.provide('SlicksortHub', new SlicksortHub());
    },
};

exports.ContainerMixin = ContainerMixin;
exports.DragHandle = DragHandle;
exports.ElementMixin = ElementMixin;
exports.HandleDirective = HandleDirective;
exports.SlickItem = SlickItem;
exports.SlickList = SlickList;
exports.arrayMove = arrayMove;
exports.plugin = plugin;