Source: jscore/DragDrop.js

(function() {
    /**
     * enables drag and drop
     *
     * parameters:
     * - object: defines the DOM object to be moved
     * - area (optional): defines the area that should be reactive to pointer events; if no area is provided the object is used
     * - remoteMode (optional): defines whether the WA is used by the DA to adopt behavior
     *
     * @augments Help4.jscore.Base
     */
    Help4.jscore.DragDrop = class extends Help4.jscore.Base {
        /**
         * @override
         * @param {Help4.control2.DragDropParams} params
         */
        constructor(params) {
            super({
                statics: {
                    _params: {init: params, destroy: false},

                    _ctl: {init: {x: 0, y: 0}, destroy: false},
                    _pos: {init: {x: 0, y: 0}, destroy: false},
                    _min: {init: {x: 0, y: 0}, destroy: false},
                    _max: {init: {x: 0, y: 0}, destroy: false},

                    _ps: {init: event => event.target === dom || Help4.Element.isDescendant(dom, event.target) ? _start.call(this, event) : true, destroy: false},
                    _pm: {init: event => void _move.call(this, event), destroy: false},
                    _pe: {init: event => void _end.call(this, event), destroy: false},

                    onStart: {init: new Help4.EmbeddedEvent()},
                    onMove:  {init: new Help4.EmbeddedEvent()},
                    onEnd:   {init: new Help4.EmbeddedEvent()}
                }
            });

            const {_params, _ps} = this;
            const {area, object} = _params;
            const dom = area || object;
            Help4.Element.addClass(dom, 'dnd');
            dom.onselectstart = () => false;
            dom.ondragstart = () => false;

            Help4.Event.observe(dom, {manual: true, eventType: 'pointerdown', callback: _ps});
        }

        /** destroys the control */
        destroy() {
            const {Event, Element} = Help4;
            const {_params, _ps} = this;
            const {area, object} = _params;
            const dom = area || object;
            Event.stopObserving(dom, {manual: true, eventType: 'pointerdown', callback: _ps});
            Element.removeClass(dom, 'dnd');
            dom.onselectstart = null;
            dom.ondragstart = null;

            _end.call(this);

            super.destroy();
        }
    }

    /**
     * @methodOf Help4.jscore.DragDrop#
     * @param {PointerEvent} event
     * @returns {boolean}
     * @private
     */
    function _start(event) {
        const {_params, _pm, _pe} = this;

        if (!_params.remoteMode) {
            if (event.preventDefault) event.preventDefault();
            event.target.setPointerCapture(event.pointerId);
        }

        this._doss = document.onselectstart;
        document.onselectstart = () => false;

        const {Element, Event} = Help4;

        if ((event = Help4.Event.normalize(event))) {
            const {object, min, max} = _params;
            const s = getComputedStyle(object);
            const r = object.getBoundingClientRect();
            this._ctl = {x: parseFloat(s.left) - r.left, y: parseFloat(s.top) - r.top};
            this._pos = {x: event.clientX - r.left, y: event.clientY - r.top};
            this._max = max || {x: window.innerWidth - r.width, y: window.innerHeight - r.height};
            if (min != null) this._min = min;

            Element.addClass(object, 'no-transition');
            this._started = true;

            this.onStart.onEvent({data: event});
        }

        Event.observe(window, {manual: true, eventType: 'pointermove', callback: _pm});
        Event.observe(window, {manual: true, eventType: 'pointerup', callback: _pe});

        return false;
    }

    /**
     * @methodOf Help4.jscore.DragDrop#
     * @param {PointerEvent} event
     * @private
     */
    function _move(event) {
        if (!(event = Help4.Event.normalize(event))) return;

        this._moved = true;
        this.onMove.onEvent({data: event});

        const {_pos: {x: posX, y: posY}, _min: {x: minX, y: minY}, _max: {x: maxX, y: maxY}} = this;
        const x = Math.max(minX, Math.min(event.clientX - posX, maxX));
        const y = Math.max(minY, Math.min(event.clientY - posY, maxY));

        const {_params: {object: {style}}, _ctl: {x: ctlX, y: ctlY}} = this;
        style.left = ctlX + x + 'px';
        style.top = ctlY + y + 'px';
    }

    /**
     * @methodOf Help4.jscore.DragDrop#
     * @param {PointerEvent} [event]
     * @private
     */
    function _end(event) {
        const {Event, Element} = Help4;
        const {_pm, _pe} = this;

        Event.stopObserving(window, {manual: true, eventType: 'pointermove', callback: _pm});
        Event.stopObserving(window, {manual: true, eventType: 'pointerup', callback: _pe});

        const {_params} = this;
        if (event && !_params.remoteMode) event.target.releasePointerCapture(event.pointerId);

        document.onselectstart = this._doss;
        delete this._doss;
        delete this._moved;

        Element.removeClass(_params.object, 'no-transition');

        if (this._started) {
            this.onEnd.onEvent({data: null});
            delete this._started;
        }
    }
})();