import { __extends } from "tslib";
import { targetIsInput } from '../../../utils/event/event';
import { dragDropDelay, polyFillActive } from '../../../utils/polyfills/drag-and-drop.polyfill';
import { clearUserSelection } from '../../../utils/selection/selection';
import { getVNodeAttributeValue } from '../../../utils/vue/directive';
import { dispatchEvent } from '../../../utils/vue/events';
import { DRAGGABLE_NAME } from '../../directive-names';
import { MDOMPlugin, MElementDomPlugin } from '../../domPlugin';
import { MSortable } from '../../sortable/sortable';
import RemoveUserSelectPlugin, { MRemoveUserSelect } from '../../user-select/remove-user-select';
import { MDroppable } from '../droppable/droppable';
import { MDraggableAllowScroll } from './draggable-allow-scroll';
import './draggable.scss';
export var MDraggableClassNames;
(function (MDraggableClassNames) {
    MDraggableClassNames["Draggable"] = "m--is-draggable";
    MDraggableClassNames["Dragging"] = "m--is-dragging";
    MDraggableClassNames["Grabbing"] = "m--is-grabbing";
    MDraggableClassNames["DragImage"] = "dragImage";
    MDraggableClassNames["DragHandle"] = "m-drag-handle";
    MDraggableClassNames["HasDragHandle"] = "m--has-drag-handle";
})(MDraggableClassNames || (MDraggableClassNames = {}));
export var MDraggableEventNames;
(function (MDraggableEventNames) {
    MDraggableEventNames["OnDragStart"] = "draggable:dragstart";
    MDraggableEventNames["OnDragEnd"] = "draggable:dragend";
})(MDraggableEventNames || (MDraggableEventNames = {}));
var DEFAULT_ACTION = 'any';
var MDraggable = /** @class */ (function (_super) {
    __extends(MDraggable, _super);
    function MDraggable(element, options) {
        var _this = _super.call(this, element, options) || this;
        _this.grabEvents = ['mousedown', 'touchstart'];
        _this.cancelGrabEvents = ['mouseup', 'touchend', 'click', 'touchcancel'];
        _this.touchUpListener = _this.doCleanUp.bind(_this);
        _this.intputTouchUpListener = _this.turnDragOn.bind(_this);
        _this.grabDelay = undefined;
        _this.touchHasMoved = false;
        _this.isMouseInitiatedDrag = false;
        return _this;
    }
    MDraggable.prototype.doCleanUp = function () {
        this.destroyGrabBehavior();
        this.cleanupCssClasses();
        MDraggable.currentDraggable = undefined;
    };
    MDraggable.prototype.attach = function (mount) {
        var _this = this;
        this.attachDragImage();
        if (this.options.canDrag === undefined) {
            this.options.canDrag = true;
        }
        if (this.options.canDrag) {
            mount(function () {
                _this.doCleanUp();
                _this.element.classList.add(MDraggableClassNames.Draggable);
                _this.options.action = _this.options.action ? _this.options.action : DEFAULT_ACTION;
                _this.turnDragOn();
            });
        }
    };
    MDraggable.prototype.update = function (options, refresh) {
        var _this = this;
        if (options.canDrag === undefined) {
            options.canDrag = true;
        }
        this._options = options;
        if (this.options.canDrag) {
            refresh(function () {
                _this.options.action = _this.options.action ? _this.options.action : DEFAULT_ACTION;
                _this.attachDragImage();
            });
        }
    };
    MDraggable.prototype.detach = function () {
        this.element.draggable = false;
        MDOMPlugin.detach(MRemoveUserSelect, this.element);
        this.element.classList.remove(MDraggableClassNames.Draggable);
        this.destroyGrabBehavior();
        this.cleanupCssClasses();
        this.removeAllEvents();
    };
    MDraggable.prototype.onDragStart = function (event) {
        // On some mobile devices dragStart will be triggered even though user has not moved / dragged yet.  We want to avoid that.
        if (polyFillActive.dragDrop && (!this.touchHasMoved && !this.isMouseInitiatedDrag)) {
            event.preventDefault();
            event.stopPropagation();
            event.stopImmediatePropagation();
            return;
        }
        event.stopPropagation();
        this.doCleanUp();
        clearUserSelection();
        MDraggable.currentDraggable = this;
        this.element.classList.add(MDraggableClassNames.Dragging);
        if (typeof this.options.dragData === 'object') {
            event.dataTransfer.setData('application/json', JSON.stringify(this.options.dragData));
        }
        else {
            event.dataTransfer.setData('text', this.options.dragData);
        }
        this.setEventDragImage(event);
        this.dispatchEvent(event, MDraggableEventNames.OnDragStart);
    };
    MDraggable.prototype.onDragEnd = function (event) {
        event.stopPropagation();
        this.doCleanUp();
        // Fix for IE / Edge.  clientX / clientY don't appear to be out of element on dragLeave.
        // We can't detect whether we're leaving de droppable for real therefore we have to force leave onDragEnd.
        if (MDroppable.currentHoverDroppable) {
            MDroppable.currentHoverDroppable.leaveDroppable(event);
        }
        if (MSortable.activeSortContainer) {
            MSortable.activeSortContainer.doCleanUp();
        }
        if (MSortable.fromSortContainer) {
            MSortable.fromSortContainer.doCleanUp();
        }
        if (MDraggableAllowScroll.currentDraggableScroll) {
            MDraggableAllowScroll.currentDraggableScroll.doCleanUp();
        }
        this.dispatchEvent(event, MDraggableEventNames.OnDragEnd);
        var dragImage = this.element.querySelector("." + MDraggableClassNames.DragImage);
        if (dragImage) {
            dragImage.hidden = true;
        }
    };
    MDraggable.prototype.setupGrabBehavior = function () {
        var _this = this;
        this.destroyGrabBehavior();
        if (this.draggableHasHandle(this.element)) {
            this.element.classList.add(MDraggableClassNames.HasDragHandle);
        }
        this.grabEvents.forEach(function (eventName) { return _this.removeEventListener(eventName); });
        this.grabEvents.forEach(function (eventName) { return _this.addEventListener(eventName, function (event) {
            if (targetIsInput(_this.element, event) || (_this.draggableHasHandle(_this.element) && !_this.isHandleUsedToDrag(event))) {
                _this.turnDragOff();
                _this.cancelGrabEvents.forEach(function (eventName) { return document.addEventListener(eventName, _this.intputTouchUpListener); });
            }
            else if (_this.targetIsGrabbable(event)) {
                _this.cancelGrabEvents.forEach(function (eventName) { return document.addEventListener(eventName, _this.touchUpListener); });
                _this.grabDelay = window.setTimeout(function () {
                    if (!MDraggable.currentDraggable && _this.grabDelay) {
                        _this.element.classList.add(MDraggableClassNames.Grabbing);
                        _this.forceCursorRefresh();
                    }
                }, polyFillActive.dragDrop && !_this.isMouseInitiatedDrag ? dragDropDelay : 0);
            }
        }); });
    };
    MDraggable.prototype.turnDragOn = function () {
        var _this = this;
        this.element.draggable = true;
        this.addEventListener('dragend', function (event) { return _this.onDragEnd(event); });
        this.addEventListener('dragstart', function (event) { return _this.onDragStart(event); });
        this.addEventListener('touchmove', function () { return _this.touchHasMoved = true; }, true); // here is it important to use capture on devices that supports mouse + touch events.
        this.addEventListener('mousedown', function () { _this.isMouseInitiatedDrag = true; }, true);
        this.setupGrabBehavior();
        MDOMPlugin.attach(MRemoveUserSelect, this.element, true);
    };
    MDraggable.prototype.turnDragOff = function () {
        this.element.draggable = false;
        this.removeEventListener('dragend');
        this.removeEventListener('dragstart');
        this.removeEventListener('touchmove');
        this.removeEventListener('mousedown');
        this.destroyGrabBehavior();
        MDOMPlugin.detach(MRemoveUserSelect, this.element);
    };
    MDraggable.prototype.targetIsGrabbable = function (event) {
        // We can't call event.preventDefault or event.stopPropagation here for the drag to be handled correctly on mobile devices.
        // So we make sure that the draggable affected by the dragEvent is the closest draggable parent of the event target.
        // We don't apply the "grabbing" style on mouse down when target correspond to a link or a button, it just looks weird.
        var draggable = MDOMPlugin.getRecursive(MDraggable, event.target);
        if (!draggable || draggable !== this) {
            return false;
        }
        var recursiveElement = event.target;
        var noGrabTags = ['A', 'BUTTON'];
        var targetGrabbable = true;
        while (recursiveElement && targetGrabbable && recursiveElement !== draggable.element) {
            if (noGrabTags.find(function (tag) { return tag === recursiveElement.tagName; })) {
                targetGrabbable = false;
            }
            recursiveElement = recursiveElement.parentElement;
        }
        return targetGrabbable;
    };
    MDraggable.prototype.destroyGrabBehavior = function () {
        var _this = this;
        this.touchHasMoved = false;
        this.isMouseInitiatedDrag = false;
        this.forceCursorRefresh();
        if (this.grabDelay) {
            window.clearTimeout(this.grabDelay);
            this.grabDelay = undefined;
        }
        this.cancelGrabEvents.forEach(function (eventName) { return document.removeEventListener(eventName, _this.touchUpListener); });
        this.cancelGrabEvents.forEach(function (eventName) { return document.removeEventListener(eventName, _this.intputTouchUpListener); });
    };
    MDraggable.prototype.forceCursorRefresh = function () {
        // Hack to force cursor refresh.
        this.element.style.webkitUserDrag = 'none';
        this.element.style.webkitUserDrag = '';
    };
    MDraggable.prototype.attachDragImage = function () {
        var dragImage = this.element.querySelector("." + MDraggableClassNames.DragImage);
        // We use this property to know if the dragImage was handled or not.
        var dragImagePluginName = '__mdraggableimage__';
        if (dragImage && !dragImage[dragImagePluginName]) {
            var offsetWidth = dragImage.offsetWidth;
            requestAnimationFrame(function () {
                var origin = -9999;
                dragImage.style.left = origin + "px";
                dragImage.style.top = origin + "px";
                dragImage.style.position = 'absolute';
                dragImage.style.overflow = 'hidden';
                dragImage.style.zIndex = '1';
                dragImage.hidden = true;
                dragImage[dragImagePluginName] = true;
            });
        }
    };
    MDraggable.prototype.setEventDragImage = function (event) {
        var dragImage = this.element.querySelector("." + MDraggableClassNames.DragImage);
        if (dragImage && event.dataTransfer.setDragImage) {
            dragImage.hidden = false;
            if (polyFillActive.dragDrop) {
                event.dataTransfer.setDragImage(dragImage, 0, 0);
            }
            else {
                event.dataTransfer.setDragImage(dragImage, this.calculateHorizontalCenterOffset(dragImage), this.calculateVerticalCenterOffset(dragImage));
            }
        }
    };
    MDraggable.prototype.calculateHorizontalCenterOffset = function (dragImage) {
        var dragImageWidth = window.getComputedStyle(dragImage).width;
        return dragImageWidth ? parseInt(dragImageWidth, 10) / 2 : 0;
    };
    MDraggable.prototype.calculateVerticalCenterOffset = function (dragImage) {
        var dragImageHeight = window.getComputedStyle(dragImage).height;
        return dragImageHeight ? parseInt(dragImageHeight, 10) / 2 : 0;
    };
    MDraggable.prototype.dispatchEvent = function (event, name) {
        var data = this.options.dragData ? this.options.dragData : event.dataTransfer.getData('text');
        var dragInfo = {
            action: this.options.action,
            grouping: this.options.grouping,
            data: data
        };
        var customEvent = document.createEvent('CustomEvent');
        customEvent.initCustomEvent(name, true, true, Object.assign(event, { dragInfo: dragInfo }));
        customEvent.dragInfo = dragInfo;
        dispatchEvent(this.element, name, customEvent);
    };
    MDraggable.prototype.cleanupCssClasses = function () {
        this.element.classList.remove(MDraggableClassNames.Dragging);
        this.element.classList.remove(MDraggableClassNames.Grabbing);
        this.element.classList.remove(MDraggableClassNames.HasDragHandle);
    };
    MDraggable.prototype.draggableHasHandle = function (element) {
        return element.getElementsByClassName(MDraggableClassNames.DragHandle).length > 0;
    };
    MDraggable.prototype.isHandleUsedToDrag = function (event) {
        var dragHandle = this.element.querySelector('.' + MDraggableClassNames.DragHandle);
        if (dragHandle) {
            return dragHandle.classList.contains(MDraggableClassNames.DragHandle) && dragHandle.contains(event.target);
        }
        else {
            return false;
        }
    };
    MDraggable.defaultMountPoint = '__mdraggable__';
    return MDraggable;
}(MElementDomPlugin));
export { MDraggable };
var extractVnodeAttributes = function (binding, node) {
    return {
        canDrag: binding.value,
        action: getVNodeAttributeValue(node, 'action'),
        dragData: getVNodeAttributeValue(node, 'drag-data'),
        grouping: getVNodeAttributeValue(node, 'grouping')
    };
};
var Directive = {
    inserted: function (element, binding, node) {
        MDOMPlugin.attach(MDraggable, element, extractVnodeAttributes(binding, node));
    },
    update: function (element, binding, node) {
        MDOMPlugin.attach(MDraggable, element, extractVnodeAttributes(binding, node));
    },
    unbind: function (element, binding) {
        MDOMPlugin.detach(MDraggable, element);
    }
};
var DraggablePlugin = {
    install: function (v, options) {
        v.use(RemoveUserSelectPlugin);
        v.directive(DRAGGABLE_NAME, Directive);
    }
};
export default DraggablePlugin;
//# sourceMappingURL=draggable.js.map