    /** * Provides constraining behavior for a {@link Ext.drag.Source}. */Ext.define('Ext.drag.Constraint', {    alias: 'drag.constraint.base',    mixins: [        'Ext.mixin.Factoryable'    ],     factoryConfig: {        defaultType: 'base',        type: 'drag.constraint'    },     config: {        /**         * @cfg {Boolean/String/HTMLELement/Ext.dom.Element} element         *         * The element to constrain to:         * - `true` to constrain to the parent of the {@link Ext.drag.Source#element}.         * - The id, DOM element or Ext.dom.Element to constrain to.         */        element: null,         /**         * @cfg {Boolean} horizontal          * `true` to limit dragging to the horizontal axis.         */        horizontal: false,         /**         * @cfg {Ext.util.Region} region         *         * The region to constrain to.         */        region: null,         /**         * @cfg {Number/Object} snap         * The interval to move this drag target during a drag in both dimensions.         * - `{x: 30}`, snap only x         * - `{y: 30}`, snap only y         * - `{x: 30, y: 40}`, snap both         * - `40`, snap both to `40`.         *         * The snap may also be a function to calculate the snap value on each tick.         *         *      snap: {         *          x: function(info, x) {         *              return x < 300 ? 150 : 500;         *          }         *      }         */        snap: null,         /**         * @cfg {Ext.drag.Source} source         * The {@link Ext.drag.Source source} for the constraint. This will be         * set automatically when constructed via the source.         */        source: null,         /**         * @cfg {Boolean} vertical          * `true` to limit dragging to the vertical axis.         */        vertical: false,         /**         * @cfg {Number[]} x         * The minimum and maximum x position. Use `null` to         * not set a constraint:         * - `[100, null]`, constrain only the minimum         * - `[null, 100]`, constrain only the maximum         * - `[200, 200]`, constrain both.         */        x: null,         /**         * @cfg {Number[]} y         * The minimum and maximum y position. Use `null` to         * not set a constraint:         * - `[100, null]`, constrain only the minimum         * - `[null, 100]`, constrain only the maximum         * - `[200, 200]`, constrain both.         */        y: null    },     constructor: function(config) {        this.initConfig(config);    },     applyElement: function(element) {        if (element) {            if (typeof element === 'boolean') {                element = this.getSource().getElement().parent();            } else {                element = Ext.get(element);            }        }        return element || null;    },     applySnap: function(snap) {        if (typeof snap === 'number') {            snap = {                x: snap,                y: snap            };        }        return snap;    },     /**     * Constrain the position of the drag proxy using the configured rules.     *     * @param {Number[]} xy The position.     * @param {Ext.drag.Info} info The drag information.     *      * @return {Number[]} The xy position.     */    constrain: function(xy, info) {        var me = this,            x = xy[0],            y = xy[1],            constrainInfo = me.constrainInfo,            initial = constrainInfo.initial,            constrainX = constrainInfo.x,            constrainY = constrainInfo.y,            snap = constrainInfo.snap,            min, max;         if (!constrainInfo.vertical) {            if (snap && snap.x) {                if (snap.xFn) {                    x = snap.x.call(me, info, x);                } else {                    x = me.doSnap(x, initial.x, snap.x);                }            }             if (constrainX) {                min = constrainX[0];                max = constrainX[1];                 if (min !== null && x < min) {                    x = min;                }                 if (max !== null && x > max) {                    x = max;                }            }        } else {            x = initial.x;        }         if (!constrainInfo.horizontal) {            if (snap && snap.y) {                if (snap.yFn) {                    y = snap.y.call(me, info, y);                } else {                    y = me.doSnap(y, initial.y, snap.y);                }            }             if (constrainY) {                min = constrainY[0];                max = constrainY[1];                 if (min !== null && y < min) {                    y = min;                }                 if (max !== null && y > max) {                    y = max;                }            }        } else {            y = initial.y;        }         return [x, y];    },     destroy: function() {        this.setSource(null);        this.setElement(null);        this.callParent();    },     privates: {        /**         * Constrains 2 values, while taking into         * account nulls.          * @param {Number} a The first value.         * @param {Number} b The second value.         * @param {Function} resolver The function to resolve the value if         * both are non null.         * @return {Number} If both values are `null`, `null`. If `a` is null, `b`.         * If `b` is null, `a`, otherwise the result of the resolver, passing a & b.         *         * @private         */        constrainValue: function(a, b, resolver) {            var val = null,                aNull = a === null,                bNull = b === null;             if (!(aNull && bNull)) {                if (aNull) {                    val = b;                } else if (bNull) {                    val = a;                } else {                    val = resolver(a, b);                }            }            return val;        },         /**         * Calculates the position to move the proxy element         * to when using snapping.         *          * @param {Number} position The current mouse position.         * @param {Number} initial The start position.         * @param {Number} snap The snap position.         * @return {Number} The snapped position.         *         * @private         */        doSnap: function(position, initial, snap) {            if (!snap) {                return position;            }             var ratio = (position - initial) / snap,                floor = Math.floor(ratio);             // Check whether we need to snap less than current, or             // greater than current position.             if (ratio - floor <= 0.5) {                ratio = floor;            } else {                ratio = floor + 1;            }             return initial + (snap * ratio);        },         /**         * Setup data that is needed during a drag for monitoring constraints.         * Attempt to merge min/max values with any constrain region.         *         * @param {Ext.drag.Info} info The drag info.         *         * @private         */        onDragStart: function(info) {            var me = this,                snap = me.getSnap(),                vertical = me.getVertical(),                horizontal = me.getHorizontal(),                element = me.getElement(),                region = me.getRegion(),                proxy = info.proxy,                proxyEl = proxy.element,                x = me.getX(),                y = me.getY(),                minX = null,                maxX = null,                minY = null,                maxY = null,                rminX = null,                rmaxX = null,                rminY = null,                rmaxY = null;             if (element) {                region = element.getRegion(true);            }             if (region) {                if (!vertical) {                    rminX = region.left;                    rmaxX = region.right - (proxyEl ? proxy.width : 0);                }                 if (!horizontal) {                    rminY = region.top;                    rmaxY = region.bottom - (proxyEl ? proxy.height : 0);                }            }              // The following piece sets up the numeric values for our constraint.             // If there is an axis constraint, don't bother calculating the values since             // it is already explicitly constrained so we can shortcut that portion.             //              // Attempt to merge the appropriate min/max values (if needed). With:             // a) A region and a minimum, the larger value is needed (stricter constraint)             // b) A region and a maximum, the smaller value is needed (stricter constraint)              if (!vertical && (region || x)) {                if (x) {                    minX = x[0];                    maxX = x[1];                }                 if (minX !== null || maxX !== null || rminX !== null || rmaxX !== null) {                    minX = me.constrainValue(minX, rminX, Math.max);                    maxX = me.constrainValue(maxX, rmaxX, Math.min);                    x = [minX, maxX];                }            }             if (!horizontal && (region || y)) {                if (y) {                    minY = y[0];                    maxY = y[1];                }                if (minY !== null || maxY !== null || rminY !== null || rmaxY !== null) {                    minY = me.constrainValue(minY, rminY, Math.max);                    maxY = me.constrainValue(maxY, rmaxY, Math.min);                    y = [minY, maxY];                }            }             if (snap) {                snap = {                    x: snap.x,                    xFn: typeof snap.x === 'function',                    y: snap.y,                    yFn: typeof snap.y === 'function'                };            }             me.constrainInfo = {                initial: info.element.initial,                vertical: me.getVertical(),                horizontal: me.getHorizontal(),                x: x,                y: y,                snap: snap            };        }    }});