    /** * A wrapper around a DOM element that allows it to be dragged. * * ## Constraining * * The {@link #constrain} config gives various options for limiting drag, for example: * - Vertical or horizontal only * - Minimum/maximum x/y values. * - Snap to grid * - Constrain to an element or region. * * See {@link Ext.drag.Constrain} for detailed options. * * *      new Ext.drag.Source({ *          element: dragEl, *          constrain: { *              // Drag only vertically in 30px increments *              vertical: true, *              snap: { *                  y: 30 *              } *          } *      }); * * ## Data * * Data representing the underlying drag is driven by the {@link #describe} method. This method * is called once at the beginning of the drag. It should populate the info object with data using * the {@link Ext.data.Info#setData setData} method. It accepts 2 arguments.  *  * - The `type` is used to indicate to {@link Ext.drag.Target targets} the type(s) of data being provided.  * This allows the {@link Ext.drag.Target target} to decide whether it is able to interact with the source.  * All types added are available in {@link Ext.data.Info#types types}. * - The value can be a static value, or a function reference. In the latter case, the function is evaluated * when the data is requested. * * The {@link Ext.drag.Info#getData} method may be called once the drop completes. The data for the relevant type * is retrieved. All values from this method return a {@link Ext.Promise} to allow for consistency when dealing * with synchronous and asynchronous data. * * ## Proxy * * A {@link #proxy} is an element that follows the mouse cursor during a drag. This may be the {@link #element}, * a newly created element, or none at all (if the purpose is to just track the cursor). * * See {@link Ext.drag.proxy.None for details}. * *      var data = [{ *          id: 1, *          name: 'Adam' *      }, { *          id: 2, *          name: 'Barbara' *      }, { *          id: 3, *          name: 'Charlie' *      }]; * *      var tpl = new Ext.XTemplate( *          '<div class="container">', *              '<tpl for=".">', *                  '<div class="child" data-id="{id}">{name}</div>', *              '</tpl>', *          '</div>' *      ); * *      var el = tpl.append(Ext.getBody(), data); * *      new Ext.drag.Source({ *          element: el, *          handle: '.child', *          proxy: { *              type: 'placeholder', *              getElement: function(info) { *                  return Ext.getBody().createChild({ *                      cls: 'foo', *                      html: info.eventTarget.innerHTML *                  }); *              } *          } *      }); *        * * ## Handle * * A {@link #handle} is a CSS selector that allows certain child elements of the {@link #element} * to begin a drag. This is useful in 2 case: * - Where only a certain part of the element should trigger a drag, but the whole element should move. * - When there are several repeated elements that may represent objects.  *  * In the example below, each child element becomes draggable and * the describe method is used to extract the id from the DOM element. * * *      var data = [{ *          id: 1, *          name: 'Adam' *      }, { *          id: 2, *          name: 'Barbara' *      }, { *          id: 3, *          name: 'Charlie' *      }]; * *      var tpl = new Ext.XTemplate( *          '<div class="container">', *              '<tpl for=".">', *                  '<div class="child" data-id="{id}">{name}</div>', *              '</tpl>', *          '</div>' *      ); * *      var el = tpl.append(Ext.getBody(), data); * *      new Ext.drag.Source({ *          element: el, *          handle: '.child', *          describe: function(info) { *              info.setData('item', Ext.fly(info.eventTarget).getAttribute('data-id')); *          } *      }); *   */Ext.define('Ext.drag.Source', {    extend: 'Ext.drag.Item',     defaultIdPrefix: 'source-',     requires: [        'Ext.GlobalEvents',        'Ext.drag.Constraint'    ],     config: {        /**         * @cfg {Boolean/String/String[]} activeOnLongPress         * `true` to always begin a drag with longpress. `false` to         * never drag with longpress. If a string (or strings) are passed, it should         * correspond to the pointer event type that should initiate a a drag on         * longpress. See {@Ext.event.Event#pointerType} for available types.         */        activateOnLongPress: false,                /**         * @cfg {String} activeCls          * A css class to add to the {@link #element} while dragging is         * active.         */        activeCls: null,         /**         * @cfg {Object/Ext.util.Region/Ext.dom.Element} constrain         *         * Adds constraining behavior for this drag source. See {@link Ext.drag.Constraint} for         * configuration options. As a shortcut, a {@link Ext.util.Region Region}          * or {@link Ext.dom.Element} may be passed, which will be mapped to the          * appropriate configuration on the constraint.         */        constrain: null,         /**         * @cfg {String} handle          * A CSS selector to identify child elements of the {@link #element} that will cause         * a drag to be activated. If this is not specified, the entire {@link #element} will         * be draggable.         */        handle: null,         // @cmd-auto-dependency {aliasPrefix: "drag.proxy."}         /**         * {String/Object/Ext.drag.proxy.Base} proxy         * The proxy to show while this element is dragging. This may be         * the alias, a config, or instance of a proxy.         *         * See {@link Ext.drag.proxy.None None}, {@link Ext.drag.proxy.Original Original},          * {@link Ext.drag.proxy.Status Status}.         */        proxy: 'original',         /**         * @cfg {Boolean/Object} revert         * `true` (or an animation configuration) to animate the {@link #proxy} (which may be         * the {@link #element}) back to the original position after drag.         */        revert: false    },     /**     * @cfg {Function} describe      * See {@link #method-describe}.     */     /**     * @event beforedragstart     * Fires before drag starts on this source. Return `false` to cancel the drag.     *      * @param {Ext.drag.Source} this This source.     * @param {Ext.drag.Info} The drag info.     * @param {Ext.event.Event} event The event.     */        /**     * @event dragstart     * Fires when the drag starts on this source.     *      * @param {Ext.drag.Source} this This source.     * @param {Ext.drag.Info} The drag info.     * @param {Ext.event.Event} event The event.     */        /**     * @event dragmove     * Fires continuously as this source is dragged.     *      * @param {Ext.drag.Source} this This source.     * @param {Ext.drag.Info} The drag info.     * @param {Ext.event.Event} event The event.     */        /**     * @event dragend     * Fires when the drag ends on this source.     *      * @param {Ext.drag.Source} this This source.     * @param {Ext.drag.Info} The drag info.     * @param {Ext.event.Event} event The event.     */        /**     * @event dragcancel     * Fires when a drag is cancelled.     *     * @param {Ext.drag.Source} this This source.     * @param {Ext.drag.Info} The drag info.     * @param {Ext.event.Event} event The event.     */        /**     * @property {Boolean} dragging      * `true` if this source is currently dragging.     *     * @protected     */    dragging: false,     constructor: function(config) {        var describe = config && config.describe;         if (describe) {            this.describe = describe;             // Don't mutate the object the user passed. Need to do this             // here otherwise initConfig will complain about writing over             // the method.             config = Ext.apply({}, config);            delete config.describe;        }        this.callParent([config]);         // Use bracket syntax to prevent Cmd from creating an         // auto dependency. Will be pulled in by the target if         // required.         this.manager = Ext.drag['Manager'];    },     /**     * @method     * Sets up the underlying data that describes the drag. This method     * is called once at the start of the drag operation.     *     * Data should be set on the {@link Ext.drag.Info info} using the      * {@link Ext.drag.Info#setData setData} method. See      * {@link Ext.drag.Info#setData setData} for more information.     *     * This method should not be called by user code.     *      * @param {Ext.drag.Info} info The drag info.     *     * @protected     */    describe: Ext.emptyFn,     /**     * Checks whether this source is actively dragging.     * @return {Boolean} `true` if this source is dragging.     */    isDragging: function() {        return this.dragging;    },     /**     * @method     * Called before a drag starts. Return `false` to veto the drag.     * @param {Ext.data.Info} The drag info.     *     * @return {Boolean} `false` to veto the drag.     *     * @protected     * @template     */    beforeDragStart: Ext.emptyFn,     /**     * @method     * Called when a drag is cancelled.     *     * @protected     * @template     */    onDragCancel: Ext.emptyFn,     /**     * @method     * Called when a drag ends.     *     * @protected     * @template     */    onDragEnd: Ext.emptyFn,     /**     * @method     * Called for each move in a drag.     *     * @protected     * @template     */    onDragMove: Ext.emptyFn,     /**     * @method     * Called when a drag starts.     *     * @protected     * @template     */    onDragStart: Ext.emptyFn,     applyActivateOnLongPress: function(activateOnLongPress) {        if (typeof activateOnLongPress === 'string') {            activateOnLongPress = [activateOnLongPress];        }        return activateOnLongPress;    },     updateActivateOnLongPress: function(activateOnLongPress) {        if (!this.isConfiguring) {            this.setupListeners();        }    },     updateActiveCls: function(cls, oldCls) {        if (this.dragging) {            if (oldCls) {                this.getElement().removeCls(oldCls);            }             if (cls) {                this.getElement().addCls(cls);            }        }    },     applyConstrain: function(constrain) {        if (constrain && !constrain.$isClass) {            if (constrain.isRegion) {                constrain = {                    region: constrain                };            } else if (constrain.isElement || !Ext.isObject(constrain)) {                constrain = {                    element: constrain                };            }            constrain = Ext.apply({                source: this            }, constrain);            constrain = Ext.Factory.dragConstraint(constrain);        }        return constrain;    },     updateElement: function(element, oldElement) {        if (element && !this.getHandle()) {            element.setTouchAction({                panX: false,                panY: false            });        }        this.callParent([element, oldElement]);    },     updateHandle: function() {        if (!this.isConfiguring) {            this.setupListeners();        }    },     applyProxy: function(proxy) {        if (proxy) {            proxy = Ext.Factory.dragproxy(proxy);        }        return proxy;    },     updateProxy: function(proxy, oldProxy) {        if (oldProxy) {            oldProxy.destroy();        }        if (proxy) {            proxy.setSource(this);        }    },     destroy: function() {        var me = this;         me.manager = me.initialEvent = null;        me.setConstrain(null);        me.setProxy(null);        me.callParent();    },     privates: {        /**         * @property {String} draggingCls          * A class to add while dragging to give a high z-index and         * disable pointer events.         *         * @private         */        draggingCls: Ext.baseCSSPrefix + 'drag-dragging',         /**         * @property {Ext.drag.Info} info         * The info. Only available while a drag is active.         *         * @private         */        info: null,         /**         * @property {String} revertCls          * A class to add to the proxy element while a revert is active.         *         * @private         */        revertCls: Ext.baseCSSPrefix + 'drag-revert',         canActivateOnLongPress: function(e) {            var activate = this.getActivateOnLongPress();            return !!(activate && (activate === true || Ext.Array.contains(activate, e.pointerType)));        },         /**         * Perform any cleanup after a drag.         *         * @private         */        dragCleanup: function(info) {            var me = this,                cls = me.getActiveCls(),                proxy = me.getProxy(),                el = me.getElement(),                proxyEl = info ? info.proxy.element : null;             if (cls) {                el.removeCls(cls);            }            if (proxyEl) {                proxyEl.removeCls(me.draggingCls);            }            proxy.cleanup(info);             me.dragging = false;            me.initialEvent = me.info = null;        },         /**         * @inheritdoc         */        getElListeners: function() {            var o = {                touchstart: 'handleTouchStart',                dragstart: 'handleDragStart',                drag: 'handleDragMove',                dragend: 'handleDragEnd',                dragcancel: 'handleDragCancel'            }, handle = this.getHandle();             if (handle) {                o.delegate = handle;            }            if (this.getActivateOnLongPress()) {                o.longpress = 'handleLongPress';            }            return o;        },         /**         * Called when a drag is cancelled.         * @param {Ext.event.Event} e The event.         *         * @private         */        handleDragCancel: function(e) {            var me = this,                info = me.info,                manager = me.manager;             if (manager) {                manager.onDragCancel(info, e);            }            me.onDragCancel(info);            if (me.hasListeners.dragcancel) {                me.fireEvent('dragcancel', me, info, e);            }             Ext.fireEvent('dragcancel', me, info, e);             me.dragCleanup(info);        },         /**         * Called when a drag is ended.         * @param {Ext.event.Event} e The event.         *         * @private         */        handleDragEnd: function(e) {            if (!this.dragging) {                return;            }             var me = this,                manager = me.manager,                revert = me.getRevert(),                info = me.info,                proxy = info.proxy.initial,                proxyEl = me.info.proxy.element;             info.update(e);             if (manager) {                manager.onDragEnd(info, e);            }            me.onDragEnd(info);            if (me.hasListeners.dragend) {                me.fireEvent('dragend', me, info, e);            }            Ext.fireEvent('dragend', me, info, e);             if (revert && proxyEl) {                proxyEl.addCls(me.revertCls);                proxyEl.setXY([proxy.x, proxy.y], Ext.apply({                    callback: function() {                        proxyEl.removeCls(me.revertCls);                        me.dragCleanup(info);                    }                }, revert));            } else {                me.dragCleanup(info);            }        },         /**         * Called for each drag movement.         * @param {Ext.event.Event} e The event.         *         * @private         */        handleDragMove: function(e) {            var me = this,                info = me.info,                manager = me.manager;             if (!me.dragging) {                return;            }             e.stopPropagation();            e.claimGesture();            info.update(e);             if (manager) {                manager.onDragMove(info, e);            }            me.onDragMove(info);            if (me.hasListeners.dragmove) {                me.fireEvent('dragmove', me, info, e);            }        },         /**         * Called when a drag is started.         * @param {Ext.event.Event} e The event.         *         * @private         */        handleDragStart: function(e) {            var me = this,                hasListeners = me.hasListeners,                manager = me.manager,                constrain = me.getConstrain(),                initialEvent = me.initialEvent,                el, cls, info, cancel, proxyEl;             if (me.preventStart(e)) {                return false;            }             me.info = info = new Ext.drag.Info(me, initialEvent);             me.setup(info);             if (constrain) {                constrain.onDragStart(info);            }             info.update(e, true);             cancel = me.beforeDragStart(info) === false;             if (!cancel && hasListeners.beforedragstart) {                cancel = me.fireEvent('beforedragstart', me, info, e) === false;            }             if (cancel) {                me.dragCleanup();                return false;            }             e.claimGesture();            me.dragging = true;                    cls = me.getActiveCls();            el = me.getElement();            if (cls) {                el.addCls(cls);            }            proxyEl = info.proxy.element;            if (proxyEl) {                proxyEl.addCls(me.draggingCls);            }             info.update(e);            if (manager) {                manager.onDragStart(info, e);            }            me.onDragStart(info);            if (hasListeners.dragstart) {                me.fireEvent('dragstart', me, info, e);            }            Ext.fireEvent('dragstart', me, info, e);        },         /**         * Called when a longpress is started on this target (which may lead to a drag)         * @param {Ext.event.Event} e The event.         *         * @private         */        handleLongPress: function(e) {            if (!this.isDisabled() && this.canActivateOnLongPress(e)) {                 this.initialEvent = e;                e.startDrag();            }        },         /**         * Called when a touch starts on this target (which may lead to a drag).         * @param {Ext.event.Event} e The event.         *         * @private         */        handleTouchStart: function(e) {            if (!this.isDisabled()) {                this.initialEvent = e;            }        },         preventStart: function(e) {            return this.isDisabled() || (!e.longpress && this.canActivateOnLongPress(e));        },         /**         * Allow for any setup as soon as the info object is created.         *         * @private         */        setup: Ext.privateFn    }});